// White Space Tracker — Map · List · Meta-Data (services reference).
// Add Project is a modal launched from List and Meta-Data, not its own tab.
// Data persists in Supabase (white_space_projects); falls back to static seed.
(function () {
  const { useState, useMemo, useEffect } = React;
  const WS = () => window.VaultWhiteSpace;
  const F = () => window.VAULT_FIRM || {};

  const BLUE = "#196EA7", INK = "#1A2733", INK2 = "#5A6B7B", MUTE = "#93A1B0", LINE = "#E4E9F0";

  function stateToRegion() {
    const rs = (WS().data().regionStates) || {};
    const m = {};
    Object.keys(rs).forEach(r => (rs[r] || []).forEach(s => { m[s] = r; }));
    return m;
  }
  // sub-teams, cleaned: drop "-direct", strip "Direct" labels, sort alphabetically
  function cleanSubteams() {
    return (F().SUBSUBTEAMS || [])
      .filter(st => !String(st.id).endsWith("-direct") && !/ Direct$/.test(st.label || ""))
      .slice().sort((a, b) => (a.label || "").localeCompare(b.label || ""));
  }

  // region coverage given projects + scoped services
  // Binary coverage: covered (red) = a filtered project is retained here; open (green); unknown (white)
  const C_OPEN = "#7BC96F", C_COVERED = "#D9534F", C_BLANK = "#F4F6F8";
  function statusColor(status) { return status === "covered" ? C_COVERED : status === "open" ? C_OPEN : C_BLANK; }

  // For a region: is any of the (already-filtered) projects retained there, respecting service scope?
  // scopeServices (from keyword/category/service) limits WHICH service must match; null = any service.
  function regionStatusMap(projects, scopeServices, anyActive) {
    const out = {};
    const regions = WS().data().regions || [];
    regions.forEach(rg => {
      if (!anyActive) { out[rg] = "blank"; return; }
      const covered = projects.some(p => {
        if (!(p.regions || []).includes(rg)) return false;
        if (scopeServices && scopeServices.length) return (p.services || []).some(s => scopeServices.includes(s));
        return true;
      });
      out[rg] = covered ? "covered" : "open";
    });
    return out;
  }

  // ── FULL US MAP ──────────────────────────────────────────────────────────────
  function USMap({ projects, scopeServices, anyActive, onPickRegion }) {
    const paths = window.US_STATE_PATHS || {};
    const labels = window.US_STATE_LABELS || {};
    const small = window.US_SMALL_STATES || [];
    const s2r = useMemo(stateToRegion, []);
    const [hover, setHover] = useState(null);
    const regionStat = useMemo(() => regionStatusMap(projects, scopeServices, anyActive), [projects, scopeServices, anyActive]);
    const leaderX = 940;
    const leaderYs = { VT: 70, NH: 95, MA: 120, RI: 145, CT: 170, NJ: 195, DE: 220, MD: 245, DC: 270 };
    const statText = { covered: "Covered", open: "Open", blank: "" };
    return (
      <svg viewBox="0 0 970 550" style={{ width: "100%", display: "block" }} onMouseLeave={() => setHover(null)}>
        {Object.keys(paths).map(ab => {
          const region = s2r[ab];
          const st = region != null ? regionStat[region] : "blank";
          const isHover = hover && s2r[hover] === region;
          return (
            <path key={ab} d={paths[ab]} fill={statusColor(st)} stroke="#1A2733" strokeWidth={isHover ? 2 : 1}
              style={{ cursor: region ? "pointer" : "default", transition: "fill .12s", fillOpacity: isHover ? 0.85 : 1 }}
              onClick={() => region && onPickRegion && onPickRegion(region)} onMouseEnter={() => setHover(ab)}>
              <title>{ab}{(window.US_STATE_TZ || {})[ab] ? ` (${window.US_STATE_TZ[ab]})` : ""}{region && st !== "blank" ? ` · ${region} · ${statText[st]}` : (region ? ` · ${region}` : "")}</title>
            </path>
          );
        })}
        {Object.keys(labels).filter(ab => !small.includes(ab)).map(ab => (
          <text key={ab} x={labels[ab][0]} y={labels[ab][1] + 3} textAnchor="middle" fontSize="11" fontWeight="600"
            fill="#1A2733" pointerEvents="none" style={{ userSelect: "none" }}>{ab}</text>
        ))}
        {small.filter(ab => labels[ab]).map(ab => {
          const [cx, cy] = labels[ab]; const ly = leaderYs[ab] || cy;
          return (
            <g key={ab} pointerEvents="none">
              <line x1={cx} y1={cy} x2={leaderX - 4} y2={ly} stroke="#9AA7B4" strokeWidth="0.6" />
              <text x={leaderX} y={ly + 3} fontSize="10" fontWeight="600" fill="#1A2733">{ab}</text>
            </g>
          );
        })}
        {hover && s2r[hover] && regionStat[s2r[hover]] !== "blank" && (
          <text x="485" y="544" textAnchor="middle" fontSize="13" fontWeight="600" fill={INK}>
            {hover} — {s2r[hover]} · {statText[regionStat[s2r[hover]]]}
          </text>
        )}
      </svg>
    );
  }

  // Zoomed single-region map: shows just that region's states, click a state for detail.
  function RegionMap({ region, onlyState, projects, scopeServices, mapActive, onPickState, onBack }) {
    const paths = window.US_STATE_PATHS || {};
    const allRs = (WS().data().regionStates || {})[region] || [];
    const rs = onlyState ? [onlyState] : allRs;
    const [hover, setHover] = useState(null);
    // compute bbox of the shown states to fit the viewBox
    const box = useMemo(() => {
      let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9;
      rs.forEach(ab => {
        const d = paths[ab]; if (!d) return;
        const nums = (d.match(/-?\d+\.?\d*/g) || []).map(Number);
        for (let i = 0; i < nums.length; i += 2) { x0 = Math.min(x0, nums[i]); x1 = Math.max(x1, nums[i]); y0 = Math.min(y0, nums[i + 1]); y1 = Math.max(y1, nums[i + 1]); }
      });
      const pad = 20;
      return { x: x0 - pad, y: y0 - pad, w: (x1 - x0) + pad * 2, h: (y1 - y0) + pad * 2 };
    }, [region, onlyState]);
    const regionStat = useMemo(() => regionStatusMap(projects, scopeServices, true)[region], [projects, scopeServices, region]);
    const fillStat = mapActive ? regionStat : "blank";
    const labels = window.US_STATE_LABELS || {};
    return (
      <svg viewBox={`${box.x} ${box.y} ${box.w} ${box.h}`} style={{ width: "100%", display: "block" }} onMouseLeave={() => setHover(null)}>
        {rs.map(ab => paths[ab] && (
          <path key={ab} d={paths[ab]} fill={statusColor(fillStat)} stroke="#1A2733" strokeWidth={hover === ab ? 1.6 : 0.9}
            style={{ cursor: "pointer", fillOpacity: hover === ab ? 0.85 : 1 }}
            onClick={() => onPickState && onPickState(ab)} onMouseEnter={() => setHover(ab)}>
            <title>{ab}</title>
          </path>
        ))}
        {rs.map(ab => labels[ab] && (
          <text key={ab} x={labels[ab][0]} y={labels[ab][1] + 4} textAnchor="middle" fontSize={Math.max(7, box.w / 40)} fontWeight="600" fill="#1A2733" pointerEvents="none">{ab}</text>
        ))}
      </svg>
    );
  }

  function Legend({ horizontal }) {
    const rows = [["open", "Open"], ["covered", "Covered"]];
    if (horizontal) {
      return (
        <div style={{ display: "inline-flex", alignItems: "center", gap: 14, padding: "6px 12px", border: `1px solid ${LINE}`, borderRadius: 9, background: "#fff" }}>
          <span style={{ fontSize: 10.5, fontWeight: 600, color: MUTE, textTransform: "uppercase", letterSpacing: ".04em" }}>Legend</span>
          {rows.map(([k, lbl], i) => (
            <span key={i} style={{ display: "inline-flex", alignItems: "center", gap: 5 }}>
              <span style={{ width: 16, height: 12, borderRadius: 3, background: statusColor(k), border: "1px solid rgba(0,0,0,.12)" }} />
              <span style={{ fontSize: 11, color: INK }}>{lbl}</span>
            </span>
          ))}
        </div>
      );
    }
    return (
      <div style={{ display: "inline-flex", flexDirection: "column", gap: 6 }}>
        <div style={{ fontSize: 11, fontWeight: 600, color: MUTE, textTransform: "uppercase", letterSpacing: ".04em", marginBottom: 2 }}>Legend</div>
        {rows.map(([k, lbl], i) => (
          <div key={i} style={{ display: "flex", alignItems: "center", gap: 9 }}>
            <span style={{ width: 22, height: 14, borderRadius: 3, background: statusColor(k), border: "1px solid rgba(0,0,0,.12)", flexShrink: 0 }} />
            <span style={{ fontSize: 12, color: INK }}>{lbl}</span>
          </div>
        ))}
      </div>
    );
  }

  // ── FILTERS ────────────────────────────────────────────────────────────────
  function useFilters(projects) {
    const [kw, setKw] = useState("");
    const [subTeam, setSubTeam] = useState("all");
    const [region, setRegion] = useState("all");
    const [client, setClient] = useState("all");
    const [type, setType] = useState("all");
    const [sector, setSector] = useState("all");
    const [active, setActive] = useState("active");

    // a single predicate per dimension, so we can compute "all filters except X" for self-pruning
    function matchKw(p, q) { if (!q) return true; return [p.project, p.client, p.sector, p.category, (p.services || []).join(" ")].join(" ").toLowerCase().includes(q); }
    const preds = {
      active: p => active === "all" ? true : (active === "active" ? p.active !== false : p.active === false),
      subTeam: p => subTeam === "all" || p.subTeam === subTeam,
      region: p => region === "all" || (p.regions || []).includes(region),
      client: p => client === "all" || p.project === client,
      type: p => type === "all" || (p.engagementType || "").toLowerCase() === type,
      sector: p => sector === "all" || p.sector === sector,
      kw: p => matchKw(p, kw.trim().toLowerCase()),
    };
    // projects matching ALL predicates except the named one (for that dropdown's options)
    function matchingExcept(except) {
      return projects.filter(p => Object.keys(preds).every(k => k === except || preds[k](p)));
    }
    const filtered = useMemo(() => projects.filter(p => Object.keys(preds).every(k => preds[k](p))),
      [projects, kw, subTeam, region, client, type, sector, active]);

    // any filter active? (drives blank-by-default map)
    // region is a navigation/zoom choice, NOT a coverage filter — exclude it from anyActive
    // so zooming into a region without other filters keeps the map blank (white), not colored.
    const anyActive = subTeam !== "all" || client !== "all" || type !== "all" || sector !== "all" || kw.trim() !== "";

    // self-pruning option lists: only values that still yield results given the OTHER filters
    const opts = useMemo(() => {
      const uniq = (arr) => Array.from(new Set(arr.filter(Boolean))).sort();
      const stIds = new Set(matchingExcept("subTeam").map(p => p.subTeam).filter(Boolean));
      const clientVals = uniq(matchingExcept("client").map(p => p.project));
      const sectorVals = uniq(matchingExcept("sector").map(p => p.sector));
      const typeVals = new Set(matchingExcept("type").map(p => (p.engagementType || "").toLowerCase()).filter(Boolean));
      const regionVals = (WS().data().regions || []).filter(r => matchingExcept("region").some(p => (p.regions || []).includes(r)));
      const subteams = cleanSubteams().filter(st => stIds.has(st.id));
      return { clients: clientVals, sectors: sectorVals, regions: regionVals, subteams, types: typeVals };
    }, [projects, kw, subTeam, region, client, type, sector, active]);

    // keyword-scoped services (for map service-level matching + the service dropdown elsewhere)
    const kwServices = useMemo(() => {
      const q = kw.trim().toLowerCase();
      if (!q) return null;
      const hit = WS().allServices().filter(s => s.service.toLowerCase().includes(q) || (s.category || "").toLowerCase().includes(q)).map(s => s.service);
      return hit.length ? hit : null;
    }, [kw]);

    const bar = (
      <div>
        <div style={{ position: "relative", marginBottom: 12 }}>
          <input value={kw} onChange={e => setKw(e.target.value)} placeholder="Search projects, clients, services, categories…"
            style={{ width: "100%", padding: "11px 14px 11px 38px", borderRadius: 10, border: `1px solid ${LINE}`, fontSize: 14, color: INK, background: "#fff", boxSizing: "border-box" }} />
          <span style={{ position: "absolute", left: 13, top: "50%", transform: "translateY(-50%)", color: MUTE, fontSize: 15 }}>⌕</span>
        </div>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
          <select value={subTeam} onChange={e => setSubTeam(e.target.value)} style={fSel}>
            <option value="all">All sub-teams</option>
            {opts.subteams.map(st => <option key={st.id} value={st.id}>{st.label}</option>)}
          </select>
          <select value={region} onChange={e => setRegion(e.target.value)} style={fSel}>
            <option value="all">All regions</option>{opts.regions.map(r => <option key={r} value={r}>{r}</option>)}
          </select>
          <select value={client} onChange={e => setClient(e.target.value)} style={fSel}>
            <option value="all">All projects</option>{opts.clients.map(c => <option key={c} value={c}>{c}</option>)}
          </select>
          <select value={type} onChange={e => setType(e.target.value)} style={fSel}>
            <option value="all">All types</option>
            {["platform", "add-on", "ceo"].filter(t => opts.types.has(t)).map(t => <option key={t} value={t}>{t === "add-on" ? "Add-On" : t.charAt(0).toUpperCase() + t.slice(1)}</option>)}
          </select>
          <select value={sector} onChange={e => setSector(e.target.value)} style={fSel}>
            <option value="all">All sectors</option>{opts.sectors.map(s => <option key={s} value={s}>{s}</option>)}
          </select>
          <select value={active} onChange={e => setActive(e.target.value)} style={fSel}>
            <option value="active">Active only</option><option value="all">Active + inactive</option><option value="inactive">Inactive only</option>
          </select>
          {anyActive && <button onClick={() => { setKw(""); setSubTeam("all"); setRegion("all"); setClient("all"); setType("all"); setSector("all"); setActive("active"); }} style={{ ...fSel, color: BLUE, cursor: "pointer", maxWidth: "none" }}>Clear filters</button>}
          <span style={{ fontSize: 12, color: MUTE, marginLeft: "auto" }}>{filtered.length} projects</span>
        </div>
      </div>
    );
    return { filtered, anyActive, kwServices, bar, kw, region, setRegion };
  }

  // ── MAP TAB ──────────────────────────────────────────────────────────────────
  function MapTab({ projects }) {
    const { filtered, anyActive, kwServices, bar, region, setRegion } = useFilters(projects);
    const [category, setCategory] = useState("");
    const [service, setService] = useState("");
    const [zoomRegion, setZoomRegion] = useState(null);
    const [pickedState, setPickedState] = useState(null);
    const cats = useMemo(() => WS().categories(), []);

    // selecting a region in the filter bar zooms to it (same as clicking the map)
    // dropdown region change drives zoom; guard so it only fires on actual dropdown changes
    useEffect(() => {
      if (region && region !== "all") { setZoomRegion(region); setPickedState(null); }
    }, [region]);
    function backToFull() { setZoomRegion(null); setPickedState(null); if (region !== "all") setRegion("all"); }
    // service dropdown options also prune to services actually held by the filtered projects
    const heldServices = useMemo(() => {
      const set = new Set(); filtered.forEach(p => (p.services || []).forEach(s => set.add(s))); return set;
    }, [filtered]);
    const svcOptions = useMemo(() => WS().allServices(category || null).map(s => s.service).filter(s => heldServices.has(s)), [category, heldServices]);

    // scopeServices: which services define a "conflict". service > category > keyword-matched.
    const scopeServices = useMemo(() => {
      if (service) return [service];
      if (category) return WS().allServices(category).map(s => s.service);
      return kwServices; // may be null
    }, [category, service, kwServices]);
    // the map shows coverage when ANY filter OR a category/service scope is set
    const mapActive = anyActive || !!category || !!service;
    const scopeLabel = service || category || "current filters";

    function regionDetail(region) {
      const target = region || zoomRegion; // when a state is picked, show its region's projects
      const svcScope = scopeServices;
      const inRegion = filtered.filter(p => {
        if (!(p.regions || []).includes(target)) return false;
        if (svcScope && svcScope.length) return (p.services || []).some(s => svcScope.includes(s));
        return true;
      });
      return { region: target, activeProjects: inRegion, covered: inRegion.length > 0 };
    }

    return (
      <div>
        {bar}
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", margin: "14px 0 18px", alignItems: "center" }}>
          <select value={category} onChange={e => { setCategory(e.target.value); setService(""); }} style={fSel}>
            <option value="">All categories</option>{cats.filter(c => WS().allServices(c).some(s => heldServices.has(s.service))).map(c => <option key={c} value={c}>{c}</option>)}
          </select>
          <select value={service} onChange={e => setService(e.target.value)} style={fSel} disabled={!svcOptions.length}>
            <option value="">{category ? "All services in category" : "All services"}</option>{svcOptions.map(s => <option key={s} value={s}>{s}</option>)}
          </select>
        </div>

        {zoomRegion ? (
          <div>
            <button onClick={() => { if (pickedState) { setPickedState(null); } else { backToFull(); } }} style={{ display: "inline-flex", alignItems: "center", gap: 6, border: "none", background: "transparent", color: BLUE, fontSize: 13, fontWeight: 600, cursor: "pointer", marginBottom: 8 }}>
              ‹ {pickedState ? `Back to ${zoomRegion}` : "Full Map"}
            </button>
            <div style={{ display: "flex", gap: 20, alignItems: "flex-start", flexWrap: "wrap" }}>
              <div style={{ flex: "1 1 420px", minWidth: 320 }}>
                <h3 style={{ fontSize: 16, fontWeight: 600, color: INK, marginBottom: 4 }}>{pickedState ? `${pickedState} — ${zoomRegion}` : zoomRegion}</h3>
                <p style={{ fontSize: 12, color: MUTE, marginBottom: 10 }}>{pickedState ? "Showing one state." : "Click a State for Its Detail"}</p>
                <RegionMap region={zoomRegion} onlyState={pickedState} projects={filtered} scopeServices={scopeServices} mapActive={mapActive} onPickState={setPickedState} />
              </div>
              <div style={{ flex: "1 1 340px", minWidth: 300 }}>
                <RegionPanel detail={regionDetail(pickedState ? null : zoomRegion)} pickedState={pickedState} mapActive={mapActive} scopeLabel={scopeLabel} />
              </div>
            </div>
          </div>
        ) : (
          <div>
            <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 4 }}><Legend horizontal /></div>
            <USMap projects={filtered} scopeServices={scopeServices} anyActive={mapActive} onPickRegion={(r) => { setZoomRegion(r); setPickedState(null); setRegion(r); }} />
            {!mapActive && <p style={{ fontSize: 13, color: MUTE, textAlign: "center", marginTop: 14 }}>Click any region to see its projects, or add a filter to map coverage.</p>}
          </div>
        )}
      </div>
    );
  }

  function RegionPanel({ detail, pickedState, mapActive, scopeLabel }) {
    const n = detail.activeProjects.length;
    return (
      <div style={{ border: `1px solid ${LINE}`, borderRadius: 12, background: "#fff", overflow: "hidden" }}>
        <div style={{ background: BLUE, color: "#fff", padding: "14px 18px" }}>
          <div style={{ fontSize: 16, fontWeight: 600 }}>{pickedState ? `${pickedState} · ${detail.region}` : detail.region}{pickedState && (window.US_STATE_TZ || {})[pickedState] ? ` · ${window.US_STATE_TZ[pickedState]}` : ""}</div>
          <div style={{ fontSize: 12, opacity: 0.85, marginTop: 2 }}>
            {mapActive ? `${detail.covered ? "Covered" : "Open"} · ` : ""}{n} active {n === 1 ? "project" : "projects"}
          </div>
        </div>
        <div style={{ padding: "14px 18px" }}>
          <div style={{ ...fl, marginBottom: 8 }}>{mapActive ? `Active Here (${n})` : `Projects Here (${n})`}</div>
          {n === 0
            ? <p style={{ fontSize: 13, color: "#2E7D32", margin: "4px 0 10px" }}>{mapActive ? `Open — No Retained Projects for ${scopeLabel}.` : "No Projects Here."}</p>
            : (
              // cap visible height to ~5 cards (each ~62px) with a scrollbar for the rest
              <div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 8, maxHeight: 330, overflowY: "auto", paddingRight: n > 5 ? 4 : 0 }}>
                {detail.activeProjects.map((p, i) => (
                  <div key={i} style={{ border: `1px solid ${LINE}`, borderRadius: 9, padding: "9px 12px", flexShrink: 0 }}>
                    <div style={{ fontSize: 13.5, fontWeight: 600, color: INK }}>{window.HarveyLink ? <window.HarveyLink projectId={p.projectId}>{p.project}</window.HarveyLink> : p.project}</div>
                    <div style={{ fontSize: 12, color: INK2, marginTop: 2 }}>{p.client}{p.engagementType ? ` · ${p.engagementType}` : ""}</div>
                    <div style={{ fontSize: 11.5, color: MUTE, marginTop: 4 }}>{(p.services || []).join(", ")}</div>
                  </div>
                ))}
              </div>
            )}
          {n > 5 && <div style={{ fontSize: 11, color: MUTE, textAlign: "center" }}>Scroll for {n - 5} more</div>}
        </div>
      </div>
    );
  }

  // ── LIST TAB ───────────────────────────────────────────────────────────────
  function ListTab({ projects, onEdit, onAdd }) {
    const { filtered, bar } = useFilters(projects);
    const { sorted, header } = useSort(filtered, "project");
    return (
      <div>
        <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 12 }}>
          <button onClick={onAdd} style={addBtn}>+ Add project</button>
        </div>
        {bar}
        <div style={{ overflowX: "auto", border: `1px solid ${LINE}`, borderRadius: 12, marginTop: 14 }}>
          <table style={{ borderCollapse: "collapse", fontSize: 12.5, width: "100%", minWidth: 820 }}>
            <thead><tr>
              {header("project", "Project")}{header("client", "Client")}{header("sector", "Sector")}
              {header("category", "Category")}<th style={th}>Services</th>{header("regions", "Regions")}<th style={{ ...th, textAlign: "right" }}></th>
            </tr></thead>
            <tbody>
              {sorted.map((p, i) => (
                <tr key={p.projectId || i} style={{ borderTop: `1px solid ${LINE}` }}>
                  <td style={{ ...td, fontWeight: 600 }}>{window.HarveyLink ? <window.HarveyLink projectId={p.projectId}>{p.project}</window.HarveyLink> : p.project}{p.active === false && <span style={inactiveTag}>inactive</span>}</td>
                  <td style={td}>{p.client}</td>
                  <td style={td}>{p.sector}</td>
                  <td style={td}>{p.category}</td>
                  <td style={{ ...td, color: INK2 }}>{(p.services || []).join(", ")}</td>
                  <td style={{ ...td, color: INK2 }}>{(p.regions || []).length}</td>
                  <td style={{ ...td, textAlign: "right" }}><button onClick={() => onEdit(p)} style={editBtn}>Edit</button></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    );
  }

  // ── META-DATA TAB (services reference) ─────────────────────────────────────────
  function MetaTab({ onAdd }) {
    const services = WS().data().services || [];
    const [kw, setKw] = useState("");
    const [market, setMarket] = useState("all");
    const markets = useMemo(() => Array.from(new Set(services.map(s => s.market).filter(Boolean))).sort(), [services]);
    const filteredRows = useMemo(() => {
      const q = kw.trim().toLowerCase();
      return services.filter(s => {
        if (market !== "all" && s.market !== market) return false;
        if (q) return [s.service, s.category, s.market, s.definition, s.businessType, s.trade, s.exampleCompany].join(" ").toLowerCase().includes(q);
        return true;
      });
    }, [services, kw, market]);
    const { sorted, header } = useSort(filteredRows, "service");

    return (
      <div>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12, gap: 12, flexWrap: "wrap" }}>
          <p style={{ fontSize: 13, color: INK2, margin: 0 }}>The services reference — every market, category, and service the firm classifies, with definitions, business type, trade, and example companies.</p>
          <button onClick={onAdd} style={addBtn}>+ Add project</button>
        </div>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 14, alignItems: "center" }}>
          <div style={{ position: "relative", flex: "1 1 280px" }}>
            <input value={kw} onChange={e => setKw(e.target.value)} placeholder="Search services, definitions, companies…"
              style={{ width: "100%", padding: "9px 12px 9px 34px", borderRadius: 9, border: `1px solid ${LINE}`, fontSize: 13, color: INK, background: "#fff", boxSizing: "border-box" }} />
            <span style={{ position: "absolute", left: 11, top: "50%", transform: "translateY(-50%)", color: MUTE }}>⌕</span>
          </div>
          <select value={market} onChange={e => setMarket(e.target.value)} style={fSel}>
            <option value="all">All markets</option>{markets.map(m => <option key={m} value={m}>{m}</option>)}
          </select>
          <span style={{ fontSize: 12, color: MUTE }}>{sorted.length} services</span>
        </div>
        <div style={{ overflowX: "auto", border: `1px solid ${LINE}`, borderRadius: 12 }}>
          <table style={{ borderCollapse: "collapse", fontSize: 12, width: "100%", minWidth: 1100 }}>
            <thead><tr>
              {header("market", "Market")}{header("category", "Category")}{header("service", "Service")}
              <th style={th}>Definition</th>{header("businessType", "Business Type")}{header("revenueModel", "Revenue Model")}
              {header("trade", "Trade")}<th style={th}>Codes & Reg.</th>{header("exampleCompany", "Example Company")}
            </tr></thead>
            <tbody>
              {sorted.map((s, i) => (
                <tr key={i} style={{ borderTop: `1px solid ${LINE}`, verticalAlign: "top" }}>
                  <td style={{ ...tdc, fontWeight: 600, color: BLUE }}>{s.market}</td>
                  <td style={tdc}>{s.category}</td>
                  <td style={{ ...tdc, fontWeight: 600 }}>{s.service}</td>
                  <td style={{ ...tdc, color: INK2, maxWidth: 260 }}>{s.definition}</td>
                  <td style={tdc}>{s.businessType}</td>
                  <td style={tdc}>{s.revenueModel}</td>
                  <td style={{ ...tdc, color: INK2 }}>{s.trade}</td>
                  <td style={{ ...tdc, color: INK2, fontSize: 11 }}>{s.codes}</td>
                  <td style={{ ...tdc, color: INK2 }}>{s.exampleCompany}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    );
  }

  // ── ADD / EDIT PROJECT MODAL ────────────────────────────────────────────────────
  function ProjectModal({ editing, onClose, onSaved }) {
    const regions = WS().data().regions || [];
    const regionStates = WS().data().regionStates || {};
    const cats = useMemo(() => WS().categories(), []);
    const subteams = useMemo(cleanSubteams, []);
    const blank = { projectId: null, project: "", client: "", sector: "", category: "", engagementType: "", subTeam: "", active: true, services: ["", "", "", "", ""], regions: [], states: [] };
    const [form, setForm] = useState(() => {
      if (editing) {
        const s = (editing.services || []).slice(); while (s.length < 5) s.push("");
        // reconstruct selected states from the saved regions (whole-region selection)
        const states = [];
        (editing.regions || []).forEach(rg => (regionStates[rg] || []).forEach(st => { if (!states.includes(st)) states.push(st); }));
        // curated rows can carry null category/sector/etc — selects need "" not null
        const e = { ...editing };
        for (const k of ["project", "client", "sector", "category", "engagementType", "subTeam"]) if (e[k] == null) e[k] = "";
        return { ...blank, ...e, services: s.slice(0, 5), states };
      }
      return blank;
    });
    const [saving, setSaving] = useState(false);
    const [msg, setMsg] = useState("");
    const svcOptions = useMemo(() => WS().allServices(form.category || null).map(s => s.service), [form.category]);

    function setSvc(i, v) { setForm(f => { const s = f.services.slice(); s[i] = v; return { ...f, services: s }; }); }
    function toggleState(code, region) {
      setForm(f => {
        const has = (f.states || []).includes(code);
        const states = has ? f.states.filter(x => x !== code) : [...(f.states || []), code];
        // region is "retained" if any of its states are selected
        const regSet = new Set();
        Object.keys(regionStates).forEach(rg => { if (regionStates[rg].some(s => states.includes(s))) regSet.add(rg); });
        return { ...f, states, regions: Array.from(regSet) };
      });
    }
    function toggleWholeRegion(region) {
      setForm(f => {
        const sts = regionStates[region] || [];
        const allOn = sts.every(s => (f.states || []).includes(s));
        let states = (f.states || []).slice();
        if (allOn) states = states.filter(s => !sts.includes(s));
        else sts.forEach(s => { if (!states.includes(s)) states.push(s); });
        const regSet = new Set();
        Object.keys(regionStates).forEach(rg => { if (regionStates[rg].some(s => states.includes(s))) regSet.add(rg); });
        return { ...f, states, regions: Array.from(regSet) };
      });
    }

    function toggleNational() {
      setForm(f => {
        const allStates = Object.values(regionStates).flat();
        const isAll = allStates.every(s => (f.states || []).includes(s));
        if (isAll) return { ...f, states: [], regions: [] };
        return { ...f, states: allStates.slice(), regions: Object.keys(regionStates) };
      });
    }

    async function save() {
      if (!form.project) { setMsg("Project name is required."); return; }
      setSaving(true); setMsg("");
      try {
        await window.VaultAPI.upsertWhiteSpaceProject({ ...form, services: form.services.filter(Boolean) });
        if (onSaved) onSaved();
        onClose();
      } catch (e) { setMsg("Save failed: " + (e.message || e)); setSaving(false); }
    }

    return (
      <div onClick={onClose} style={{ position: "fixed", inset: 0, background: "rgba(20,30,45,.45)", zIndex: 1000, display: "flex", alignItems: "flex-start", justifyContent: "center", overflowY: "auto", padding: "40px 16px" }}>
        <div onClick={e => e.stopPropagation()} style={{ background: "#fff", borderRadius: 14, width: "100%", maxWidth: 720, boxShadow: "0 20px 60px rgba(0,0,0,.25)" }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "18px 22px", borderBottom: `1px solid ${LINE}` }}>
            <h3 style={{ fontSize: 17, fontWeight: 600, color: INK, margin: 0 }}>{editing ? "Edit project" : "Add new project"}</h3>
            <button onClick={onClose} style={{ border: "none", background: "transparent", fontSize: 22, color: MUTE, cursor: "pointer", lineHeight: 1 }}>×</button>
          </div>
          <div style={{ padding: 22, maxHeight: "70vh", overflowY: "auto" }}>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
              <Field label="Project"><input style={inp} value={form.project} onChange={e => setForm(f => ({ ...f, project: e.target.value }))} placeholder="e.g. Best Trash" /></Field>
              <Field label="Client / Sponsor"><input style={inp} value={form.client} onChange={e => setForm(f => ({ ...f, client: e.target.value }))} placeholder="e.g. H.I.G. Capital" /></Field>
              <Field label="Market Sector"><input style={inp} value={form.sector} onChange={e => setForm(f => ({ ...f, sector: e.target.value }))} placeholder="e.g. Environmental" /></Field>
              <Field label="Category">
                <select style={inp} value={form.category} onChange={e => setForm(f => ({ ...f, category: e.target.value }))}>
                  <option value="">Select…</option>{cats.map(c => <option key={c} value={c}>{c}</option>)}
                </select>
              </Field>
              <Field label="Engagement Type">
                <select style={inp} value={form.engagementType} onChange={e => setForm(f => ({ ...f, engagementType: e.target.value }))}>
                  <option value="">Select…</option><option value="Platform">Platform</option><option value="Add-on">Add-On</option><option value="CEO">CEO</option>
                </select>
              </Field>
              <Field label="Sub-Team">
                <select style={inp} value={form.subTeam} onChange={e => setForm(f => ({ ...f, subTeam: e.target.value }))}>
                  <option value="">Select…</option>{subteams.map(st => <option key={st.id} value={st.id}>{st.label}</option>)}
                </select>
              </Field>
            </div>

            <div style={{ marginTop: 14 }}>
              <label style={{ fontSize: 13, color: INK2, display: "inline-flex", alignItems: "center", gap: 8, cursor: "pointer" }}>
                <input type="checkbox" checked={form.active !== false} onChange={e => setForm(f => ({ ...f, active: e.target.checked }))} /> Active engagement
              </label>
            </div>

            <div style={{ marginTop: 18 }}>
              <div style={fl}>Services (up to 5)</div>
              {form.services.map((s, i) => (
                <select key={i} style={{ ...inp, marginBottom: 8 }} value={s} onChange={e => setSvc(i, e.target.value)}>
                  <option value="">{i === 0 ? "Primary service…" : `Service ${i + 1}…`}</option>{svcOptions.map(o => <option key={o} value={o}>{o}</option>)}
                </select>
              ))}
            </div>

            <div style={{ marginTop: 18 }}>
              <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>
                <div style={{ ...fl, margin: 0 }}>Regions & states retained</div>
                <label style={{ display: "flex", alignItems: "center", gap: 7, fontSize: 12.5, fontWeight: 600, color: BLUE, cursor: "pointer" }}>
                  <input type="checkbox" checked={Object.values(regionStates).flat().every(s => (form.states || []).includes(s))} onChange={toggleNational} /> National (all regions)
                </label>
              </div>
              <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
                {regions.map(region => {
                  const sts = regionStates[region] || [];
                  const allOn = sts.every(s => (form.states || []).includes(s));
                  const TZ = window.US_STATE_TZ || {};
                  return (
                    <div key={region} style={{ border: `1px solid ${LINE}`, borderRadius: 9, padding: "10px 12px" }}>
                      <label style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 12.5, fontWeight: 600, color: INK, cursor: "pointer", marginBottom: 8 }}>
                        <input type="checkbox" checked={allOn} onChange={() => toggleWholeRegion(region)} /> {region}
                      </label>
                      <div style={{ display: "flex", flexWrap: "wrap", gap: 5 }}>
                        {sts.map(code => {
                          const on = (form.states || []).includes(code);
                          return <button key={code} onClick={() => toggleState(code, region)} title={TZ[code] ? `${code} · ${TZ[code]}` : code} style={{ fontSize: 11, padding: "3px 8px", borderRadius: 6, cursor: "pointer", border: `1px solid ${on ? BLUE : LINE}`, background: on ? BLUE : "#fff", color: on ? "#fff" : INK2, minWidth: 32 }}>{code}</button>;
                        })}
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "16px 22px", borderTop: `1px solid ${LINE}` }}>
            <button onClick={save} disabled={saving} style={{ padding: "10px 22px", borderRadius: 9, border: "none", background: BLUE, color: "#fff", fontSize: 14, fontWeight: 600, cursor: saving ? "default" : "pointer", opacity: saving ? 0.6 : 1 }}>
              {saving ? "Saving…" : (editing ? "Update project" : "Add project")}
            </button>
            <button onClick={onClose} style={{ padding: "10px 18px", borderRadius: 9, border: `1px solid ${LINE}`, background: "#fff", color: INK2, fontSize: 14, cursor: "pointer" }}>Cancel</button>
            {msg && <span style={{ fontSize: 13, color: "#B23A2E" }}>{msg}</span>}
          </div>
        </div>
      </div>
    );
  }

  function Field({ label, children }) { return (<div><div style={fl}>{label}</div>{children}</div>); }

  // sortable table hook: click a header to sort asc/desc
  function useSort(rows, initialKey) {
    const [sort, setSort] = useState({ key: initialKey || null, dir: "asc" });
    const sorted = useMemo(() => {
      if (!sort.key) return rows;
      const r = rows.slice().sort((a, b) => {
        const av = (a[sort.key] == null ? "" : a[sort.key]); const bv = (b[sort.key] == null ? "" : b[sort.key]);
        const as = Array.isArray(av) ? av.length : av; const bs = Array.isArray(bv) ? bv.length : bv;
        if (typeof as === "number" && typeof bs === "number") return as - bs;
        return String(as).localeCompare(String(bs));
      });
      return sort.dir === "desc" ? r.reverse() : r;
    }, [rows, sort]);
    function header(key, label, align) {
      const on = sort.key === key;
      return (
        <th onClick={() => setSort(s => ({ key, dir: s.key === key && s.dir === "asc" ? "desc" : "asc" }))}
          style={{ ...th, textAlign: align || "left", cursor: "pointer", userSelect: "none" }}>
          {label}{on ? (sort.dir === "asc" ? " ▲" : " ▼") : ""}
        </th>
      );
    }
    return { sorted, header };
  }

  const fSel = { padding: "8px 11px", borderRadius: 8, border: `1px solid ${LINE}`, fontSize: 12.5, color: INK, background: "#fff", cursor: "pointer", maxWidth: 220 };
  const inp = { width: "100%", padding: "8px 11px", borderRadius: 8, border: `1px solid ${LINE}`, fontSize: 13, color: INK, background: "#fff", boxSizing: "border-box" };
  const fl = { fontSize: 11, fontWeight: 600, color: MUTE, textTransform: "uppercase", letterSpacing: ".04em", marginBottom: 6 };
  const th = { fontSize: 11, fontWeight: 600, color: MUTE, textAlign: "left", padding: "9px 12px", borderBottom: `1px solid ${LINE}`, whiteSpace: "nowrap" };
  const td = { fontSize: 12.5, color: INK, padding: "8px 12px" };
  const tdc = { fontSize: 12, color: INK, padding: "8px 12px" };
  const editBtn = { fontSize: 12, padding: "4px 12px", borderRadius: 7, border: `1px solid ${LINE}`, background: "#fff", color: BLUE, cursor: "pointer" };
  const addBtn = { fontSize: 13, fontWeight: 600, padding: "8px 16px", borderRadius: 9, border: "none", background: BLUE, color: "#fff", cursor: "pointer" };
  const inactiveTag = { fontSize: 9.5, fontWeight: 600, textTransform: "uppercase", color: "#8A6D3B", background: "#F6ECD6", borderRadius: 4, padding: "1px 5px", marginLeft: 7 };

  function WhiteSpaceScreen() {
    const [tab, setTab] = useState("map");
    const [projects, setProjects] = useState(null);
    const [modal, setModal] = useState(null); // null | {editing}

    async function load() {
      try { const rows = await window.VaultAPI.getWhiteSpaceProjects(); if (rows && rows.length) { setProjects(rows); return; } } catch (e) {}
      setProjects((WS().data().projects || []).map(p => ({ ...p, active: true })));
    }
    useEffect(() => { load(); }, []);

    const tabs = [["map", "Map"], ["list", "List"], ["meta", "Meta-Data"]];
    return (
      <div style={{ maxWidth: 1180, margin: "0 auto", padding: "24px 30px 64px" }}>
        <window.PageToolbar title="White Space" subtitle="Where the firm can and can't take on work, by service and region." style={{ marginBottom: 0 }}/>
        <div style={{ display: "inline-flex", gap: 2, background: "#EEF2F6", borderRadius: 10, padding: 3, margin: "18px 0 22px" }}>
          {tabs.map(([k, lbl]) => (
            <button key={k} onClick={() => setTab(k)} style={{ padding: "7px 16px", borderRadius: 8, border: "none", cursor: "pointer", fontSize: 13, fontWeight: 500, background: tab === k ? "#fff" : "transparent", color: tab === k ? BLUE : INK2, boxShadow: tab === k ? "0 1px 2px rgba(0,0,0,.06)" : "none" }}>{lbl}</button>
          ))}
        </div>
        {projects == null ? <window.Skeleton rows={7} height={15} gap={13} style={{ margin: "24px 0" }}/> : (
          <div>
            {tab === "map" && <MapTab projects={projects} />}
            {tab === "list" && <ListTab projects={projects} onEdit={(p) => setModal({ editing: p })} onAdd={() => setModal({ editing: null })} />}
            {tab === "meta" && <MetaTab onAdd={() => setModal({ editing: null })} />}
          </div>
        )}
        {modal && <ProjectModal editing={modal.editing} onClose={() => setModal(null)} onSaved={load} />}
      </div>
    );
  }

  window.WhiteSpaceScreen = WhiteSpaceScreen;
})();