/* Target Accounts list — Attio-style data grid with ABM scores. */

const { useState: useStateAcc } = React;

/* ─── Advanced filter: field catalog + evaluator ───────────────────────
   FIELD_CATALOG describes every queryable field on a Company. Each
   entry declares:
     - id:    stable key used in clause storage
     - label: shown in the field picker
     - group: section header in the picker (Identity / Geo / ABM / …)
     - type:  controls which operators + value editor are offered
              ("text" | "number" | "enum" | "boolean" | "array" | "date")
     - get:   extracts the value from a Company record (returns
              undefined when missing so operators can treat the field
              as "empty"). The "contactStats" arg is the per-company
              {total, requested, accepted} block computed live from
              loaded CONTACTS.
     - options: for type=enum, ordered list of allowed values
                (and optional renderOption to format chips).
*/
const AF_OPS = {
  text:    [
    { id: "contains",    label: "contains" },
    { id: "not_contains",label: "does not contain" },
    { id: "eq",          label: "is" },
    { id: "neq",         label: "is not" },
    { id: "starts_with", label: "starts with" },
    { id: "ends_with",   label: "ends with" },
    { id: "is_empty",    label: "is empty",     unary: true },
    { id: "is_not_empty",label: "is not empty", unary: true },
  ],
  number:  [
    { id: "eq",  label: "=" },
    { id: "neq", label: "≠" },
    { id: "gt",  label: ">" },
    { id: "gte", label: "≥" },
    { id: "lt",  label: "<" },
    { id: "lte", label: "≤" },
    { id: "between", label: "between", pairValue: true },
    { id: "is_empty",     label: "is empty",     unary: true },
    { id: "is_not_empty", label: "is not empty", unary: true },
  ],
  enum:    [
    { id: "in",     label: "is any of",  multiValue: true },
    { id: "not_in", label: "is none of", multiValue: true },
    { id: "is_empty",     label: "is empty",     unary: true },
    { id: "is_not_empty", label: "is not empty", unary: true },
  ],
  boolean: [
    { id: "eq_true",  label: "is true",  unary: true },
    { id: "eq_false", label: "is false", unary: true },
  ],
  array:   [
    { id: "any_in",  label: "has any of", multiValue: true },
    { id: "all_in",  label: "has all of", multiValue: true },
    { id: "contains_text", label: "any contains text" },
    { id: "is_empty",     label: "is empty",     unary: true },
    { id: "is_not_empty", label: "is not empty", unary: true },
  ],
  date:    [
    { id: "in_last_days", label: "in the last N days" },
    { id: "before",       label: "before" },
    { id: "after",        label: "after" },
    { id: "is_empty",     label: "is empty",     unary: true },
    { id: "is_not_empty", label: "is not empty", unary: true },
  ],
};

function buildFieldCatalog(filterOptions) {
  return [
    /* Identity */
    { id: "name",         label: "Account name",  group: "Identity", type: "text",   get: (c) => c.name },
    { id: "domain",       label: "Domain",        group: "Identity", type: "text",   get: (c) => c.domain || (c.api?.domain) },
    { id: "tagline",      label: "Tagline",       group: "Identity", type: "text",   get: (c) => c.api?.tagline },
    { id: "description",  label: "Description",   group: "Identity", type: "text",   get: (c) => c.description },
    /* Firmographics */
    { id: "industry",      label: "Industry",      group: "Firmographics", type: "enum",  get: (c) => c.industry,
      options: filterOptions?.industry || [] },
    { id: "employeeRange", label: "Size",          group: "Firmographics", type: "enum",  get: (c) => c.employeeRange,
      options: ["<50", "51-200", "201-500", "501–1k", "1k-5k", "5k-10k", "10k+"] },
    { id: "employeeCount", label: "Headcount",     group: "Firmographics", type: "number", get: (c) => c.employeeCount },
    { id: "yearFounded",   label: "Year founded",  group: "Firmographics", type: "number", get: (c) => c.yearFounded },
    { id: "followerCount", label: "Follower count",group: "Firmographics", type: "number", get: (c) => c.api?.follower_count },
    /* Geography */
    { id: "hqCity",        label: "HQ city",       group: "Geography", type: "enum",  get: (c) => c.hqCity,
      options: filterOptions?.hqCity || [] },
    { id: "hqCountry",     label: "HQ country",    group: "Geography", type: "text",  get: (c) => c.api?.hq_country || c.hqCountry },
    { id: "operatingCountryCount", label: "Operating countries", group: "Geography", type: "number", get: (c) => c.api?.operatingCountryCount },
    { id: "totalLocationsCount",   label: "Total locations",     group: "Geography", type: "number", get: (c) => c.api?.totalLocationsCount },
    { id: "spainLocationCount",    label: "Spain locations",     group: "Geography", type: "number", get: (c) => c.api?.spainLocationCount },
    { id: "outsideSpainLocationCount", label: "Other locations", group: "Geography", type: "number", get: (c) => c.api?.outsideSpainLocationCount },
    /* Languages */
    { id: "englishCountryRatio", label: "English-strong country ratio", group: "Languages", type: "number", get: (c) => c.api?.highestLanguageEnglishCountryRatio },
    { id: "englishCountryCount", label: "English-strong country count", group: "Languages", type: "number", get: (c) => c.api?.highestLanguageEnglishCountryCount },
    { id: "officialLanguages",   label: "Official languages",            group: "Languages", type: "array",
      get: (c) => c.api?.officialLanguages || [] },
    /* Scoring */
    { id: "executiveTier",   label: "Target tier",      group: "Scoring", type: "enum",
      get: (c) => c.api?.executive_english_target_tier,
      options: ["strong_target", "medium_target", "potential_target", "low_priority", "very_low_priority"],
      renderOption: (v) => window.TIER_LABEL?.[v] || v },
    { id: "executiveScore",  label: "Target score (0–10)", group: "Scoring", type: "number",
      get: (c) => c.api?.executive_english_target_score },
    { id: "gtmPriority",     label: "GTM priority",     group: "Scoring", type: "enum",
      get: (c) => c.gtmPriority,
      options: ["high", "medium", "low"],
      renderOption: (v) => v[0].toUpperCase() + v.slice(1) },
    /* ABM (mock-derived; surfaced so the UI is exhaustive) */
    { id: "lifecycleStage",  label: "Lifecycle",      group: "ABM", type: "enum",
      get: (c) => c.abm?.lifecycleStage,
      options: filterOptions?.lifecycle || [],
      renderOption: (v) => window.LIFECYCLE_LABEL?.[v] || v },
    { id: "fitScore",        label: "Fit score",      group: "ABM", type: "number", get: (c) => c.abm?.fitScore },
    { id: "intentScore",     label: "Intent score",   group: "ABM", type: "number", get: (c) => c.abm?.intentScore },
    { id: "relationshipScore", label: "Relationship score", group: "ABM", type: "number", get: (c) => c.abm?.relationshipScore },
    { id: "priorityScore",   label: "Priority score", group: "ABM", type: "number", get: (c) => c.abm?.priorityScore },
    { id: "openOpps",        label: "Open opportunities", group: "ABM", type: "number", get: (c) => c.abm?.openOpportunitiesCount },
    { id: "stakeholders",    label: "Stakeholders (mock)", group: "ABM", type: "number", get: (c) => c.abm?.stakeholdersCount },
    { id: "lastEngagement",  label: "Last engagement", group: "ABM", type: "date", get: (c) => c.abm?.lastEngagementAt },
    /* Live contact counts */
    { id: "associatedContacts", label: "Associated contacts", group: "Contacts", type: "number",
      get: (c, ctx) => ctx?.contactStats?.get(c.id)?.total || 0 },
    { id: "connectionRequests", label: "Connection requests", group: "Contacts", type: "number",
      get: (c, ctx) => ctx?.contactStats?.get(c.id)?.requested || 0 },
    { id: "connectedContacts",  label: "Connected contacts",  group: "Contacts", type: "number",
      get: (c, ctx) => ctx?.contactStats?.get(c.id)?.accepted || 0 },
    /* Misc */
    { id: "specialties",  label: "Specialties", group: "Misc", type: "array", get: (c) => c.specialties || [] },
    { id: "tags",         label: "Tags",        group: "Misc", type: "array", get: (c) => c.tags || [] },
    { id: "updatedAt",    label: "Last updated", group: "Misc", type: "date", get: (c) => c.api?.updatedAt },
  ];
}

/* Evaluate a single clause against a Company. Missing values return
   false for affirmative ops (contains/eq) and true for negations
   (not_contains/neq/is_empty), so accounts with sparse data don't all
   get accidentally caught by "is not X" filters. */
function evalClause(clause, company, fieldCatalog, ctx) {
  const def = fieldCatalog.find(f => f.id === clause.field);
  if (!def) return true;
  const raw = def.get(company, ctx);
  const op = clause.op;
  const val = clause.value;
  const present = raw !== undefined && raw !== null && raw !== "" && !(Array.isArray(raw) && raw.length === 0);

  if (op === "is_empty")     return !present;
  if (op === "is_not_empty") return present;
  if (op === "eq_true")      return raw === true;
  if (op === "eq_false")     return raw === false;
  if (!present) return false; /* affirmative ops on missing data: exclude */

  switch (def.type) {
    case "text": {
      const s = String(raw).toLowerCase();
      const v = String(val || "").toLowerCase();
      if (op === "contains")     return s.includes(v);
      if (op === "not_contains") return !s.includes(v);
      if (op === "eq")           return s === v;
      if (op === "neq")          return s !== v;
      if (op === "starts_with")  return s.startsWith(v);
      if (op === "ends_with")    return s.endsWith(v);
      return true;
    }
    case "number": {
      const n = Number(raw);
      if (Number.isNaN(n)) return false;
      if (op === "between") {
        const [a, b] = Array.isArray(val) ? val : [val?.from, val?.to];
        const lo = Number(a), hi = Number(b);
        if (Number.isNaN(lo) || Number.isNaN(hi)) return true;
        return n >= Math.min(lo, hi) && n <= Math.max(lo, hi);
      }
      const v = Number(val);
      if (Number.isNaN(v)) return true;
      if (op === "eq")  return n === v;
      if (op === "neq") return n !== v;
      if (op === "gt")  return n >  v;
      if (op === "gte") return n >= v;
      if (op === "lt")  return n <  v;
      if (op === "lte") return n <= v;
      return true;
    }
    case "enum": {
      const arr = Array.isArray(val) ? val : (val == null ? [] : [val]);
      if (op === "in")     return arr.includes(raw);
      if (op === "not_in") return !arr.includes(raw);
      return true;
    }
    case "array": {
      const list = Array.isArray(raw) ? raw : [raw];
      if (op === "any_in") {
        const arr = Array.isArray(val) ? val : [val];
        return arr.some(v => list.includes(v));
      }
      if (op === "all_in") {
        const arr = Array.isArray(val) ? val : [val];
        return arr.every(v => list.includes(v));
      }
      if (op === "contains_text") {
        const v = String(val || "").toLowerCase();
        return list.some(x => String(x).toLowerCase().includes(v));
      }
      return true;
    }
    case "date": {
      const t = new Date(raw).getTime();
      if (Number.isNaN(t)) return false;
      if (op === "in_last_days") {
        const days = Number(val); if (Number.isNaN(days)) return true;
        return (Date.now() - t) <= days * 86400000;
      }
      if (op === "before") return t <  new Date(val).getTime();
      if (op === "after")  return t >  new Date(val).getTime();
      return true;
    }
    default: return true;
  }
}

function PageAccounts({ onNavigate }) {
  const apiTotals = window.useApiTotals ? window.useApiTotals() : null;
  const [filter, setFilter] = useStateAcc("all"); /* all | priority | engaged */
  const [sortBy, setSortBy] = useStateAcc("priority"); /* priority | recent | name */
  const [groupBy, setGroupBy] = useStateAcc("none"); /* none | lifecycle | industry | priority | owner */
  const [search, setSearch] = useStateAcc("");
  const [extra, setExtra] = useStateAcc([]); /* locally-added accounts */
  const [syncing, setSyncing] = useStateAcc(false);
  const [addOpen, setAddOpen] = useStateAcc(false);
  /* Per-column filters. Each value is an array of allowed values (empty = no filter). */
  const [columnFilters, setColumnFilters] = useStateAcc({
    industry: [], hqCity: [], employeeRange: [], lifecycle: [], priority: [],
  });
  const setColumnFilter = (key, values) => setColumnFilters(prev => ({ ...prev, [key]: values }));
  const clearAllColumnFilters = () => setColumnFilters({
    industry: [], hqCity: [], employeeRange: [], lifecycle: [], priority: [],
  });

  /* Infinite-scroll pagination — show `visibleCount` rows; grow as the
     user scrolls a sentinel into view. Reset whenever the filtered set changes. */
  const PAGE_SIZE = 30;
  const [visibleCount, setVisibleCount] = useStateAcc(PAGE_SIZE);

  /* Saved views — capture the current filter/sort/group/column setup so users
     can flip between recurring lenses without redoing everything by hand. */
  const [savedViews, setSavedViews] = useStateAcc([]);
  const [activeViewId, setActiveViewId] = useStateAcc(null);
  const captureViewState = () => ({
    filter, search, sortBy, groupBy,
    columnFilters: { ...columnFilters },
    visibleCols:   { ...visibleCols },
    columnOrder:   columnOrder.slice(),
  });
  const saveCurrentView = (name) => {
    const id = "v-" + Date.now().toString(36);
    const view = { id, name: name.trim(), state: captureViewState() };
    setSavedViews(prev => [...prev, view]);
    setActiveViewId(id);
  };
  const applyView = (view) => {
    const s = view.state;
    setFilter(s.filter);
    setSearch(s.search);
    setSortBy(s.sortBy);
    setGroupBy(s.groupBy);
    setColumnFilters(s.columnFilters || {});
    setVisibleCols(s.visibleCols || {});
    setColumnOrder(s.columnOrder || DEFAULT_ORDER);
    setActiveViewId(view.id);
  };
  const deleteView = (id) => {
    setSavedViews(prev => prev.filter(v => v.id !== id));
    if (activeViewId === id) setActiveViewId(null);
  };

  /* Column visibility — Account/Avatar are required; everything else can
     be hidden. Most columns are off by default; toggle them in the
     "Edit columns" popover. New columns surface the rich fields shipped
     by the live /companies endpoint. */
  const ALL_COLUMNS = [
    /* Defaults visible */
    { key: "industry",       label: "Industry",            width: 130 },
    { key: "hq",             label: "HQ",                  width: 110 },
    { key: "size",           label: "Size",                width: 90  },
    { key: "lifecycle",      label: "Lifecycle",           width: 110 },
    { key: "priority",       label: "Target tier",         width: 140 },
    { key: "action",         label: "Next best action",    width: 240 },
    /* Identity / contact */
    { key: "domain",         label: "Domain",              width: 160 },
    { key: "website",        label: "Website",             width: 200 },
    { key: "linkedin",       label: "LinkedIn",            width: 120 },
    { key: "tagline",        label: "Tagline",             width: 240 },
    /* Headcount & growth */
    { key: "employeeCount",  label: "Headcount",           width: 110 },
    { key: "yearFounded",    label: "Founded",             width: 90  },
    { key: "followerCount",  label: "Followers",           width: 110 },
    /* Geographic footprint */
    { key: "hqAddress",      label: "HQ address",          width: 220 },
    { key: "countryCount",   label: "Countries",           width: 100 },
    { key: "locationCount",  label: "Locations",           width: 100 },
    { key: "spainLocations", label: "Spain locations",     width: 110 },
    { key: "otherLocations", label: "Other locations",     width: 110 },
    /* Languages */
    { key: "englishRatio",   label: "English-strong",      width: 130 },
    { key: "officialLangs",  label: "Official languages",  width: 180 },
    /* Sales reasoning */
    { key: "targetScore",    label: "Target score",        width: 110 },
    { key: "salesReason",    label: "Why this account",    width: 280 },
    /* ABM (mock-derived; surfaced for completeness) */
    { key: "fitScore",          label: "Fit score",        width: 90 },
    { key: "intentScore",       label: "Intent score",     width: 90 },
    { key: "relationshipScore", label: "Relationship",     width: 110 },
    { key: "priorityScore",     label: "Priority score",   width: 110 },
    { key: "stakeholders",      label: "Stakeholders",     width: 110 },
    { key: "openOpps",          label: "Open opps",        width: 100 },
    { key: "lastEngagement",    label: "Last engagement",  width: 140 },
    /* Live contact counts (derived from loaded CONTACTS) */
    { key: "assocContacts",     label: "Associated contacts",   width: 130 },
    { key: "connectionRequests",label: "Connection requests",   width: 140 },
    { key: "connectedContacts", label: "Connected contacts",    width: 130 },
    /* Misc */
    { key: "specialties",    label: "Specialties",         width: 220 },
    { key: "tags",           label: "Tags",                width: 200 },
    { key: "updated",        label: "Updated",             width: 110 },
  ];
  const DEFAULT_VISIBLE = { industry: true, hq: true, size: true, lifecycle: true, priority: true, action: true };
  const DEFAULT_ORDER = ALL_COLUMNS.map(c => c.key);
  const [visibleCols, setVisibleCols] = useStateAcc(DEFAULT_VISIBLE);
  const [columnOrder, setColumnOrder] = useStateAcc(DEFAULT_ORDER);
  const toggleCol = (key) => setVisibleCols(prev => ({ ...prev, [key]: !prev[key] }));
  const resetCols = () => {
    setVisibleCols(DEFAULT_VISIBLE);
    setColumnOrder(DEFAULT_ORDER);
  };
  const reorderCols = (fromKey, toKey) => {
    if (fromKey === toKey) return;
    setColumnOrder(prev => {
      const arr = prev.slice();
      const from = arr.indexOf(fromKey);
      const to   = arr.indexOf(toKey);
      if (from < 0 || to < 0) return prev;
      arr.splice(to, 0, arr.splice(from, 1)[0]);
      return arr;
    });
  };
  const hiddenColCount = ALL_COLUMNS.filter(c => !visibleCols[c.key]).length;

  /* Advanced filter — array of clauses; each is { id, field, op, value }.
     The clauses AND together. Filtering happens client-side against the
     records currently in memory; once backend ships richer query
     operators the same clauses can be translated into URL params. */
  const [advancedFilters, setAdvancedFilters] = useStateAcc([]);
  const [advancedFilterOpen, setAdvancedFilterOpen] = useStateAcc(false);

  /* Pre-compute live contact stats per company so each AccountRow can
     show real "associated / requested / connected" counts without doing
     a 3-pass .filter() per row. Re-derived whenever the data version
     bumps (i.e. live data lands). The lookup is a Map keyed by company
     id; misses default to zeros. */
  const apiDataVersion = window.useApiDataVersion ? window.useApiDataVersion() : 0;
  const contactStatsByCompany = React.useMemo(() => {
    const m = new Map();
    const all = window.CONTACTS || [];
    for (const c of all) {
      const cid = c.companyId;
      if (!cid) continue;
      let s = m.get(cid);
      if (!s) { s = { total: 0, requested: 0, accepted: 0 }; m.set(cid, s); }
      s.total += 1;
      if (c.linkedinStatus === "pending_sent") s.requested += 1;
      else if (c.linkedinStatus === "accepted") s.accepted += 1;
    }
    return m;
  }, [apiDataVersion]);

  /* Multi-selection across filtered results. Stored as a Set of company ids. */
  const [selectedIds, setSelectedIds] = useStateAcc(() => new Set());
  const [bulkToast, setBulkToast] = useStateAcc(null); /* string shown briefly after a bulk action */
  const [peekId, setPeekId] = useStateAcc(null); /* company id shown in the quick-look drawer */
  const toggleSelected = (id) => setSelectedIds(prev => {
    const next = new Set(prev);
    if (next.has(id)) next.delete(id); else next.add(id);
    return next;
  });
  const selectMany = (ids) => setSelectedIds(prev => {
    const next = new Set(prev); ids.forEach(id => next.add(id)); return next;
  });
  const deselectMany = (ids) => setSelectedIds(prev => {
    const next = new Set(prev); ids.forEach(id => next.delete(id)); return next;
  });
  const clearSelection = () => setSelectedIds(new Set());
  const flashToast = (msg) => {
    setBulkToast(msg);
    setTimeout(() => setBulkToast(null), 2400);
  };

  let list = window.COMPANIES.concat(extra);
  if (search) {
    const s = search.toLowerCase();
    list = list.filter(c => c.name.toLowerCase().includes(s) || c.industry.toLowerCase().includes(s));
  }
  if (filter === "priority") list = list.filter(c => c.gtmPriority === "high");
  if (filter === "engaged")  list = list.filter(c => ["engaged", "opportunity"].includes(c.abm.lifecycleStage));
  /* Apply column filters. */
  if (columnFilters.industry.length)     list = list.filter(c => columnFilters.industry.includes(c.industry));
  if (columnFilters.hqCity.length)       list = list.filter(c => columnFilters.hqCity.includes(c.hqCity));
  if (columnFilters.employeeRange.length) list = list.filter(c => columnFilters.employeeRange.includes(c.employeeRange));
  if (columnFilters.lifecycle.length)    list = list.filter(c => columnFilters.lifecycle.includes(c.abm.lifecycleStage));
  if (columnFilters.priority.length)     list = list.filter(c => columnFilters.priority.includes(c.gtmPriority));
  /* Apply advanced-filter clauses — they AND together. Missing clauses
     (no field selected yet, no value yet) are ignored. Uses an inline
     facet snapshot for enum options because the real `filterOptions`
     object isn't built until later in the function body; this snapshot
     is good enough to feed back into enum-typed clauses. */
  if (advancedFilters.length) {
    const facetUniverse = window.COMPANIES.concat(extra);
    const facetUniq = (arr) => Array.from(new Set(arr)).filter(Boolean);
    const facetSnapshot = {
      industry: facetUniq(facetUniverse.map(c => c.industry)).sort(),
      hqCity:   facetUniq(facetUniverse.map(c => c.hqCity)).sort(),
      lifecycle: facetUniq(facetUniverse.map(c => c.abm.lifecycleStage)),
    };
    const catalog = buildFieldCatalog(facetSnapshot);
    const ctx = { contactStats: contactStatsByCompany };
    list = list.filter(c => advancedFilters.every(cl => {
      if (!cl.field || !cl.op) return true;
      return evalClause(cl, c, catalog, ctx);
    }));
  }
  if (sortBy === "priority") list.sort((a, b) => b.abm.priorityScore - a.abm.priorityScore);
  if (sortBy === "recent")   list.sort((a, b) => (new Date(b.abm.lastEngagementAt || 0)) - (new Date(a.abm.lastEngagementAt || 0)));
  if (sortBy === "contacts") list.sort((a, b) => (b.abm.stakeholdersCount || 0) - (a.abm.stakeholdersCount || 0));
  if (sortBy === "stale")    list.sort((a, b) => {
    /* "Lack of activity": never-engaged accounts first, then oldest-engaged-first. */
    const ta = a.abm.lastEngagementAt ? new Date(a.abm.lastEngagementAt).getTime() : 0;
    const tb = b.abm.lastEngagementAt ? new Date(b.abm.lastEngagementAt).getTime() : 0;
    return ta - tb;
  });
  if (sortBy === "name")     list.sort((a, b) => a.name.localeCompare(b.name));

  /* Reset infinite-scroll window when filters / search / sort change. */
  const filterSignature = JSON.stringify([
    filter, search, sortBy, groupBy, columnFilters, advancedFilters,
  ]);
  React.useEffect(() => { setVisibleCount(PAGE_SIZE); }, [filterSignature]);

  /* True corpus size lives on the server (`meta.total`). The in-memory
     `list` is only the records loaded so far; we mix the server total
     into `totalCount` so the floating pill and infinite-scroll target
     reflect the real ceiling. When filters or search are active the
     server total no longer matches what we'd render, so we fall back to
     the in-memory length. */
  const filtersActive = filter !== "all" || search.trim() !== "" ||
    Object.values(columnFilters || {}).some(v => Array.isArray(v) ? v.length : v) ||
    advancedFilters.length > 0;
  const serverTotal = filtersActive ? null : (apiTotals?.companies ?? null);
  const totalCount  = serverTotal != null ? Math.max(list.length, serverTotal) : list.length;
  const shownCount  = Math.min(visibleCount, totalCount);
  const visibleList = list.slice(0, Math.min(visibleCount, list.length));
  const hasMore     = shownCount < totalCount;

  /* Sentinel ref + IntersectionObserver to grow the window on scroll.
     If the user is approaching the end of the in-memory list and the
     server still has more records, fetch the next page. */
  /* Continuous prefetch: independent of the sentinel, keep ~3 pages of
     headroom in memory ahead of the visible window. Fires whenever
     visibleCount, list.length, or the server total changes — load-more
     is deduped + idempotent so re-firing is safe. */
  React.useEffect(() => {
    if (filtersActive) return;
    const target = (apiTotals?.companies ?? Infinity);
    const HEADROOM_PAGES = 3;
    const ahead = list.length - visibleCount;
    if (ahead < HEADROOM_PAGES * PAGE_SIZE && list.length < target) {
      window.BusinessTeamBoot?.loadMoreCompaniesFromLive();
    }
  }, [visibleCount, list.length, filtersActive, apiTotals?.companies]);

  /* Sentinel ref + IntersectionObserver — fires as backup near the end
     of the visible list. */
  const sentinelRef = React.useRef(null);
  React.useEffect(() => {
    if (!hasMore) return;
    const el = sentinelRef.current;
    if (!el) return;
    const io = new IntersectionObserver(entries => {
      entries.forEach(e => {
        if (!e.isIntersecting) return;
        /* Advance the visible window — but never past records we've
           actually loaded into memory. Otherwise the sentinel stays in
           view (since list.slice caps at list.length) and the observer
           re-fires forever, jumping the count straight to totalCount. */
        setVisibleCount(v => Math.min(v + PAGE_SIZE, list.length));
        /* Lookahead pre-fetch: keep 2 pages of headroom in memory ahead
           of where the user is looking. If we're inside the headroom,
           fire load-more — the boot helper dedupes concurrent calls so
           firing it on every sentinel hit is safe and keeps scrolling
           smooth (no visible pause when one page runs out). */
        const headroom = 2 * PAGE_SIZE;
        if (!filtersActive
            && list.length < (apiTotals?.companies ?? Infinity)
            && (visibleCount + headroom) >= list.length) {
          window.BusinessTeamBoot?.loadMoreCompaniesFromLive();
        }
      });
    }, { rootMargin: "600px 0px" /* fire earlier so prefetch starts before the user actually reaches the end */ });
    io.observe(el);
    return () => io.disconnect();
  }, [hasMore, totalCount, list.length, visibleCount, filtersActive, apiTotals?.companies, filterSignature]);

  const scrollToTop = () => {
    const scroller = document.querySelector(".page-scroll");
    if (scroller) scroller.scrollTo({ top: 0, behavior: "smooth" });
    else window.scrollTo({ top: 0, behavior: "smooth" });
  };

  /* Jump to a specific page in the (paginated) list. Loads enough rows to
     cover the page, then scrolls the table viewport so the page's first
     row sits near the top. Uses a tiny rAF chain so the freshly-rendered
     rows are in the DOM before we measure them. */
  /* Inline loading flag for jump-to-far-page. The fetch can take several
     seconds for deep jumps (each page = 1 round-trip), so we surface a
     small banner while in-flight. */
  const [paging, setPaging] = useStateAcc(false);
  /* Jump-to-page in the floating pill. Bumps the visible window up to
     the last row of the requested page and scrolls so that page's first
     row sits near the top. If the requested page isn't loaded yet from
     the API, we fetch enough records first. Uses a tiny rAF chain so
     the freshly-rendered rows are in the DOM before we measure them. */
  const jumpToPage = async (pageIndex) => {
    const lastRow = pageIndex * PAGE_SIZE;
    const firstRowIdx = (pageIndex - 1) * PAGE_SIZE; /* 0-indexed within filtered list */
    /* If the jump exceeds what's loaded and we're not filtering, pull
       enough pages from the API to honor it. */
    if (!filtersActive && lastRow > window.COMPANIES.length) {
      setPaging(true);
      try { await window.BusinessTeamBoot?.ensureCompaniesLoaded(lastRow); }
      finally { setPaging(false); }
    }
    setVisibleCount(v => Math.max(v, Math.min(lastRow, totalCount)));
    /* Wait two frames so the new rows are committed, then scroll. */
    requestAnimationFrame(() => requestAnimationFrame(() => {
      const scroller = document.querySelector(".page-scroll");
      const rows = document.querySelectorAll("[data-account-row]");
      const target = rows[firstRowIdx];
      if (!scroller || !target) return;
      const scrollerRect = scroller.getBoundingClientRect();
      const targetRect   = target.getBoundingClientRect();
      const delta = targetRect.top - scrollerRect.top - 80; /* leave headroom for sticky header */
      scroller.scrollTo({ top: scroller.scrollTop + delta, behavior: "smooth" });
    }));
  };

  /* Unique values for each filterable column — derived from the unfiltered universe
     so options don't vanish once you pick one. Sorted, lifecycle ordered by stage. */
  const universe = window.COMPANIES.concat(extra);
  const uniq = (arr) => Array.from(new Set(arr)).filter(Boolean);
  const filterOptions = {
    industry:      uniq(universe.map(c => c.industry)).sort(),
    hqCity:        uniq(universe.map(c => c.hqCity)).sort(),
    employeeRange: ["<50", "51-200", "201-500", "501–1k", "1k-5k", "5k-10k", "10k+"]
                     .filter(v => universe.some(c => c.employeeRange === v)),
    lifecycle:     uniq(universe.map(c => c.abm.lifecycleStage)),
    priority:      ["high", "medium", "low"].filter(v => universe.some(c => c.gtmPriority === v)),
  };
  const activeColumnFilterCount = Object.values(columnFilters).reduce((n, arr) => n + (arr.length ? 1 : 0), 0);

  const handleSync = () => {
    if (syncing) return;
    setSyncing(true);
    setTimeout(() => {
      setSyncing(false);
    }, 1600);
  };

  return (
    <>
      <PageHeader
        title="Target Accounts"
        actions={<>
          <Button
            kind="secondary" size="md" icon="filter"
            onClick={() => setAdvancedFilterOpen(true)}
            title="Advanced filter — build compound rules across any field"
          >
            Filter
            {advancedFilters.length > 0 && (
              <span style={{
                marginLeft: 6, padding: "0 5px", height: 16, minWidth: 16,
                borderRadius: 999, background: "var(--accent)", color: "#fff",
                fontSize: 10.5, fontWeight: 700,
                display: "inline-flex", alignItems: "center", justifyContent: "center",
              }}>{advancedFilters.length}</span>
            )}
          </Button>
          <Button kind="secondary" size="md" icon={syncing ? null : "refresh"} onClick={handleSync} disabled={syncing}>
            {syncing && <window.Spinner size={13} />}
            {syncing ? "Syncing…" : "Sync"}
          </Button>
          <Button kind="primary"   size="md" icon="plus" onClick={() => setAddOpen(true)}>Add account</Button>
        </>}
        tabs={<>
          <Tab active={filter === "all"}      onClick={() => setFilter("all")} badge={window.COMPANIES.concat(extra).length}>All accounts</Tab>
          <Tab active={filter === "priority"} onClick={() => setFilter("priority")} badge={window.COMPANIES.concat(extra).filter(c => c.gtmPriority === "high").length}>High priority</Tab>
          <Tab active={filter === "engaged"}  onClick={() => setFilter("engaged")} badge={window.COMPANIES.concat(extra).filter(c => ["engaged","opportunity"].includes(c.abm.lifecycleStage)).length}>Engaged</Tab>
          <Tab>Customers</Tab>
          <Tab>My accounts</Tab>
          <MyViewsTab
            views={savedViews}
            activeViewId={activeViewId}
            onApply={applyView}
            onDelete={deleteView}
          />
        </>}
      />

      <div className="page-pad" style={{ paddingTop: 20, display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
        <TextField icon="search" placeholder="Search…" value={search} onChange={(e) => setSearch(e.target.value)} style={{ width: "min(180px, 100%)" }} />
        <window.Menu
          trigger={<Button kind="ghost" size="md" icon="sort">Sort: {{ priority: "Priority", recent: "Recent activity", stale: "Lack of activity", contacts: "Number of contacts", name: "Name (A–Z)" }[sortBy]}</Button>}
          items={[
            { icon: "trending-up", label: "Priority",           active: sortBy === "priority", onClick: () => setSortBy("priority") },
            { icon: "clock",       label: "Recent activity",    active: sortBy === "recent",   onClick: () => setSortBy("recent") },
            { icon: "alert",       label: "Lack of activity",   active: sortBy === "stale",    onClick: () => setSortBy("stale") },
            { icon: "users",       label: "Number of contacts", active: sortBy === "contacts", onClick: () => setSortBy("contacts") },
            { icon: "sort",        label: "Name (A–Z)",         active: sortBy === "name",     onClick: () => setSortBy("name") },
          ]}
        />
        <window.Menu
          trigger={<Button kind="ghost" size="md" icon="sliders">{`Group: ${({ none: "None", lifecycle: "Lifecycle", industry: "Industry", priority: "Priority", owner: "Owner" })[groupBy]}`}</Button>}
          items={[
            { icon: "circle",      label: "No grouping",  active: groupBy === "none",      onClick: () => setGroupBy("none") },
            { icon: "circle-dot",  label: "Lifecycle",    active: groupBy === "lifecycle", onClick: () => setGroupBy("lifecycle") },
            { icon: "briefcase",   label: "Industry",     active: groupBy === "industry",  onClick: () => setGroupBy("industry") },
            { icon: "flag",        label: "GTM priority", active: groupBy === "priority",  onClick: () => setGroupBy("priority") },
            { icon: "user",        label: "Owner",        active: groupBy === "owner",     onClick: () => setGroupBy("owner") },
          ]}
        />
        <EditColumnsButton
          allColumns={ALL_COLUMNS}
          visibleCols={visibleCols}
          columnOrder={columnOrder}
          onToggle={toggleCol}
          onReorder={reorderCols}
          onReset={resetCols}
          hiddenCount={hiddenColCount}
        />
        <SaveViewButton
          views={savedViews}
          activeViewId={activeViewId}
          onSave={saveCurrentView}
          onApply={applyView}
          onDelete={deleteView}
        />
        {activeColumnFilterCount > 0 && (
          <FilterChips
            columnFilters={columnFilters}
            filterOptions={filterOptions}
            onClear={(key) => setColumnFilter(key, [])}
            onClearAll={clearAllColumnFilters}
          />
        )}
        <div style={{ flex: 1 }} />
      </div>

      <div className="page-pad-y" style={{ paddingTop: 16 }}>
        <div className="scroll-x">
          <AccountsTable
            list={visibleList}
            groupBy={groupBy}
            onOpen={(id) => setPeekId(id)}
            columnFilters={columnFilters}
            filterOptions={filterOptions}
            onColumnFilter={setColumnFilter}
            selectedIds={selectedIds}
            onToggleSelected={toggleSelected}
            onSelectMany={selectMany}
            onDeselectMany={deselectMany}
            visibleCols={visibleCols}
            columnOrder={columnOrder}
            contactStatsByCompany={contactStatsByCompany}
          />
        </div>
        {/* Infinite-scroll sentinel + footer indicator. */}
        <div ref={sentinelRef} style={{ height: 1 }} />
        {hasMore ? (
          <div style={{ padding: "16px 0 60px", display: "flex", justifyContent: "center" }}>
            <div style={{ display: "inline-flex", alignItems: "center", gap: 10, color: "var(--ink-3)", fontSize: 12 }}>
              <window.Spinner size={14} />
              <span>Loading more accounts…</span>
              <button onClick={() => setVisibleCount(v => Math.min(v + PAGE_SIZE, totalCount))}
                style={{
                  fontSize: 12, fontWeight: 600, color: "var(--accent)",
                  padding: "4px 8px", borderRadius: 6,
                }}>Load more</button>
            </div>
          </div>
        ) : totalCount > PAGE_SIZE ? (
          <div style={{ padding: "14px 0 60px", display: "flex", justifyContent: "center" }}>
            <div style={{ display: "inline-flex", alignItems: "center", gap: 8, color: "var(--ink-4)", fontSize: 12 }}>
              <Icon name="check" size={11} />
              You've reached the end · {totalCount} account{totalCount === 1 ? "" : "s"}
            </div>
          </div>
        ) : null}
      </div>

      {/* Inline indicator while we pre-fetch a far page. */}
      {paging && (
        <div style={{
          position: "fixed", left: "50%", bottom: 84, transform: "translateX(-50%)",
          background: "var(--surface)", border: "1px solid var(--line)",
          borderRadius: 999, padding: "8px 14px", boxShadow: "0 8px 24px rgba(0,0,0,0.10)",
          display: "inline-flex", alignItems: "center", gap: 8,
          fontSize: 12, color: "var(--ink-2)", zIndex: 60,
        }}>
          <window.Spinner size={13} />
          <span>Loading more accounts from server…</span>
        </div>
      )}
      {/* Floating pagination pill — shows progress and a back-to-top affordance. */}
      <PaginationPill
        shown={shownCount}
        total={totalCount}
        displayTotal={apiTotals?.companies}
        onScrollTop={scrollToTop}
        hidden={selectedIds.size > 0 /* yield space to the bulk bar */}
        pageSize={PAGE_SIZE}
        onJumpToPage={jumpToPage}
      />

      <BulkActionBar
        count={selectedIds.size}
        onClear={clearSelection}
        onAction={(action) => {
          const ids = Array.from(selectedIds);
          /* Mocked side-effects — bulk actions are illustrative here. */
          if (action === "delete") {
            setExtra(prev => prev.filter(c => !selectedIds.has(c.id)));
            flashToast(`Removed ${ids.length} account${ids.length === 1 ? "" : "s"}`);
            clearSelection();
            return;
          }
          flashToast({
            "assign":   `Owner reassigned for ${ids.length} account${ids.length === 1 ? "" : "s"}`,
            "priority": `Priority updated for ${ids.length} account${ids.length === 1 ? "" : "s"}`,
            "tag":      `Tag added to ${ids.length} account${ids.length === 1 ? "" : "s"}`,
            "campaign": `Added ${ids.length} account${ids.length === 1 ? "" : "s"} to campaign`,
            "export":   `Exported ${ids.length} account${ids.length === 1 ? "" : "s"} to CSV`,
          }[action] || "Done");
        }}
      />
      {bulkToast && <BulkToast message={bulkToast} />}

      <NewAccountModal
        open={addOpen}
        onClose={() => setAddOpen(false)}
        onCreate={(c) => {
          setExtra(prev => [c, ...prev]);
          setAddOpen(false);
        }}
      />

      <AccountQuickLookDrawer
        companyId={peekId}
        onClose={() => setPeekId(null)}
        onOpenFull={(id) => { setPeekId(null); onNavigate("account-detail", { companyId: id }); }}
        onNavigate={onNavigate}
      />

      <AdvancedFilterModal
        open={advancedFilterOpen}
        onClose={() => setAdvancedFilterOpen(false)}
        clauses={advancedFilters}
        onChange={setAdvancedFilters}
        fieldCatalog={buildFieldCatalog(filterOptions)}
      />
    </>
  );
}

function NewAccountModal({ open, onClose, onCreate }) {
  const [name, setName] = useStateAcc("");
  const [domain, setDomain] = useStateAcc("");
  const [industry, setIndustry] = useStateAcc("Hospitality");
  const [size, setSize] = useStateAcc("501–1k");
  const [hq, setHq] = useStateAcc("Barcelona, Spain");
  const [priority, setPriority] = useStateAcc("high");

  const [submitting, setSubmitting] = useStateAcc(false);
  const [submitError, setSubmitError] = useStateAcc(null);

  React.useEffect(() => {
    if (open) {
      setName(""); setDomain(""); setIndustry("Hospitality"); setSize("501–1k"); setHq("Barcelona, Spain"); setPriority("high");
      setSubmitting(false); setSubmitError(null);
    }
  }, [open]);

  const handle = async () => {
    if (!name.trim() || submitting) return;
    setSubmitting(true); setSubmitError(null);
    const [hqCity, hqCountry] = hq.split(",").map(s => s.trim());
    /* Generate a stable-ish company_id. Server upserts by this key, so
       re-submitting the same name within the same minute is idempotent. */
    const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
    const company_id = `new-${slug || "account"}-${Date.now()}`;
    const employeeCount = ({"<50":25, "51-200":125, "201-500":350, "501–1k":700, "1k-5k":2500, "5k-10k":7500, "10k+":15000})[size] || 500;
    const live_payload = {
      company_id,
      company_name: name.trim(),
      domain: domain || `${slug}.com`,
      industry,
      employee_range: size,
      employee_count: employeeCount,
      hq_city: hqCity || null,
      hq_country: hqCountry || null,
      gtm_priority: priority,
      tagline: "",
      description: "Newly added account — enrichment pending.",
    };

    try {
      /* Fire the live POST. The server upserts by company_id and
         returns the persisted record (with backend-managed `id` and
         `updatedAt`). If the network call fails we still surface the
         account locally so the user isn't blocked — but flag it. */
      const created = await window.ApiClient.createCompany(live_payload);
      const adapted = created && created.company_id
        ? window.adaptApiCompany(created)
        : window.adaptApiCompany(live_payload);
      onCreate(adapted);
    } catch (err) {
      console.warn("[BusinessTeam] createCompany failed, adding locally:", err && err.message);
      setSubmitError(err && err.message ? err.message : "Could not save to backend — added locally.");
      /* Fall back to a local-only record so the workflow continues. */
      onCreate(window.adaptApiCompany(live_payload));
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <window.Modal
      open={open} onClose={onClose}
      icon="building" title="Add target account"
      subtitle="Enrichment will run automatically after creation."
      footer={<>
        <Button kind="ghost" onClick={onClose} disabled={submitting}>Cancel</Button>
        <Button kind="primary" icon={submitting ? null : "plus"} onClick={handle} disabled={!name.trim() || submitting}>
          {submitting && <window.Spinner size={13} />}
          {submitting ? "Creating…" : "Create account"}
        </Button>
      </>}
    >
      {submitError && (
        <div style={{
          marginBottom: 12, padding: "8px 10px", borderRadius: 7,
          background: "var(--warm-soft)", color: "var(--warm-ink)",
          border: "1px solid color-mix(in oklch, var(--warm) 30%, var(--line))",
          fontSize: 12, display: "flex", alignItems: "center", gap: 8,
        }}>
          <Icon name="alert" size={12} />
          <span>{submitError}</span>
        </div>
      )}
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
        <window.FormField label="Company name" span={2}>
          <window.FormInput autoFocus value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. Acme Hospitality Group" />
        </window.FormField>
        <window.FormField label="Domain" optional>
          <window.FormInput value={domain} onChange={(e) => setDomain(e.target.value)} placeholder="acme.com" />
        </window.FormField>
        <window.FormField label="Industry">
          <window.FormSelect value={industry} onChange={(e) => setIndustry(e.target.value)} options={[
            { value: "Hospitality", label: "Hospitality" }, { value: "Retail", label: "Retail" },
            { value: "Pharmaceuticals", label: "Pharmaceuticals" }, { value: "Manufacturing", label: "Manufacturing" },
            { value: "Technology", label: "Technology" }, { value: "Other", label: "Other" },
          ]} />
        </window.FormField>
        <window.FormField label="Employees">
          <window.FormSelect value={size} onChange={(e) => setSize(e.target.value)} options={[
            { value: "<50", label: "<50" }, { value: "51-200", label: "51–200" }, { value: "201-500", label: "201–500" },
            { value: "501–1k", label: "501–1k" }, { value: "1k-5k", label: "1k–5k" }, { value: "5k-10k", label: "5k–10k" }, { value: "10k+", label: "10k+" },
          ]} />
        </window.FormField>
        <window.FormField label="HQ">
          <window.FormInput value={hq} onChange={(e) => setHq(e.target.value)} placeholder="City, Country" />
        </window.FormField>
        <window.FormField label="Priority" span={2}>
          <div style={{ display: "flex", border: "1px solid var(--line-strong)", borderRadius: 7, overflow: "hidden" }}>
            {["high", "medium", "low"].map((p, i) => (
              <button key={p} onClick={() => setPriority(p)} style={{
                flex: 1, padding: "8px 10px", fontSize: 12.5, fontWeight: 500,
                background: priority === p ? "var(--inset)" : "var(--surface)",
                color: priority === p ? "var(--ink)" : "var(--ink-3)",
                borderRight: i < 2 ? "1px solid var(--line)" : "none",
                textTransform: "capitalize",
              }}>{p}</button>
            ))}
          </div>
        </window.FormField>
      </div>
    </window.Modal>
  );
}

/* EditColumnsButton — toolbar button that opens a popover with checkboxes
   for each toggleable column. The Account column is permanent. */
function EditColumnsButton({ allColumns, visibleCols, columnOrder, onToggle, onReorder, onReset, hiddenCount }) {
  const [open, setOpen] = useStateAcc(false);
  const triggerRef = React.useRef(null);
  const panelRef = React.useRef(null);
  const [pos, setPos] = useStateAcc({ top: 0, left: 0 });
  const [dragKey, setDragKey]   = useStateAcc(null); /* key being dragged */
  const [overKey, setOverKey]   = useStateAcc(null); /* key the pointer is currently over */
  React.useEffect(() => {
    if (!open) return;
    const place = () => {
      const t = triggerRef.current;
      if (!t) return;
      const r = t.getBoundingClientRect();
      const panelW = panelRef.current?.offsetWidth || 240;
      const panelH = panelRef.current?.offsetHeight || 320;
      const margin = 12;
      let left = r.left;
      let top  = r.bottom + 6;
      /* Clamp horizontally so the panel never goes past the viewport edges. */
      if (left + panelW > window.innerWidth  - margin) left = Math.max(margin, window.innerWidth  - panelW - margin);
      if (left < margin) left = margin;
      /* Flip above the trigger if there isn't room below. */
      if (top + panelH > window.innerHeight - margin) top = Math.max(margin, r.top - panelH - 6);
      setPos({ top, left });
    };
    place();
    /* Re-measure once the panel has rendered (panelRef is null on first frame). */
    const raf = requestAnimationFrame(place);
    const onDoc = (e) => {
      if (panelRef.current && panelRef.current.contains(e.target)) return;
      if (triggerRef.current && triggerRef.current.contains(e.target)) return;
      setOpen(false);
    };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    window.addEventListener("resize", place);
    window.addEventListener("scroll", place, true);
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", place);
      window.removeEventListener("scroll", place, true);
      document.removeEventListener("mousedown", onDoc);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);
  return (
    <>
      <button
        ref={triggerRef}
        onClick={() => setOpen(o => !o)}
        style={{
          display: "inline-flex", alignItems: "center", gap: 6,
          padding: "0 10px", height: 30, borderRadius: 7,
          background: open ? "var(--inset)" : "transparent",
          color: "var(--ink-2)", fontSize: 12.5, fontWeight: 500,
          border: "1px solid transparent",
          transition: "background 120ms, color 120ms, border-color 120ms",
        }}
        onMouseEnter={(e) => { if (!open) e.currentTarget.style.background = "var(--inset)"; }}
        onMouseLeave={(e) => { if (!open) e.currentTarget.style.background = "transparent"; }}
        title="Show or hide columns"
      >
        <Icon name="menu" size={13} />
        Edit columns
        {hiddenCount > 0 && (
          <span style={{
            padding: "0 6px", height: 16, borderRadius: 999,
            background: "var(--accent)", color: "#fff",
            fontSize: 10, fontWeight: 700,
            display: "inline-flex", alignItems: "center",
          }}>{hiddenCount}</span>
        )}
      </button>
      {open && (
        <div ref={panelRef} style={{
          position: "fixed", top: pos.top, left: pos.left,
          zIndex: 1000,
          minWidth: 220, maxWidth: 260,
          /* Cap height to the viewport (with a small margin) so the
             popover never spills off-screen when many columns are
             listed. The toggleable-rows region inside scrolls; header
             and reset footer stay pinned. */
          maxHeight: "calc(100vh - 24px)",
          display: "flex", flexDirection: "column",
          background: "var(--surface)",
          border: "1px solid var(--line-strong)",
          borderRadius: 10,
          boxShadow: "var(--shadow-3)",
          padding: 6,
          animation: "fadein 140ms ease-out",
        }}>
          <div style={{
            padding: "4px 8px 6px", fontSize: 10.5, fontWeight: 600,
            letterSpacing: "0.04em", color: "var(--ink-4)", textTransform: "uppercase",
            flexShrink: 0,
          }}>Visible columns</div>
          {/* Required column row */}
          <div style={{
            display: "flex", alignItems: "center", gap: 8,
            padding: "6px 8px", borderRadius: 6,
            color: "var(--ink-3)", fontSize: 12.5,
            flexShrink: 0,
          }}>
            <span style={{
              width: 16, height: 16, borderRadius: 4,
              border: "1.5px solid var(--line-strong)",
              background: "var(--inset)",
              display: "inline-flex", alignItems: "center", justifyContent: "center",
              flexShrink: 0,
            }}>
              <Icon name="check" size={10} style={{ color: "var(--ink-4)" }} />
            </span>
            <span style={{ flex: 1 }}>Account</span>
            <span style={{ fontSize: 10.5, color: "var(--ink-4)" }}>required</span>
          </div>
          {/* Scrollable list of toggleable column rows. */}
          <div style={{ overflowY: "auto", flex: "1 1 auto", minHeight: 0, margin: "0 -2px", padding: "0 2px" }}>
          {(columnOrder || allColumns.map(c => c.key)).map(key => {
            const col = allColumns.find(c => c.key === key);
            if (!col) return null;
            const on = visibleCols[col.key];
            const isDragging = dragKey === col.key;
            const isOver = overKey === col.key && dragKey && dragKey !== col.key;
            return (
              <div key={col.key}
                draggable
                onDragStart={(e) => {
                  setDragKey(col.key);
                  e.dataTransfer.effectAllowed = "move";
                  try { e.dataTransfer.setData("text/plain", col.key); } catch (_) {}
                }}
                onDragOver={(e) => { e.preventDefault(); setOverKey(col.key); e.dataTransfer.dropEffect = "move"; }}
                onDragLeave={() => { if (overKey === col.key) setOverKey(null); }}
                onDrop={(e) => {
                  e.preventDefault();
                  if (dragKey && dragKey !== col.key) onReorder && onReorder(dragKey, col.key);
                  setDragKey(null); setOverKey(null);
                }}
                onDragEnd={() => { setDragKey(null); setOverKey(null); }}
                onClick={() => onToggle(col.key)}
                style={{
                  display: "flex", alignItems: "center", gap: 6,
                  padding: "6px 8px", borderRadius: 6,
                  cursor: isDragging ? "grabbing" : "pointer",
                  fontSize: 12.5, color: "var(--ink)",
                  background: isOver ? "color-mix(in oklch, var(--accent) 14%, var(--surface))"
                                    : "transparent",
                  opacity: isDragging ? 0.5 : 1,
                  borderTop: isOver ? "2px solid var(--accent)" : "2px solid transparent",
                  transition: "background 100ms, border-color 100ms, opacity 100ms",
                }}
                onMouseEnter={(e) => { if (!dragKey) e.currentTarget.style.background = "var(--inset)"; }}
                onMouseLeave={(e) => { if (!isOver) e.currentTarget.style.background = "transparent"; }}
              >
                <span
                  title="Drag to reorder"
                  style={{
                    display: "inline-flex", flexDirection: "column", gap: 1,
                    padding: "2px 1px", color: "var(--ink-4)",
                    cursor: "grab",
                  }}
                  onClick={(e) => e.stopPropagation()}
                >
                  {[0, 1].map(row => (
                    <span key={row} style={{ display: "inline-flex", gap: 2 }}>
                      <span style={{ width: 2, height: 2, background: "currentColor", borderRadius: 1 }} />
                      <span style={{ width: 2, height: 2, background: "currentColor", borderRadius: 1 }} />
                    </span>
                  ))}
                </span>
                <span style={{
                  width: 16, height: 16, borderRadius: 4,
                  border: `1.5px solid ${on ? "var(--accent)" : "var(--line-strong)"}`,
                  background: on ? "var(--accent)" : "var(--surface)",
                  display: "inline-flex", alignItems: "center", justifyContent: "center",
                  flexShrink: 0,
                }}>
                  {on && <Icon name="check" size={10} style={{ color: "#fff" }} />}
                </span>
                <span style={{ flex: 1 }}>{col.label}</span>
              </div>
            );
          })}
          </div>
          <div style={{
            display: "flex", alignItems: "center", justifyContent: "flex-end",
            borderTop: "1px solid var(--line)", marginTop: 4, padding: "6px 6px 2px",
            flexShrink: 0,
          }}>
            <button onClick={(e) => { e.stopPropagation(); onReset(); }}
              disabled={hiddenCount === 0}
              style={{
                fontSize: 11.5, fontWeight: 600,
                color: hiddenCount === 0 ? "var(--ink-4)" : "var(--ink-2)",
                padding: "4px 8px", borderRadius: 5,
                cursor: hiddenCount === 0 ? "default" : "pointer",
              }}>Reset</button>
          </div>
        </div>
      )}
    </>
  );
}

/* FilterChips — surfaces each active column filter as a removable chip so
   the user can see (and individually clear) what's applied. */
function FilterChips({ columnFilters, filterOptions, onClear, onClearAll, labels: customLabels, renderValue: customRenderValue }) {
  const LABELS = customLabels || {
    industry: "Industry", hqCity: "HQ", employeeRange: "Size",
    lifecycle: "Lifecycle", priority: "Priority",
  };
  const renderValue = customRenderValue || ((key, v) => {
    if (key === "lifecycle") return window.LIFECYCLE_LABEL[v] || v;
    if (key === "priority")  return v[0].toUpperCase() + v.slice(1);
    return v;
  });
  const activeKeys = Object.keys(columnFilters).filter(k => (columnFilters[k] || []).length > 0);
  if (activeKeys.length === 0) return null;
  return (
    <div style={{ display: "inline-flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
      {activeKeys.map(key => {
        const values = columnFilters[key];
        const showInline = values.length <= 2;
        const inline = values.slice(0, 2).map(v => renderValue(key, v)).join(", ");
        const overflow = values.length - 2;
        return (
          <span key={key} style={{
            display: "inline-flex", alignItems: "center", gap: 4,
            padding: "3px 4px 3px 10px", borderRadius: 999,
            background: "color-mix(in oklch, var(--accent) 8%, var(--surface))",
            border: "1px solid color-mix(in oklch, var(--accent) 28%, var(--line))",
            color: "var(--accent-ink, var(--accent))",
            fontSize: 11.5, fontWeight: 500,
            maxWidth: 280,
          }}
          title={values.map(v => renderValue(key, v)).join(", ")}>
            <span style={{ fontWeight: 600 }}>{LABELS[key] || key}:</span>
            <span style={{
              color: "var(--ink-2)", fontWeight: 500,
              overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
            }}>{showInline ? inline : `${inline}${overflow > 0 ? ` +${overflow}` : ""}`}</span>
            <button
              onClick={() => onClear(key)}
              title={`Clear ${LABELS[key] || key}`}
              style={{
                width: 18, height: 18, borderRadius: 999,
                display: "inline-flex", alignItems: "center", justifyContent: "center",
                color: "var(--ink-3)", marginLeft: 1,
              }}
              onMouseEnter={(e) => { e.currentTarget.style.background = "color-mix(in oklch, var(--accent) 18%, transparent)"; e.currentTarget.style.color = "var(--accent)"; }}
              onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; e.currentTarget.style.color = "var(--ink-3)"; }}
            >
              <Icon name="x" size={10} />
            </button>
          </span>
        );
      })}
      {activeKeys.length > 1 && (
        <button onClick={onClearAll} style={{
          fontSize: 11.5, fontWeight: 500, color: "var(--ink-3)",
          padding: "3px 8px", borderRadius: 999,
        }}
        onMouseEnter={(e) => { e.currentTarget.style.color = "var(--ink)"; e.currentTarget.style.background = "var(--inset)"; }}
        onMouseLeave={(e) => { e.currentTarget.style.color = "var(--ink-3)"; e.currentTarget.style.background = "transparent"; }}
        >Clear all</button>
      )}
    </div>
  );
}

/* SaveViewButton — popover with a name input + a list of saved views the
   user can apply or delete. */
function SaveViewButton({ views, activeViewId, onSave, onApply, onDelete, describe: customDescribe }) {
  const [open, setOpen] = useStateAcc(false);
  const [name, setName] = useStateAcc("");
  const triggerRef = React.useRef(null);
  const panelRef = React.useRef(null);
  const [pos, setPos] = useStateAcc({ top: 0, left: 0 });
  React.useEffect(() => {
    if (!open) return;
    const place = () => {
      const t = triggerRef.current; if (!t) return;
      const r = t.getBoundingClientRect();
      const panelW = panelRef.current?.offsetWidth || 260;
      const margin = 12;
      let left = r.left;
      let top  = r.bottom + 6;
      if (left + panelW > window.innerWidth - margin) left = Math.max(margin, window.innerWidth - panelW - margin);
      if (left < margin) left = margin;
      setPos({ top, left });
    };
    place();
    const raf = requestAnimationFrame(place);
    const onDoc = (e) => {
      if (panelRef.current && panelRef.current.contains(e.target)) return;
      if (triggerRef.current && triggerRef.current.contains(e.target)) return;
      setOpen(false); setName("");
    };
    const onKey = (e) => { if (e.key === "Escape") { setOpen(false); setName(""); } };
    window.addEventListener("resize", place);
    window.addEventListener("scroll", place, true);
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", place);
      window.removeEventListener("scroll", place, true);
      document.removeEventListener("mousedown", onDoc);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);
  const commit = () => {
    const n = name.trim();
    if (!n) return;
    onSave(n);
    setName("");
  };
  return (
    <>
      <button
        ref={triggerRef}
        onClick={() => setOpen(o => !o)}
        style={{
          display: "inline-flex", alignItems: "center", gap: 6,
          padding: "0 10px", height: 30, borderRadius: 7,
          background: open ? "var(--inset)" : "transparent",
          color: "var(--ink-2)", fontSize: 12.5, fontWeight: 500,
        }}
        onMouseEnter={(e) => { if (!open) e.currentTarget.style.background = "var(--inset)"; }}
        onMouseLeave={(e) => { if (!open) e.currentTarget.style.background = "transparent"; }}
        title="Save the current filter / sort / column setup as a reusable view"
      >
        <Icon name="bookmark" size={13} />
        Save view
        {views.length > 0 && (
          <span style={{
            padding: "0 6px", height: 16, borderRadius: 999,
            background: "var(--inset)", color: "var(--ink-3)",
            fontSize: 10, fontWeight: 600,
            display: "inline-flex", alignItems: "center",
          }}>{views.length}</span>
        )}
      </button>
      {open && (
        <div ref={panelRef} style={{
          position: "fixed", top: pos.top, left: pos.left,
          zIndex: 1000,
          minWidth: 260, maxWidth: 300,
          background: "var(--surface)",
          border: "1px solid var(--line-strong)",
          borderRadius: 10,
          boxShadow: "var(--shadow-3)",
          padding: 6,
          animation: "fadein 140ms ease-out",
        }}>
          <div style={{
            padding: "4px 8px 6px", fontSize: 10.5, fontWeight: 600,
            letterSpacing: "0.04em", color: "var(--ink-4)", textTransform: "uppercase",
          }}>Save current view</div>
          <div style={{ display: "flex", gap: 6, padding: "0 4px 6px" }}>
            <input
              autoFocus
              value={name}
              onChange={(e) => setName(e.target.value)}
              onKeyDown={(e) => { if (e.key === "Enter") commit(); }}
              placeholder="View name"
              style={{
                flex: 1, padding: "6px 8px", fontSize: 12,
                background: "var(--inset)", color: "var(--ink)",
                border: "1px solid var(--line)", borderRadius: 6, outline: "none",
              }}
            />
            <button
              onClick={commit}
              disabled={!name.trim()}
              style={{
                padding: "6px 10px", borderRadius: 6,
                background: name.trim() ? "var(--ink)" : "var(--inset)",
                color: name.trim() ? "#fff" : "var(--ink-4)",
                fontSize: 12, fontWeight: 600,
                cursor: name.trim() ? "pointer" : "default",
              }}
            >Save</button>
          </div>
          {views.length > 0 && (
            <>
              <div style={{
                padding: "10px 8px 6px", fontSize: 10.5, fontWeight: 600,
                letterSpacing: "0.04em", color: "var(--ink-4)", textTransform: "uppercase",
                borderTop: "1px solid var(--line)", marginTop: 2,
              }}>Saved views</div>
              <div style={{ maxHeight: 220, overflowY: "auto" }}>
                {views.map(v => {
                  const isActive = v.id === activeViewId;
                  const summary = (customDescribe || describeViewState)(v.state);
                  return (
                    <div key={v.id} style={{
                      display: "flex", alignItems: "center", gap: 6,
                      padding: "6px 6px 6px 8px", borderRadius: 6,
                      background: isActive ? "color-mix(in oklch, var(--accent) 10%, var(--surface))" : "transparent",
                    }}
                    onMouseEnter={(e) => { if (!isActive) e.currentTarget.style.background = "var(--inset)"; }}
                    onMouseLeave={(e) => { if (!isActive) e.currentTarget.style.background = "transparent"; }}
                    >
                      <button
                        onClick={() => { onApply(v); setOpen(false); }}
                        style={{
                          flex: 1, minWidth: 0, padding: "2px 0", textAlign: "left",
                          background: "transparent", color: "var(--ink)",
                        }}
                      >
                        <div style={{
                          fontSize: 12.5, fontWeight: 600,
                          color: isActive ? "var(--accent)" : "var(--ink)",
                          overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                          display: "flex", alignItems: "center", gap: 6,
                        }}>
                          {isActive && <Icon name="check" size={11} style={{ color: "var(--accent)" }} />}
                          {v.name}
                        </div>
                        {summary && (
                          <div style={{
                            fontSize: 10.5, color: "var(--ink-4)",
                            overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                            marginTop: 1,
                          }}>{summary}</div>
                        )}
                      </button>
                      <button
                        onClick={() => onDelete(v.id)}
                        title="Delete view"
                        style={{
                          width: 24, height: 24, borderRadius: 6,
                          display: "inline-flex", alignItems: "center", justifyContent: "center",
                          color: "var(--ink-4)",
                        }}
                        onMouseEnter={(e) => { e.currentTarget.style.background = "var(--inset)"; e.currentTarget.style.color = "var(--ink-2)"; }}
                        onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; e.currentTarget.style.color = "var(--ink-4)"; }}
                      >
                        <Icon name="trash" size={11} />
                      </button>
                    </div>
                  );
                })}
              </div>
            </>
          )}
        </div>
      )}
    </>
  );
}

/* Human-readable summary of a saved view's state — shown as the subtitle
   under the view name in the Save-view popover. */
function describeViewState(s) {
  if (!s) return "";
  const parts = [];
  if (s.filter && s.filter !== "all") parts.push({ priority: "High priority", engaged: "Engaged + opp." }[s.filter] || s.filter);
  if (s.sortBy && s.sortBy !== "priority") parts.push(`Sort: ${({ recent: "Recent activity", stale: "Lack of activity", name: "Name" })[s.sortBy] || s.sortBy}`);
  if (s.groupBy && s.groupBy !== "none") parts.push(`Group: ${s.groupBy}`);
  const cf = s.columnFilters || {};
  const cfCount = Object.values(cf).reduce((n, arr) => n + (arr.length ? 1 : 0), 0);
  if (cfCount > 0) parts.push(`${cfCount} filter${cfCount === 1 ? "" : "s"}`);
  return parts.join(" · ");
}

/* MyViewsTab — tab-styled trigger that opens a dropdown listing the user's
   saved views. Picking one applies it; the active view's name shows in the
   tab label. */
function MyViewsTab({ views, activeViewId, onApply, onDelete, describe: customDescribe }) {
  const [open, setOpen] = useStateAcc(false);
  const triggerRef = React.useRef(null);
  const panelRef = React.useRef(null);
  const [pos, setPos] = useStateAcc({ top: 0, left: 0 });
  React.useEffect(() => {
    if (!open) return;
    const place = () => {
      const t = triggerRef.current; if (!t) return;
      const r = t.getBoundingClientRect();
      const panelW = panelRef.current?.offsetWidth || 260;
      const margin = 12;
      let left = r.left;
      let top  = r.bottom + 6;
      if (left + panelW > window.innerWidth - margin) left = Math.max(margin, window.innerWidth - panelW - margin);
      if (left < margin) left = margin;
      setPos({ top, left });
    };
    place();
    const raf = requestAnimationFrame(place);
    const onDoc = (e) => {
      if (panelRef.current && panelRef.current.contains(e.target)) return;
      if (triggerRef.current && triggerRef.current.contains(e.target)) return;
      setOpen(false);
    };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    window.addEventListener("resize", place);
    window.addEventListener("scroll", place, true);
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", place);
      window.removeEventListener("scroll", place, true);
      document.removeEventListener("mousedown", onDoc);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);
  const activeView = views.find(v => v.id === activeViewId);
  const label = activeView ? activeView.name : "My views";
  return (
    <>
      <button
        ref={triggerRef}
        onClick={() => setOpen(o => !o)}
        style={{
          padding: "8px 10px 8px 12px", fontSize: 13, fontWeight: 500,
          color: activeView ? "var(--ink)" : "var(--ink-3)",
          borderBottom: activeView ? "1.5px solid var(--ink)" : "1.5px solid transparent",
          marginBottom: -1,
          display: "inline-flex", alignItems: "center", gap: 6,
          transition: "color 120ms, border-color 120ms",
        }}
      >
        <Icon name="bookmark" size={12} />
        <span style={{ maxWidth: 160, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
          {label}
        </span>
        {views.length > 0 && (
          <span className="mono num" style={{
            minWidth: 18, height: 18, padding: "0 5px",
            background: "var(--inset)", color: "var(--ink-3)",
            borderRadius: 9, fontSize: 10, fontWeight: 600,
            display: "inline-flex", alignItems: "center", justifyContent: "center",
          }}>{views.length}</span>
        )}
        <Icon name="chevron-down" size={11} style={{ color: "var(--ink-4)" }} />
      </button>
      {open && (
        <div ref={panelRef} style={{
          position: "fixed", top: pos.top, left: pos.left,
          zIndex: 1000,
          minWidth: 240, maxWidth: 300,
          background: "var(--surface)",
          border: "1px solid var(--line-strong)",
          borderRadius: 10,
          boxShadow: "var(--shadow-3)",
          padding: 6,
          animation: "fadein 140ms ease-out",
          textTransform: "none", letterSpacing: "normal",
        }}>
          <div style={{
            padding: "4px 8px 6px", fontSize: 10.5, fontWeight: 600,
            letterSpacing: "0.04em", color: "var(--ink-4)", textTransform: "uppercase",
          }}>My saved views</div>
          {views.length === 0 ? (
            <div style={{
              padding: "16px 12px 14px", fontSize: 12, color: "var(--ink-4)",
              textAlign: "center", lineHeight: 1.5,
            }}>
              No saved views yet.<br/>
              <span style={{ color: "var(--ink-3)" }}>
                Click <strong style={{ color: "var(--ink-2)" }}>Save view</strong> in the toolbar to capture filters, sort, and columns.
              </span>
            </div>
          ) : (
            <div style={{ maxHeight: 280, overflowY: "auto" }}>
              {views.map(v => {
                const isActive = v.id === activeViewId;
                const summary = (customDescribe || describeViewState)(v.state);
                return (
                  <div key={v.id} style={{
                    display: "flex", alignItems: "center", gap: 6,
                    padding: "6px 6px 6px 8px", borderRadius: 6,
                    background: isActive ? "color-mix(in oklch, var(--accent) 10%, var(--surface))" : "transparent",
                  }}
                  onMouseEnter={(e) => { if (!isActive) e.currentTarget.style.background = "var(--inset)"; }}
                  onMouseLeave={(e) => { if (!isActive) e.currentTarget.style.background = "transparent"; }}
                  >
                    <button
                      onClick={() => { onApply(v); setOpen(false); }}
                      style={{
                        flex: 1, minWidth: 0, padding: "2px 0", textAlign: "left",
                        background: "transparent", color: "var(--ink)",
                      }}
                    >
                      <div style={{
                        fontSize: 12.5, fontWeight: 600,
                        color: isActive ? "var(--accent)" : "var(--ink)",
                        overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                        display: "flex", alignItems: "center", gap: 6,
                      }}>
                        {isActive && <Icon name="check" size={11} style={{ color: "var(--accent)" }} />}
                        {v.name}
                      </div>
                      {summary && (
                        <div style={{
                          fontSize: 10.5, color: "var(--ink-4)",
                          overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
                          marginTop: 1,
                        }}>{summary}</div>
                      )}
                    </button>
                    <button
                      onClick={() => onDelete(v.id)}
                      title="Delete view"
                      style={{
                        width: 24, height: 24, borderRadius: 6,
                        display: "inline-flex", alignItems: "center", justifyContent: "center",
                        color: "var(--ink-4)",
                      }}
                      onMouseEnter={(e) => { e.currentTarget.style.background = "var(--inset)"; e.currentTarget.style.color = "var(--ink-2)"; }}
                      onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; e.currentTarget.style.color = "var(--ink-4)"; }}
                    >
                      <Icon name="trash" size={11} />
                    </button>
                  </div>
                );
              })}
            </div>
          )}
        </div>
      )}
    </>
  );
}

function AccountsTable({
  list, onOpen, groupBy = "none",
  columnFilters = {}, filterOptions = {}, onColumnFilter,
  selectedIds = new Set(), onToggleSelected, onSelectMany, onDeselectMany,
  visibleCols = { industry: true, hq: true, size: true, lifecycle: true, priority: true, action: true },
  columnOrder = ["industry", "hq", "size", "lifecycle", "priority", "action"],
  contactStatsByCompany = null,
}) {
  /* Group the rows when requested. Single-table render with sticky-style
     group header rows interleaved between data rows. */
  const groupOf = (c) => {
    if (groupBy === "lifecycle") return window.LIFECYCLE_LABEL[c.abm.lifecycleStage] || c.abm.lifecycleStage;
    if (groupBy === "industry")  return c.industry;
    if (groupBy === "priority")  return ({ high: "High priority", medium: "Medium priority", low: "Low priority" })[c.gtmPriority] || "Other";
    if (groupBy === "owner")     return getUser(c.owner)?.name || "Unassigned";
    return null;
  };

  const grouped = groupBy === "none" ? null : (() => {
    const buckets = {};
    list.forEach(c => {
      const g = groupOf(c) || "Other";
      (buckets[g] = buckets[g] || []).push(c);
    });
    return Object.keys(buckets).sort().map(name => ({ name, rows: buckets[name] }));
  })();

  const visibleIds = list.map(c => c.id);
  const selectedVisible = visibleIds.filter(id => selectedIds.has(id));
  const headerState = selectedVisible.length === 0
    ? "none"
    : selectedVisible.length === visibleIds.length ? "all" : "some";
  const toggleHeader = () => {
    if (headerState === "all") onDeselectMany && onDeselectMany(visibleIds);
    else                       onSelectMany && onSelectMany(visibleIds);
  };

  /* Build the grid template dynamically based on which columns are visible
     AND their current order. Pixel ceilings (minmax(min, max)) keep columns
     from blowing out the row when content is long. */
  const COL_DEFS = {
    /* defaults */
    industry:           { width: "minmax(110px, 150px)" },
    hq:                 { width: "minmax(100px, 140px)" },
    size:               { width: "90px"  },
    lifecycle:          { width: "minmax(100px, 130px)" },
    priority:           { width: "minmax(120px, 140px)" },
    action:             { width: "minmax(220px, 320px)" },
    /* identity / contact */
    domain:             { width: "minmax(120px, 180px)" },
    website:            { width: "minmax(140px, 220px)" },
    linkedin:           { width: "100px" },
    tagline:            { width: "minmax(160px, 280px)" },
    /* headcount + growth */
    employeeCount:      { width: "100px" },
    yearFounded:        { width: "84px"  },
    followerCount:      { width: "100px" },
    /* geo */
    hqAddress:          { width: "minmax(160px, 240px)" },
    countryCount:       { width: "90px"  },
    locationCount:      { width: "90px"  },
    spainLocations:     { width: "100px" },
    otherLocations:     { width: "100px" },
    /* languages */
    englishRatio:       { width: "minmax(110px, 140px)" },
    officialLangs:      { width: "minmax(140px, 200px)" },
    /* sales reasoning */
    targetScore:        { width: "100px" },
    salesReason:        { width: "minmax(220px, 320px)" },
    /* abm */
    fitScore:           { width: "90px"  },
    intentScore:        { width: "90px"  },
    relationshipScore:  { width: "100px" },
    priorityScore:      { width: "100px" },
    stakeholders:       { width: "100px" },
    openOpps:           { width: "90px"  },
    lastEngagement:    { width: "minmax(110px, 140px)" },
    /* live counts */
    assocContacts:      { width: "120px" },
    connectionRequests: { width: "120px" },
    connectedContacts:  { width: "120px" },
    /* misc */
    specialties:        { width: "minmax(160px, 240px)" },
    tags:               { width: "minmax(140px, 220px)" },
    updated:            { width: "100px" },
  };
  const orderedKeys = columnOrder.filter(k => visibleCols[k] && COL_DEFS[k]);
  const dynCols = orderedKeys.map(k => COL_DEFS[k].width).join(" ");
  const gridTemplate = `36px 32px minmax(180px, 240px) ${dynCols} 40px`;

  return (
    <Card padding={0} style={{ overflow: "hidden", borderRadius: 0 }}>
      <div style={{
        display: "grid",
        gridTemplateColumns: gridTemplate,
        alignItems: "center",
        padding: "10px 16px",
        borderBottom: "1px solid var(--line)",
        background: "var(--surface-2)",
        fontSize: 11, fontWeight: 600, letterSpacing: "0.04em",
        color: "var(--ink-3)", textTransform: "uppercase",
      }}>
        <div style={{ display: "flex", alignItems: "center" }}>
          <SelectionCheckbox
            state={headerState}
            onChange={toggleHeader}
            title={headerState === "all" ? "Deselect all" : "Select all on this page"}
          />
        </div>
        <div />
        <div>Account</div>
        {orderedKeys.map(key => {
          if (key === "industry") return (
            <ColumnHeader key={key} label="Industry"
              options={filterOptions.industry || []}
              selected={columnFilters.industry || []}
              onChange={(v) => onColumnFilter && onColumnFilter("industry", v)} />
          );
          if (key === "hq") return (
            <ColumnHeader key={key} label="HQ"
              options={filterOptions.hqCity || []}
              selected={columnFilters.hqCity || []}
              onChange={(v) => onColumnFilter && onColumnFilter("hqCity", v)}
              searchable />
          );
          if (key === "size") return (
            <ColumnHeader key={key} label="Size"
              options={filterOptions.employeeRange || []}
              selected={columnFilters.employeeRange || []}
              onChange={(v) => onColumnFilter && onColumnFilter("employeeRange", v)} />
          );
          if (key === "lifecycle") return (
            <ColumnHeader key={key} label="Lifecycle"
              options={filterOptions.lifecycle || []}
              renderOption={(v) => window.LIFECYCLE_LABEL[v] || v}
              selected={columnFilters.lifecycle || []}
              onChange={(v) => onColumnFilter && onColumnFilter("lifecycle", v)} />
          );
          if (key === "priority") return (
            <ColumnHeader key={key} label="Target tier"
              options={filterOptions.priority || []}
              renderOption={(v) => v[0].toUpperCase() + v.slice(1)}
              selected={columnFilters.priority || []}
              onChange={(v) => onColumnFilter && onColumnFilter("priority", v)} />
          );
          if (key === "action") return <div key={key}>Next best action</div>;
          /* Static-label headers for new columns. They have no filter
             popovers — just plain labels. (Filterable ones could be
             added later once /facets ships.) */
          if (key === "domain")            return <div key={key}>Domain</div>;
          if (key === "website")           return <div key={key}>Website</div>;
          if (key === "linkedin")          return <div key={key}>LinkedIn</div>;
          if (key === "tagline")           return <div key={key}>Tagline</div>;
          if (key === "employeeCount")     return <div key={key}>Headcount</div>;
          if (key === "yearFounded")       return <div key={key}>Founded</div>;
          if (key === "followerCount")     return <div key={key}>Followers</div>;
          if (key === "hqAddress")         return <div key={key}>HQ address</div>;
          if (key === "countryCount")      return <div key={key}>Countries</div>;
          if (key === "locationCount")     return <div key={key}>Locations</div>;
          if (key === "spainLocations")    return <div key={key}>Spain loc.</div>;
          if (key === "otherLocations")    return <div key={key}>Other loc.</div>;
          if (key === "englishRatio")      return <div key={key}>English-strong</div>;
          if (key === "officialLangs")     return <div key={key}>Languages</div>;
          if (key === "targetScore")       return <div key={key}>Target score</div>;
          if (key === "salesReason")       return <div key={key}>Why this account</div>;
          if (key === "fitScore")          return <div key={key}>Fit</div>;
          if (key === "intentScore")       return <div key={key}>Intent</div>;
          if (key === "relationshipScore") return <div key={key}>Relationship</div>;
          if (key === "priorityScore")     return <div key={key}>Priority score</div>;
          if (key === "stakeholders")      return <div key={key}>Stakeholders</div>;
          if (key === "openOpps")          return <div key={key}>Open opps</div>;
          if (key === "lastEngagement")    return <div key={key}>Last engagement</div>;
          if (key === "assocContacts")     return <div key={key}>Associated contacts</div>;
          if (key === "connectionRequests")return <div key={key}>Connection requests</div>;
          if (key === "connectedContacts") return <div key={key}>Connected contacts</div>;
          if (key === "specialties")       return <div key={key}>Specialties</div>;
          if (key === "tags")              return <div key={key}>Tags</div>;
          if (key === "updated")           return <div key={key}>Updated</div>;
          return null;
        })}
        <div />
      </div>
      {grouped
        ? grouped.map(g => {
            const groupIds = g.rows.map(c => c.id);
            const selectedInGroup = groupIds.filter(id => selectedIds.has(id));
            const groupState = selectedInGroup.length === 0
              ? "none"
              : selectedInGroup.length === groupIds.length ? "all" : "some";
            const toggleGroup = () => {
              if (groupState === "all") onDeselectMany && onDeselectMany(groupIds);
              else                      onSelectMany && onSelectMany(groupIds);
            };
            return (
              <React.Fragment key={g.name}>
                <div style={{
                  padding: "8px 16px",
                  background: "var(--bg-tint)",
                  borderBottom: "1px solid var(--line)",
                  display: "flex", alignItems: "center", gap: 10,
                  fontSize: 11, fontWeight: 600, color: "var(--ink-2)",
                  textTransform: "uppercase", letterSpacing: "0.04em",
                }}>
                  <SelectionCheckbox state={groupState} onChange={toggleGroup} title={`Select all in ${g.name}`} />
                  <span>{g.name}</span>
                  <span className="mono num" style={{ color: "var(--ink-4)", fontWeight: 500 }}>{g.rows.length}</span>
                </div>
                {g.rows.map(c => (
                  <AccountRow
                    key={c.id} c={c}
                    selected={selectedIds.has(c.id)}
                    onToggle={() => onToggleSelected && onToggleSelected(c.id)}
                    onOpen={() => onOpen(c.id)}
                    orderedKeys={orderedKeys}
                    gridTemplate={gridTemplate}
                    contactStats={contactStatsByCompany?.get(c.id) || null}
                  />
                ))}
              </React.Fragment>
            );
          })
        : list.map(c => (
            <AccountRow
              key={c.id} c={c}
              selected={selectedIds.has(c.id)}
              onToggle={() => onToggleSelected && onToggleSelected(c.id)}
              onOpen={() => onOpen(c.id)}
              orderedKeys={orderedKeys}
              gridTemplate={gridTemplate}
              contactStats={contactStatsByCompany?.get(c.id) || null}
            />
          ))}
    </Card>
  );
}

/* Column header with built-in multi-select filter popover.
   Clicking the label or the small filter icon opens a panel of checkboxes.
   When `searchable`, a search input filters the options list. */
function ColumnHeader({ label, options = [], selected = [], onChange, renderOption, searchable = false, align = "left" }) {
  const [open, setOpen] = useStateAcc(false);
  const [q, setQ] = useStateAcc("");
  const [pos, setPos] = useStateAcc({ top: 0, left: 0 });
  const triggerRef = React.useRef(null);
  const panelRef = React.useRef(null);

  const place = () => {
    const t = triggerRef.current;
    if (!t) return;
    const r = t.getBoundingClientRect();
    setPos({ top: r.bottom + 6, left: r.left });
  };

  React.useEffect(() => {
    if (!open) return;
    place();
    const onDoc = (e) => {
      if (panelRef.current && panelRef.current.contains(e.target)) return;
      if (triggerRef.current && triggerRef.current.contains(e.target)) return;
      setOpen(false); setQ("");
    };
    const onKey = (e) => { if (e.key === "Escape") { setOpen(false); setQ(""); } };
    const onScroll = () => place();
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    window.addEventListener("scroll", onScroll, true);
    window.addEventListener("resize", onScroll);
    return () => {
      document.removeEventListener("mousedown", onDoc);
      document.removeEventListener("keydown", onKey);
      window.removeEventListener("scroll", onScroll, true);
      window.removeEventListener("resize", onScroll);
    };
  }, [open]);

  const active = selected.length > 0;
  const toggle = (v) => onChange && onChange(selected.includes(v) ? selected.filter(x => x !== v) : [...selected, v]);
  const clear  = () => onChange && onChange([]);
  const shown  = q ? options.filter(o => (renderOption ? renderOption(o) : String(o)).toLowerCase().includes(q.toLowerCase())) : options;

  return (
    <div style={{ position: "relative", textAlign: align }}>
      <button
        ref={triggerRef}
        onClick={() => setOpen(o => !o)}
        title={active ? `${label} — ${selected.length} filter${selected.length === 1 ? "" : "s"} active` : `Filter ${label}`}
        style={{
          display: "inline-flex", alignItems: "center", gap: 5,
          padding: "2px 6px", margin: "-2px -6px", borderRadius: 5,
          color: active ? "var(--accent)" : "var(--ink-3)",
          background: open ? "var(--inset)" : "transparent",
          fontSize: 11, fontWeight: 600, letterSpacing: "0.04em", textTransform: "uppercase",
          cursor: "pointer",
          transition: "background 120ms, color 120ms",
        }}
        onMouseEnter={(e) => { if (!open) e.currentTarget.style.background = "var(--inset)"; }}
        onMouseLeave={(e) => { if (!open) e.currentTarget.style.background = "transparent"; }}
      >
        <span>{label}</span>
        {active
          ? <span style={{
              minWidth: 14, height: 14, padding: "0 4px", borderRadius: 999,
              background: "var(--accent)", color: "#fff",
              display: "inline-flex", alignItems: "center", justifyContent: "center",
              fontSize: 9, fontWeight: 700, lineHeight: 1,
            }}>{selected.length}</span>
          : <Icon name="chevron-down" size={10} style={{ opacity: 0.7 }} />}
      </button>
      {open && (
        <div ref={panelRef} style={{
          position: "fixed", top: pos.top, left: pos.left,
          zIndex: 1000,
          minWidth: 220, maxWidth: 280,
          background: "var(--surface)",
          border: "1px solid var(--line-strong)",
          borderRadius: 10,
          boxShadow: "var(--shadow-3)",
          textTransform: "none", letterSpacing: "normal",
          padding: 6,
        }}
        onClick={(e) => e.stopPropagation()}
        >
          {searchable && (
            <div style={{ padding: "4px 4px 6px" }}>
              <input
                autoFocus
                value={q} onChange={(e) => setQ(e.target.value)}
                placeholder={`Filter ${label.toLowerCase()}…`}
                style={{
                  width: "100%", padding: "6px 8px", fontSize: 12,
                  background: "var(--inset)", color: "var(--ink)",
                  border: "1px solid var(--line)", borderRadius: 6, outline: "none",
                }}
              />
            </div>
          )}
          <div style={{ maxHeight: 260, overflowY: "auto", padding: "2px 0" }}>
            {shown.length === 0 && (
              <div style={{ padding: "10px 10px", fontSize: 12, color: "var(--ink-4)", textAlign: "center" }}>
                No matches
              </div>
            )}
            {shown.map(v => {
              const isOn = selected.includes(v);
              return (
                <label key={String(v)} style={{
                  display: "flex", alignItems: "center", gap: 8,
                  padding: "6px 8px", borderRadius: 6,
                  cursor: "pointer", fontSize: 12.5, color: "var(--ink)",
                  background: "transparent",
                }}
                onMouseEnter={(e) => e.currentTarget.style.background = "var(--inset)"}
                onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}
                >
                  <span style={{
                    width: 14, height: 14, borderRadius: 3,
                    border: `1.5px solid ${isOn ? "var(--accent)" : "var(--line-strong)"}`,
                    background: isOn ? "var(--accent)" : "var(--surface)",
                    display: "inline-flex", alignItems: "center", justifyContent: "center",
                    flexShrink: 0,
                  }}>
                    {isOn && <Icon name="check" size={9} style={{ color: "#fff" }} />}
                  </span>
                  <input
                    type="checkbox" checked={isOn} onChange={() => toggle(v)}
                    style={{ position: "absolute", opacity: 0, pointerEvents: "none" }}
                  />
                  <span style={{ flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
                    onClick={() => toggle(v)}>
                    {renderOption ? renderOption(v) : String(v)}
                  </span>
                </label>
              );
            })}
          </div>
          <div style={{
            display: "flex", alignItems: "center", justifyContent: "space-between",
            borderTop: "1px solid var(--line)", marginTop: 4, paddingTop: 6, padding: "6px 6px 2px",
          }}>
            <button onClick={clear} disabled={!active} style={{
              fontSize: 11.5, fontWeight: 600, color: active ? "var(--ink-2)" : "var(--ink-4)",
              padding: "4px 6px", borderRadius: 5,
              cursor: active ? "pointer" : "default",
            }}>Clear</button>
            <span style={{ fontSize: 11, color: "var(--ink-4)" }} className="mono">
              {selected.length}/{options.length}
            </span>
          </div>
        </div>
      )}
    </div>
  );
}

/* SelectionCheckbox — tri-state (none/some/all) checkbox styled to match the design system.
   `visible` hides it until row hover when used in data rows. */
function SelectionCheckbox({ state = "none", onChange, title, visible = true }) {
  const checked = state === "all";
  const indeterminate = state === "some";
  const handle = (e) => {
    e.stopPropagation();
    if (e.preventDefault) e.preventDefault();
    onChange && onChange();
  };
  return (
    <span
      onClick={handle}
      onKeyDown={(e) => { if (e.key === " " || e.key === "Enter") handle(e); }}
      tabIndex={0}
      title={title}
      role="checkbox"
      aria-checked={indeterminate ? "mixed" : checked}
      style={{
        width: 16, height: 16, borderRadius: 4,
        border: `1.5px solid ${checked || indeterminate ? "var(--accent)" : "var(--line-strong)"}`,
        background: checked || indeterminate ? "var(--accent)" : "var(--surface)",
        display: "inline-flex", alignItems: "center", justifyContent: "center",
        flexShrink: 0, cursor: "pointer",
        opacity: visible || checked || indeterminate ? 1 : 0,
        transition: "opacity 120ms, background 120ms, border-color 120ms",
        outline: "none",
      }}
    >
      {checked && <Icon name="check" size={10} style={{ color: "#fff" }} />}
      {indeterminate && (
        <span style={{ width: 8, height: 2, borderRadius: 1, background: "#fff" }} />
      )}
    </span>
  );
}

/* BulkActionBar — floating action surface that appears at the bottom of the
   page when one or more accounts are selected. */
function BulkActionBar({ count, onAction, onClear }) {
  if (!count) return null;
  return (
    <div style={{
      position: "fixed", bottom: 18, left: "50%", transform: "translateX(-50%)",
      zIndex: 120,
      display: "flex", alignItems: "center", gap: 4,
      padding: "6px 6px 6px 12px",
      background: "var(--ink)", color: "#fff",
      borderRadius: 12,
      boxShadow: "0 12px 28px rgba(0,0,0,0.20), 0 2px 6px rgba(0,0,0,0.12)",
      animation: "bulkBarIn 180ms ease-out",
    }}>
      <span style={{
        display: "inline-flex", alignItems: "center", gap: 8,
        paddingRight: 10, marginRight: 4,
        borderRight: "1px solid rgba(255,255,255,0.16)",
        fontSize: 12.5, fontWeight: 600, letterSpacing: "-0.005em",
      }}>
        <span style={{
          minWidth: 20, height: 20, padding: "0 6px", borderRadius: 999,
          background: "var(--accent)", color: "#fff",
          display: "inline-flex", alignItems: "center", justifyContent: "center",
          fontSize: 11, fontWeight: 700,
        }}>{count}</span>
        selected
      </span>
      <BulkAction icon="user"        label="Assign"   onClick={() => onAction("assign")} />
      <BulkAction icon="flag"        label="Priority" onClick={() => onAction("priority")} />
      <BulkAction icon="tag"         label="Tag"      onClick={() => onAction("tag")} />
      <BulkAction icon="send"        label="Campaign" onClick={() => onAction("campaign")} />
      <BulkAction icon="download"    label="Export"   onClick={() => onAction("export")} />
      <BulkAction icon="trash"       label="Delete"   onClick={() => onAction("delete")} danger />
      <button
        onClick={onClear} title="Clear selection"
        style={{
          marginLeft: 4, width: 30, height: 30, borderRadius: 8,
          display: "inline-flex", alignItems: "center", justifyContent: "center",
          background: "rgba(255,255,255,0.08)", color: "#fff",
        }}
      >
        <Icon name="x" size={13} />
      </button>
    </div>
  );
}

function BulkAction({ icon, label, onClick, danger = false }) {
  const [hover, setHover] = useStateAcc(false);
  return (
    <button
      onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        display: "inline-flex", alignItems: "center", gap: 5,
        padding: "6px 10px", borderRadius: 8,
        background: hover
          ? (danger ? "rgba(220, 60, 60, 0.18)" : "rgba(255,255,255,0.10)")
          : "transparent",
        color: danger && hover ? "#ffb3b3" : "#fff",
        fontSize: 12, fontWeight: 500,
        transition: "background 120ms, color 120ms",
      }}
    >
      <Icon name={icon} size={12} />
      {label}
    </button>
  );
}

/* PaginationPill — small floating pill that tracks scroll progress and
   shows "shown / total". A back-to-top button appears once the user has
   scrolled meaningfully into the list. */
function PaginationPill({ shown, total, displayTotal, onScrollTop, hidden = false, pageSize = 30, onJumpToPage }) {
  const [scrolled, setScrolled] = useStateAcc(false);
  const [open, setOpen] = useStateAcc(false);
  const triggerRef = React.useRef(null);
  const panelRef = React.useRef(null);
  React.useEffect(() => {
    const scroller = document.querySelector(".page-scroll");
    const target = scroller || window;
    const onScroll = () => {
      const y = scroller ? scroller.scrollTop : (window.scrollY || 0);
      setScrolled(y > 240);
    };
    target.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => target.removeEventListener("scroll", onScroll);
  }, []);
  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => {
      if (panelRef.current && panelRef.current.contains(e.target)) return;
      if (triggerRef.current && triggerRef.current.contains(e.target)) return;
      setOpen(false);
    };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onKey); };
  }, [open]);
  if (hidden || total === 0) return null;
  /* `displayTotal` (when provided) reflects the true DB total discovered
     via binary-search probe. The label and progress ring use it so the
     user sees "30 of 4,001" even though we only loaded one page. The
     page-jump menu still operates on `total` (in-memory size) so we
     don't offer to jump to unloaded pages. */
  const labelTotal = (typeof displayTotal === "number" && displayTotal >= total) ? displayTotal : total;
  const showsBothCounts = labelTotal !== total;
  const pct = Math.min(100, Math.round((shown / labelTotal) * 100));
  const pageCount = Math.max(1, Math.ceil(total / pageSize));
  /* Current "page" = the page whose last row is visible (so 90/152 with pageSize 30 = page 3). */
  const currentPage = Math.min(pageCount, Math.max(1, Math.ceil(shown / pageSize)));
  const pages = Array.from({ length: pageCount }, (_, i) => {
    const from = i * pageSize + 1;
    const to   = Math.min((i + 1) * pageSize, total);
    return { idx: i + 1, from, to };
  });
  return (
    <div style={{
      position: "fixed", bottom: 18, right: 18,
      zIndex: 110,
      display: "flex", alignItems: "center", gap: 4,
      padding: "6px 6px 6px 12px",
      background: "var(--surface)",
      border: "1px solid var(--line-strong)",
      borderRadius: 999,
      boxShadow: "0 10px 24px rgba(0,0,0,0.10), 0 1px 3px rgba(0,0,0,0.06)",
      animation: "fadein 180ms ease-out",
    }}>
      <span style={{
        width: 22, height: 22, borderRadius: 999, flexShrink: 0,
        background: `conic-gradient(var(--accent) ${pct * 3.6}deg, var(--line) 0)`,
        display: "inline-flex", alignItems: "center", justifyContent: "center",
      }}>
        <span style={{ width: 14, height: 14, borderRadius: 999, background: "var(--surface)" }} />
      </span>
      <button
        ref={triggerRef}
        onClick={() => setOpen(o => !o)}
        title={showsBothCounts
          ? `${shown.toLocaleString()} visible · ${total.toLocaleString()} loaded · ${labelTotal.toLocaleString()} in database`
          : "Jump to a page"}
        style={{
          display: "inline-flex", alignItems: "baseline", gap: 5,
          fontSize: 12, color: "var(--ink-2)",
          padding: "2px 6px", borderRadius: 6,
          background: open ? "var(--inset)" : "transparent",
          cursor: "pointer",
        }}
        onMouseEnter={(e) => { if (!open) e.currentTarget.style.background = "var(--inset)"; }}
        onMouseLeave={(e) => { if (!open) e.currentTarget.style.background = "transparent"; }}
      >
        <span className="mono num" style={{ fontWeight: 700, color: "var(--ink)" }}>{shown}</span>
        <span style={{ color: "var(--ink-4)" }}>of</span>
        <span className="mono num" style={{ fontWeight: 600 }}>{labelTotal.toLocaleString()}</span>
        <Icon name="chevron-down" size={10} style={{ alignSelf: "center", color: "var(--ink-4)" }} />
      </button>
      {scrolled && (
        <button
          onClick={onScrollTop}
          title="Back to top"
          style={{
            width: 28, height: 28, borderRadius: 999,
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            background: "var(--inset)", color: "var(--ink-2)",
            marginLeft: 2,
          }}
          onMouseEnter={(e) => { e.currentTarget.style.background = "var(--line)"; e.currentTarget.style.color = "var(--ink)"; }}
          onMouseLeave={(e) => { e.currentTarget.style.background = "var(--inset)"; e.currentTarget.style.color = "var(--ink-2)"; }}
        >
          <Icon name="chevron-up" size={13} />
        </button>
      )}
      {open && (
        <div ref={panelRef} style={{
          position: "absolute", bottom: "calc(100% + 8px)", right: 0,
          minWidth: 200, maxHeight: 280, overflowY: "auto",
          background: "var(--surface)",
          border: "1px solid var(--line-strong)",
          borderRadius: 10,
          boxShadow: "var(--shadow-3)",
          padding: 6,
          animation: "fadein 140ms ease-out",
        }}>
          <div style={{
            padding: "4px 8px 6px", fontSize: 10.5, fontWeight: 600,
            letterSpacing: "0.04em", color: "var(--ink-4)", textTransform: "uppercase",
          }}>Jump to page</div>
          {pages.map(p => {
            const isCurrent = p.idx === currentPage;
            return (
              <button key={p.idx}
                onClick={() => { onJumpToPage && onJumpToPage(p.idx); setOpen(false); }}
                style={{
                  display: "flex", alignItems: "center", justifyContent: "space-between",
                  width: "100%", padding: "6px 8px", borderRadius: 6,
                  background: isCurrent ? "color-mix(in oklch, var(--accent) 12%, var(--surface))" : "transparent",
                  color: isCurrent ? "var(--accent)" : "var(--ink)",
                  fontSize: 12.5, fontWeight: isCurrent ? 600 : 500,
                  textAlign: "left",
                }}
                onMouseEnter={(e) => { if (!isCurrent) e.currentTarget.style.background = "var(--inset)"; }}
                onMouseLeave={(e) => { if (!isCurrent) e.currentTarget.style.background = "transparent"; }}
              >
                <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
                  Page <span className="mono num">{p.idx}</span>
                  {isCurrent && <Icon name="check" size={10} style={{ color: "var(--accent)" }} />}
                </span>
                <span className="mono" style={{ fontSize: 11, color: "var(--ink-4)", whiteSpace: "nowrap" }}>{p.from}–{p.to}</span>
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
}

function BulkToast({ message }) {
  return (
    <div style={{
      position: "fixed", bottom: 78, left: "50%", transform: "translateX(-50%)",
      zIndex: 121,
      padding: "8px 14px", borderRadius: 999,
      background: "color-mix(in oklch, var(--accent) 92%, white)",
      color: "#fff", fontSize: 12, fontWeight: 500,
      boxShadow: "0 6px 16px rgba(0,0,0,0.18)",
      display: "inline-flex", alignItems: "center", gap: 6,
      animation: "bulkBarIn 180ms ease-out",
    }}>
      <Icon name="check" size={12} style={{ color: "#fff" }} />
      {message}
    </div>
  );
}

function AccountRow({ c, onOpen, selected = false, onToggle, orderedKeys = ["industry", "hq", "size", "lifecycle", "priority", "action"], gridTemplate, contactStats = null }) {
  const [hover, setHover] = useStateAcc(false);
  const renderCell = (key) => {
    if (key === "industry") return (
      <div key={key} style={{
        fontSize: 12.5, color: "var(--ink-2)", minWidth: 0,
        display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
        overflow: "hidden", textOverflow: "ellipsis",
        lineHeight: 1.3, overflowWrap: "anywhere",
      }}>{c.industry}</div>
    );
    if (key === "hq") return (
      <div key={key} style={{
        fontSize: 12.5, color: "var(--ink-2)", minWidth: 0,
        display: "flex", alignItems: "flex-start", gap: 4,
      }}>
        <Icon name="map-pin" size={11} style={{ color: "var(--ink-4)", flexShrink: 0, marginTop: 3 }} />
        <span style={{
          display: "flex", flexDirection: "column", minWidth: 0, lineHeight: 1.25,
        }}>
          <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{c.hqCity}</span>
          {c.api ? (
            <span className="mono" style={{ fontSize: 10.5, color: "var(--ink-4)" }}>
              {c.api.hq_country}
              {c.api.operatingCountryCount > 1 && (
                <span style={{ marginLeft: 6, color: "var(--ink-3)" }} title={`Operates in ${c.api.operatingCountryCount} countries · ${c.api.totalLocationsCount} locations`}>
                  +{c.api.operatingCountryCount - 1} {c.api.operatingCountryCount - 1 === 1 ? "country" : "countries"}
                </span>
              )}
            </span>
          ) : (
            <span style={{ fontSize: 10.5, color: "var(--ink-4)" }}>{c.hqCountry}</span>
          )}
        </span>
      </div>
    );
    if (key === "size") return (
      <div key={key} className="mono num" style={{ fontSize: 12, color: "var(--ink-2)" }}>{c.employeeRange}</div>
    );
    if (key === "lifecycle") return (
      <div key={key}><LifecyclePill stage={c.abm.lifecycleStage} /></div>
    );
    if (key === "priority") {
      /* Prefer the API's Executive-English tier (the relevant priority signal
         for this dataset); fall back to the legacy GTM-priority + score-ring
         for mock rows that don't have an API payload attached. */
      if (c.api) {
        const tier  = c.api.executive_english_target_tier || "low_priority";
        const tone  = window.TIER_TONE[tier]  || "neutral";
        const label = window.TIER_LABEL[tier] || tier;
        const score = c.api.executive_english_target_score || 0;
        return (
          <div key={key} style={{ display: "flex", alignItems: "center", gap: 8, minWidth: 0 }}>
            <ScoreRing value={Math.round(score * 10)} size={28} stroke={3} tone={tone === "neutral" ? "violet" : tone} />
            <div style={{ minWidth: 0, lineHeight: 1.15 }}>
              <Badge tone={tone} size="sm">{label}</Badge>
              <div className="mono num" style={{ fontSize: 10.5, color: "var(--ink-4)", marginTop: 2 }}>
                {score.toFixed(1)}<span style={{ color: "var(--ink-5)" }}>/10</span>
              </div>
            </div>
          </div>
        );
      }
      return (
        <div key={key} style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <ScoreRing value={c.abm.priorityScore} size={28} tone="violet" />
          <span style={{ fontSize: 11.5, color: "var(--ink-3)", textTransform: "capitalize" }}>{c.gtmPriority}</span>
        </div>
      );
    }
    if (key === "action") return (
      <div key={key} style={{ fontSize: 12, color: "var(--ink-2)", display: "flex", alignItems: "flex-start", gap: 6 }}>
        <Icon name="sparkles" size={11} style={{ color: "var(--accent)", flexShrink: 0, marginTop: 3 }} />
        <span style={{
          overflow: "hidden", textOverflow: "ellipsis",
          display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
          lineHeight: 1.4,
        }}>{c.abm.nextBestAction}</span>
      </div>
    );

    /* ─── extended columns (off by default; toggle in Edit columns) ─── */
    const muted = { fontSize: 12, color: "var(--ink-3)" };
    const mono  = { fontSize: 12, color: "var(--ink-2)" };
    const truncOne = { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" };
    const stop = (e) => e.stopPropagation();

    if (key === "domain") return (
      <div key={key} className="mono" style={{ ...mono, ...truncOne }} title={c.domain}>
        {c.domain || <span style={{ color: "var(--ink-5)" }}>—</span>}
      </div>
    );
    if (key === "website") {
      const url = c.api?.website || (c.domain ? `https://${c.domain}` : null);
      return (
        <div key={key} style={{ ...mono, ...truncOne }}>
          {url ? (
            <a href={url} target="_blank" rel="noopener noreferrer" onClick={(e) => {
              /* Preview iframes intercept clicks and try to load the
                 URL inline; many sites (LinkedIn, banks) refuse to be
                 framed (X-Frame-Options: DENY). Force a top-level
                 window.open so the link escapes the iframe. */
              e.stopPropagation();
              e.preventDefault();
              window.openExternalLink(url);
            }}
               style={{ color: "var(--accent)", display: "inline-flex", alignItems: "center", gap: 4, textDecoration: "none" }}>
              <Icon name="external-link" size={10} style={{ flexShrink: 0 }} />
              <span style={truncOne}>{url.replace(/^https?:\/\/(www\.)?/, "")}</span>
            </a>
          ) : <span style={{ color: "var(--ink-5)" }}>—</span>}
        </div>
      );
    }
    if (key === "linkedin") {
      const url = c.api?.linkedin_url;
      return (
        <div key={key} style={mono}>
          {url ? (
            <a href={url} target="_blank" rel="noopener noreferrer" onClick={(e) => {
              e.stopPropagation();
              e.preventDefault();
              window.openExternalLink(url);
            }}
               style={{ color: "var(--accent)", display: "inline-flex", alignItems: "center", gap: 4, textDecoration: "none" }}
               title={url}>
              <Icon name="linkedin" size={11} /> Open
            </a>
          ) : <span style={{ color: "var(--ink-5)" }}>—</span>}
        </div>
      );
    }
    if (key === "tagline") return (
      <div key={key} style={{
        ...muted, lineHeight: 1.35,
        display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
        overflow: "hidden", textOverflow: "ellipsis",
      }} title={c.api?.tagline || ""}>
        {c.api?.tagline || <span style={{ color: "var(--ink-5)" }}>—</span>}
      </div>
    );

    if (key === "employeeCount") return (
      <div key={key} className="mono num" style={mono}>
        {c.employeeCount ? c.employeeCount.toLocaleString() : <span style={{ color: "var(--ink-5)" }}>—</span>}
      </div>
    );
    if (key === "yearFounded") return (
      <div key={key} className="mono num" style={mono}>
        {c.yearFounded || <span style={{ color: "var(--ink-5)" }}>—</span>}
      </div>
    );
    if (key === "followerCount") return (
      <div key={key} className="mono num" style={mono}>
        {c.api?.follower_count ? c.api.follower_count.toLocaleString() : <span style={{ color: "var(--ink-5)" }}>—</span>}
      </div>
    );

    if (key === "hqAddress") return (
      <div key={key} style={{
        ...muted, lineHeight: 1.35,
        display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
        overflow: "hidden", textOverflow: "ellipsis",
      }} title={c.api?.hq_full_address || ""}>
        {c.api?.hq_full_address || <span style={{ color: "var(--ink-5)" }}>—</span>}
      </div>
    );
    if (key === "countryCount") {
      const n = c.api?.operatingCountryCount;
      return (
        <div key={key} className="mono num" style={mono} title={(c.api?.operatingCountries || []).join(", ")}>
          {n ? n : <span style={{ color: "var(--ink-5)" }}>—</span>}
        </div>
      );
    }
    if (key === "locationCount") return (
      <div key={key} className="mono num" style={mono}>
        {c.api?.totalLocationsCount ?? <span style={{ color: "var(--ink-5)" }}>—</span>}
      </div>
    );
    if (key === "spainLocations") return (
      <div key={key} className="mono num" style={mono}>
        {c.api?.spainLocationCount ?? <span style={{ color: "var(--ink-5)" }}>—</span>}
      </div>
    );
    if (key === "otherLocations") return (
      <div key={key} className="mono num" style={mono}>
        {c.api?.outsideSpainLocationCount ?? <span style={{ color: "var(--ink-5)" }}>—</span>}
      </div>
    );

    if (key === "englishRatio") {
      const ratio = c.api?.highestLanguageEnglishCountryRatio;
      const count = c.api?.highestLanguageEnglishCountryCount;
      if (ratio == null) return <div key={key} style={{ color: "var(--ink-5)" }}>—</div>;
      const pct = Math.round(ratio * 100);
      return (
        <div key={key} style={{ display: "flex", alignItems: "center", gap: 6, minWidth: 0 }}>
          <div style={{ width: 48, height: 4, background: "var(--inset)", borderRadius: 2, overflow: "hidden", flexShrink: 0 }}>
            <div style={{ width: pct + "%", height: "100%", background: "var(--accent)" }} />
          </div>
          <span className="mono num" style={{ fontSize: 11, color: "var(--ink-2)", whiteSpace: "nowrap" }}>{pct}%</span>
          {count != null && <span className="mono" style={{ fontSize: 10.5, color: "var(--ink-5)" }}>· {count}</span>}
        </div>
      );
    }
    if (key === "officialLangs") {
      const langs = c.api?.officialLanguages || [];
      if (!langs.length) return <div key={key} style={{ color: "var(--ink-5)" }}>—</div>;
      return (
        <div key={key} style={{ display: "flex", flexWrap: "wrap", gap: 4, alignContent: "center" }}>
          {langs.slice(0, 3).map(l => (
            <span key={l} className="mono" style={{
              padding: "1px 6px", borderRadius: 4, background: "var(--inset)",
              fontSize: 10.5, color: "var(--ink-2)", textTransform: "uppercase", letterSpacing: 0.3,
            }}>{l}</span>
          ))}
          {langs.length > 3 && <span style={{ fontSize: 10.5, color: "var(--ink-4)" }}>+{langs.length - 3}</span>}
        </div>
      );
    }

    if (key === "targetScore") {
      const s = c.api?.executive_english_target_score;
      if (s == null) return <div key={key} style={{ color: "var(--ink-5)" }}>—</div>;
      return (
        <div key={key} style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <ScoreRing value={Math.round(s * 10)} size={22} stroke={2.5} tone="violet" />
          <span className="mono num" style={{ fontSize: 11.5, color: "var(--ink-2)" }}>
            {s.toFixed(1)}<span style={{ color: "var(--ink-5)" }}>/10</span>
          </span>
        </div>
      );
    }
    if (key === "salesReason") return (
      <div key={key} style={{
        ...muted, lineHeight: 1.35,
        display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
        overflow: "hidden", textOverflow: "ellipsis",
      }} title={c.api?.executive_english_sales_reason || ""}>
        {c.api?.executive_english_sales_reason || <span style={{ color: "var(--ink-5)" }}>—</span>}
      </div>
    );

    if (key === "fitScore") return (
      <div key={key}><ScoreRing value={c.abm.fitScore} size={22} stroke={2.5} tone="green" /></div>
    );
    if (key === "intentScore") return (
      <div key={key}><ScoreRing value={c.abm.intentScore} size={22} stroke={2.5} tone="violet" /></div>
    );
    if (key === "relationshipScore") return (
      <div key={key}><ScoreRing value={c.abm.relationshipScore} size={22} stroke={2.5} tone="warm" /></div>
    );
    if (key === "priorityScore") return (
      <div key={key}><ScoreRing value={c.abm.priorityScore} size={22} stroke={2.5} tone="accent" /></div>
    );
    if (key === "stakeholders") return (
      <div key={key} className="mono num" style={mono}>
        {c.abm.stakeholdersCount}
        <span style={{ color: "var(--ink-5)" }}> ({c.abm.connectedStakeholdersCount})</span>
      </div>
    );
    if (key === "openOpps") return (
      <div key={key} className="mono num" style={mono}>
        {c.abm.openOpportunitiesCount || <span style={{ color: "var(--ink-5)" }}>—</span>}
      </div>
    );
    if (key === "lastEngagement") return (
      <div key={key} style={muted}>
        {c.abm.lastEngagementAt ? window.fmtRelative(c.abm.lastEngagementAt) : <span style={{ color: "var(--ink-5)" }}>Never</span>}
      </div>
    );

    if (key === "specialties") {
      const spec = c.specialties || [];
      if (!spec.length) return <div key={key} style={{ color: "var(--ink-5)" }}>—</div>;
      return (
        <div key={key} style={{ display: "flex", flexWrap: "wrap", gap: 4, alignContent: "center" }} title={spec.join(", ")}>
          {spec.slice(0, 3).map(s => (
            <span key={s} style={{
              padding: "1px 6px", borderRadius: 4, background: "var(--inset)",
              fontSize: 10.5, color: "var(--ink-2)",
              maxWidth: 80, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
            }}>{s}</span>
          ))}
          {spec.length > 3 && <span style={{ fontSize: 10.5, color: "var(--ink-4)" }}>+{spec.length - 3}</span>}
        </div>
      );
    }
    if (key === "tags") {
      const tags = c.tags || [];
      if (!tags.length) return <div key={key} style={{ color: "var(--ink-5)" }}>—</div>;
      return (
        <div key={key} style={{ display: "flex", flexWrap: "wrap", gap: 4, alignContent: "center" }}>
          {tags.slice(0, 3).map(t => <Badge key={t} tone="neutral" size="sm">{t}</Badge>)}
          {tags.length > 3 && <span style={{ fontSize: 10.5, color: "var(--ink-4)" }}>+{tags.length - 3}</span>}
        </div>
      );
    }
    if (key === "updated") return (
      <div key={key} style={muted}>
        {c.api?.updatedAt ? window.fmtRelative(c.api.updatedAt) : <span style={{ color: "var(--ink-5)" }}>—</span>}
      </div>
    );

    /* Live contact counts (derived per render from window.CONTACTS). */
    const stats = contactStats || { total: 0, requested: 0, accepted: 0 };
    if (key === "assocContacts") return (
      <div key={key} className="mono num" style={mono} title={`${stats.total} contact${stats.total === 1 ? "" : "s"} associated with this account`}>
        {stats.total > 0 ? stats.total : <span style={{ color: "var(--ink-5)" }}>0</span>}
      </div>
    );
    if (key === "connectionRequests") return (
      <div key={key} className="mono num" style={mono} title={`${stats.requested} pending LinkedIn invitation${stats.requested === 1 ? "" : "s"}`}>
        {stats.requested > 0 ? (
          <span style={{ display: "inline-flex", alignItems: "center", gap: 4 }}>
            <span style={{ width: 6, height: 6, borderRadius: 999, background: "var(--warm)" }} />
            {stats.requested}
          </span>
        ) : <span style={{ color: "var(--ink-5)" }}>0</span>}
      </div>
    );
    if (key === "connectedContacts") return (
      <div key={key} className="mono num" style={mono} title={`${stats.accepted} accepted connection${stats.accepted === 1 ? "" : "s"}`}>
        {stats.accepted > 0 ? (
          <span style={{ display: "inline-flex", alignItems: "center", gap: 4 }}>
            <Icon name="check" size={11} style={{ color: "var(--green)" }} />
            {stats.accepted}
          </span>
        ) : <span style={{ color: "var(--ink-5)" }}>0</span>}
      </div>
    );

    return null;
  };
  return (
    <button
      onClick={onOpen}
      data-account-row={c.id}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        display: "grid",
        gridTemplateColumns: gridTemplate || "36px 32px 1fr 130px 110px 90px 110px 100px 240px 40px",
        alignItems: "center",
        padding: "12px 16px",
        borderBottom: "1px solid var(--line)",
        background: selected ? "color-mix(in oklch, var(--accent) 7%, var(--surface))"
                            : (hover ? "var(--surface-2)" : "var(--surface)"),
        textAlign: "left", width: "100%",
        transition: "background 120ms",
      }}
    >
      <div onClick={(e) => { e.stopPropagation(); e.preventDefault(); onToggle && onToggle(); }}
           style={{ display: "flex", alignItems: "center", height: "100%" }}>
        <SelectionCheckbox state={selected ? "all" : "none"} onChange={onToggle} visible={hover || selected} />
      </div>
      <div style={{ position: "relative", width: 28, height: 28 }}>
        <CompanyLogo company={c} size={28} />
        {c.gtmPriority === "high" && (
          <span
            title="High priority (P0)"
            style={{
              position: "absolute", bottom: -4, left: -4,
              display: "inline-flex", alignItems: "center", justifyContent: "center",
              filter: "drop-shadow(0 1px 1.5px rgba(0,0,0,0.35)) drop-shadow(0 0 0.5px rgba(0,0,0,0.4))",
              pointerEvents: "none",
            }}
          >
            <Icon name="star" size={14} style={{ color: "#F5B400", fill: "#F5B400" }} />
          </span>
        )}
      </div>
      <div style={{ minWidth: 0, overflow: "hidden" }}>
        <div style={{
          display: "flex", alignItems: "center", gap: 6,
          fontSize: 13.5, fontWeight: 600, color: "var(--ink)", letterSpacing: "-0.005em",
          overflow: "hidden", textOverflow: "ellipsis",
          display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
          lineHeight: 1.3, overflowWrap: "anywhere",
        }}>{c.name}</div>
        <div style={{
          fontSize: 11.5, color: "var(--ink-3)",
          overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
        }} className="mono">{c.domain}</div>
      </div>
      {orderedKeys.map(renderCell)}
      <div style={{ opacity: hover ? 1 : 0, transition: "opacity 120ms" }}>
        <IconButton icon="more-horizontal" />
      </div>
    </button>
  );
}

/* ────────────────────────────────────────────────────────────────────────
   AccountQuickLookDrawer
   ─────────────────────────────────────────────────────────────────
   Side panel that pops in from the right when a row in the Accounts list
   is clicked. Shows the high-signal subset of the account record so the
   user can scan it without leaving the list, with a primary button to
   jump to the full account-detail page. */
function AccountQuickLookDrawer({ companyId, onClose, onOpenFull, onNavigate }) {
  const company = companyId ? window.getCompany(companyId) : null;
  if (!company) return (
    <window.SlideOver open={false} onClose={onClose} title="" />
  );
  const api = company.api;
  const stakeholders = window.CONTACTS.filter(c => c.companyId === company.id);
  const opps = window.OPPORTUNITIES
    .filter(o => o.companyId === company.id && o.stage !== "won" && o.stage !== "lost")
    .sort((a, b) => (b.value || 0) - (a.value || 0));
  const convs = window.CONVERSATIONS.filter(c => c.companyId === company.id);
  const stageLabel = (window.OPPORTUNITY_STAGES.find(s => s.id === "_") || {}); /* unused, but ensures import */

  const meta = [
    company.industry && company.industry !== "—" && company.industry,
    [company.hqCity, company.hqCountry].filter(Boolean).join(", "),
    company.employeeRange && company.employeeRange !== "—" && `${company.employeeRange} employees`,
  ].filter(Boolean).join(" · ");

  return (
    <window.SlideOver
      open={!!companyId}
      onClose={onClose}
      width={460}
      icon="building"
      title={
        <span style={{ display: "inline-flex", alignItems: "center", gap: 10, minWidth: 0 }}>
          <CompanyLogo company={company} size={26} />
          <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
            {company.name}
          </span>
        </span>
      }
      subtitle={meta || " "}
      footer={
        <>
          <Button kind="ghost" size="md" onClick={onClose}>Close</Button>
          {company.domain && (
            <Button size="md" icon="external"
              onClick={() => window.open(api?.website ? api.website : `https://${company.domain}`, "_blank")}>
              {company.domain}
            </Button>
          )}
          <Button kind="primary" size="md" iconRight="arrow-right"
            onClick={() => onOpenFull(company.id)}>
            Open full profile
          </Button>
        </>
      }
    >
      <div style={{ padding: 18, display: "flex", flexDirection: "column", gap: 16 }}>
        {/* Status pills row */}
        <div style={{ display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center" }}>
          <LifecyclePill stage={company.abm.lifecycleStage} />
          {company.gtmPriority === "high" && <Badge tone="warm" size="sm" icon="flag">P0 priority</Badge>}
          {company.gtmPriority === "medium" && <Badge tone="violet" size="sm">P1</Badge>}
          {api?.executive_english_target_tier && (
            <Badge tone={window.TIER_TONE[api.executive_english_target_tier] || "neutral"} size="sm" dot>
              {window.TIER_LABEL[api.executive_english_target_tier] || api.executive_english_target_tier}
            </Badge>
          )}
        </div>

        {company.description && (
          <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.55 }}>
            {company.description}
          </div>
        )}

        {/* ABM score rings */}
        <div style={{
          display: "grid",
          gridTemplateColumns: "repeat(4, minmax(0, 1fr))",
          gap: 10,
          padding: "14px 12px",
          background: "var(--inset)",
          border: "1px solid var(--line)",
          borderRadius: 10,
        }}>
          <QLScore label="Priority"     value={company.abm.priorityScore}     tone="warm" />
          <QLScore label="Fit"          value={company.abm.fitScore}          tone="green" />
          <QLScore label="Intent"       value={company.abm.intentScore}       tone="violet" />
          <QLScore label="Relationship" value={company.abm.relationshipScore} tone="blue" />
        </div>

        {/* Firmographics */}
        <QLSection title="Firmographics">
          <QLRow label="Industry"   value={company.industry || "—"} />
          <QLRow label="HQ"         value={[company.hqCity, company.hqCountry].filter(Boolean).join(", ") || "—"} />
          <QLRow label="Headcount"  value={company.employeeCount ? `${company.employeeCount.toLocaleString()} (${company.employeeRange})` : (company.employeeRange || "—")} />
          {company.yearFounded && <QLRow label="Founded" value={String(company.yearFounded)} />}
          {api?.follower_count && <QLRow label="LinkedIn followers" value={api.follower_count.toLocaleString()} />}
          <QLRow label="Owner"
            value={(() => {
              const u = window.getUser(company.owner);
              return u ? (
                <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
                  <Avatar name={u.name} size={18} />
                  <span>{u.name}</span>
                </span>
              ) : "—";
            })()} />
        </QLSection>

        {/* Tags / tech / specialties */}
        {(company.tags?.length > 0 || company.specialties?.length > 0 || company.technologies?.length > 0) && (
          <QLSection title="Tags & signals">
            <div style={{ display: "flex", flexWrap: "wrap", gap: 5 }}>
              {company.tags?.map(t => <Badge key={"t-" + t} tone="neutral" size="sm">{t}</Badge>)}
              {company.specialties?.slice(0, 5).map(s => <Badge key={"s-" + s} tone="violet" size="sm">{s}</Badge>)}
              {company.technologies?.slice(0, 4).map(t => <Badge key={"x-" + t} tone="blue" size="sm">{t}</Badge>)}
            </div>
          </QLSection>
        )}

        {/* Next best action */}
        {company.abm.nextBestAction && (
          <div style={{
            padding: "12px 14px",
            background: "linear-gradient(180deg, color-mix(in oklch, var(--accent) 6%, var(--surface)), var(--surface))",
            border: "1px solid color-mix(in oklch, var(--accent) 18%, var(--line))",
            borderRadius: 10,
            display: "flex", gap: 10, alignItems: "flex-start",
          }}>
            <Icon name="sparkles" size={14} style={{ color: "var(--accent)", marginTop: 2, flexShrink: 0 }} />
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: 11, color: "var(--ink-3)", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.04em", marginBottom: 3 }}>
                Next best action
              </div>
              <div style={{ fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.5 }}>
                {company.abm.nextBestAction}
              </div>
            </div>
          </div>
        )}

        {/* Stakeholders mini-list */}
        {stakeholders.length > 0 && (
          <QLSection title={`Stakeholders (${stakeholders.length})`}>
            <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
              {stakeholders.slice(0, 5).map(c => (
                <button key={c.id}
                  onClick={() => { onClose && onClose(); onNavigate && onNavigate("contact-detail", { contactId: c.id }); }}
                  style={{
                    display: "flex", gap: 10, alignItems: "center",
                    padding: "6px 4px", textAlign: "left", width: "100%",
                    borderRadius: 6,
                  }}>
                  <Avatar name={`${c.firstName} ${c.lastName}`} src={c.profilePictureUrl} size={28} />
                  <div style={{ minWidth: 0, flex: 1 }}>
                    <div style={{ fontSize: 13, color: "var(--ink)", fontWeight: 600,
                      overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      {c.firstName} {c.lastName}
                    </div>
                    <div style={{ fontSize: 11.5, color: "var(--ink-3)",
                      overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      {c.position || c.headline}
                    </div>
                  </div>
                  {c.linkedinStatus === "accepted" && <Badge tone="green" dot size="sm">Connected</Badge>}
                  {c.linkedinStatus === "pending_sent" && <Badge tone="warm" dot size="sm">Pending</Badge>}
                </button>
              ))}
              {stakeholders.length > 5 && (
                <div style={{ fontSize: 11.5, color: "var(--ink-3)", paddingLeft: 4 }}>
                  +{stakeholders.length - 5} more — open full profile to see all
                </div>
              )}
            </div>
          </QLSection>
        )}

        {/* Open opportunities */}
        {opps.length > 0 && (
          <QLSection title={`Open opportunities (${opps.length})`}>
            <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
              {opps.slice(0, 3).map(o => {
                const stage = window.OPPORTUNITY_STAGES.find(s => s.id === o.stage);
                return (
                  <div key={o.id} style={{
                    padding: "10px 12px",
                    border: "1px solid var(--line)",
                    borderRadius: 8,
                    background: "var(--surface)",
                  }}>
                    <div style={{ display: "flex", justifyContent: "space-between", gap: 8, alignItems: "flex-start" }}>
                      <div style={{ fontSize: 12.5, fontWeight: 600, color: "var(--ink)", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>
                        {o.name}
                      </div>
                      <Badge tone={stage?.tone || "neutral"} size="sm">{stage?.label || o.stage}</Badge>
                    </div>
                    <div style={{ display: "flex", gap: 10, marginTop: 6, fontSize: 11.5, color: "var(--ink-3)" }}>
                      <span className="mono num">{window.fmtCurrency(o.value, o.currency)}</span>
                      <span>·</span>
                      <span>{Math.round((o.probability || 0) * 100)}% likely</span>
                      {o.closeDate && <><span>·</span><span>closes {window.fmtDate(o.closeDate)}</span></>}
                    </div>
                  </div>
                );
              })}
              {opps.length > 3 && (
                <div style={{ fontSize: 11.5, color: "var(--ink-3)", paddingLeft: 4 }}>
                  +{opps.length - 3} more
                </div>
              )}
            </div>
          </QLSection>
        )}

        {/* Recent activity */}
        {convs.length > 0 && (
          <QLSection title="Activity">
            <QLRow label="Conversations" value={`${convs.length} thread${convs.length === 1 ? "" : "s"}`} />
            {company.abm.lastEngagementAt && (
              <QLRow label="Last touch" value={<span className="mono">{window.fmtRelative(company.abm.lastEngagementAt)}</span>} />
            )}
          </QLSection>
        )}
      </div>
    </window.SlideOver>
  );
}

function QLScore({ label, value, tone }) {
  return (
    <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 6 }}>
      <ScoreRing value={value} size={44} stroke={4} tone={tone} />
      <div style={{ fontSize: 10.5, color: "var(--ink-3)", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.04em" }}>
        {label}
      </div>
    </div>
  );
}

function QLSection({ title, children }) {
  return (
    <div>
      <div style={{
        fontSize: 11, fontWeight: 600, color: "var(--ink-3)",
        textTransform: "uppercase", letterSpacing: "0.04em",
        marginBottom: 8,
      }}>{title}</div>
      <div>{children}</div>
    </div>
  );
}

function QLRow({ label, value }) {
  return (
    <div style={{
      display: "flex", justifyContent: "space-between", gap: 10,
      padding: "6px 0", fontSize: 12.5, color: "var(--ink-2)",
      borderBottom: "1px dashed var(--line)",
    }}>
      <span style={{ color: "var(--ink-3)" }}>{label}</span>
      <span style={{ color: "var(--ink)", textAlign: "right" }}>{value}</span>
    </div>
  );
}

/* ─────────────────────────────────────────────────────────────────
   AdvancedFilterModal — compound-query builder for the Accounts list.
   Renders a stack of clauses, each with a [Field] → [Op] → [Value]
   triplet. Clauses AND together; deleting all of them clears the
   filter. Field types drive which operators show up and which value
   editor renders (text input / number / between / enum chips / array
   tokens / date picker / unary).
   ───────────────────────────────────────────────────────────────── */
function AdvancedFilterModal({ open, onClose, clauses, onChange, fieldCatalog }) {
  /* Local working copy so cancelling discards in-flight edits. */
  const [draft, setDraft] = useStateAcc([]);
  React.useEffect(() => {
    if (open) {
      setDraft((clauses || []).map(c => ({ ...c })));
    }
  }, [open]);

  const nextId = () => "af-" + Math.random().toString(36).slice(2, 9);
  const addClause = () => setDraft(d => [...d, { id: nextId(), field: null, op: null, value: null }]);
  const removeClause = (id) => setDraft(d => d.filter(c => c.id !== id));
  const updateClause = (id, patch) => setDraft(d => d.map(c => c.id === id ? { ...c, ...patch } : c));

  const apply = () => {
    /* Drop unconfigured clauses on apply so we never persist empty
       rules. Unary ops don't need a value; everything else requires
       a non-empty value (string with content, number, ≥1 chip, etc.). */
    const cleaned = draft.filter(c => {
      if (!c.field || !c.op) return false;
      const def = fieldCatalog.find(f => f.id === c.field);
      if (!def) return false;
      const opDef = (AF_OPS[def.type] || []).find(o => o.id === c.op);
      if (opDef?.unary) return true;
      if (opDef?.pairValue) {
        const v = c.value;
        if (!v) return false;
        const a = Array.isArray(v) ? v[0] : v.from;
        const b = Array.isArray(v) ? v[1] : v.to;
        return a !== "" && a != null && b !== "" && b != null;
      }
      if (opDef?.multiValue) return Array.isArray(c.value) && c.value.length > 0;
      return c.value !== "" && c.value !== null && c.value !== undefined;
    });
    onChange(cleaned);
    onClose();
  };
  const clearAll = () => { setDraft([]); };
  const isPristine = JSON.stringify(draft) === JSON.stringify(clauses || []);

  return (
    <window.Modal
      open={open} onClose={onClose}
      icon="filter"
      title="Advanced filter"
      subtitle="Build compound rules. All conditions must match (AND)."
      width={760}
      footer={<>
        <span style={{ fontSize: 11.5, color: "var(--ink-4)", marginRight: "auto" }}>
          {draft.length === 0 ? "No conditions" : `${draft.length} condition${draft.length === 1 ? "" : "s"}`}
        </span>
        <Button kind="ghost" onClick={clearAll} disabled={draft.length === 0}>Clear all</Button>
        <Button kind="ghost" onClick={onClose}>Cancel</Button>
        <Button kind="primary" onClick={apply} disabled={isPristine && draft.length === 0}>Apply filter</Button>
      </>}
    >
      <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
        {draft.length === 0 && (
          <div style={{
            padding: "28px 16px", textAlign: "center",
            border: "1px dashed var(--line)", borderRadius: 10,
            background: "var(--surface-2)",
            color: "var(--ink-3)", fontSize: 12.5,
          }}>
            No conditions yet. Add one below to start filtering accounts.
          </div>
        )}
        {draft.map((clause, idx) => (
          <AFClauseRow
            key={clause.id}
            clause={clause}
            index={idx}
            fieldCatalog={fieldCatalog}
            onUpdate={(patch) => updateClause(clause.id, patch)}
            onRemove={() => removeClause(clause.id)}
          />
        ))}
        <div>
          <button
            onClick={addClause}
            style={{
              display: "inline-flex", alignItems: "center", gap: 6,
              padding: "6px 10px", borderRadius: 7,
              fontSize: 12.5, fontWeight: 500,
              color: "var(--accent)", background: "transparent",
              border: "1px dashed color-mix(in oklch, var(--accent) 50%, var(--line))",
            }}
            onMouseEnter={(e) => { e.currentTarget.style.background = "color-mix(in oklch, var(--accent) 8%, transparent)"; }}
            onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
          >
            <Icon name="plus" size={11} /> Add condition
          </button>
        </div>
      </div>
    </window.Modal>
  );
}

function AFClauseRow({ clause, index, fieldCatalog, onUpdate, onRemove }) {
  const def = fieldCatalog.find(f => f.id === clause.field);
  const opOptions = def ? (AF_OPS[def.type] || []) : [];
  const opDef = opOptions.find(o => o.id === clause.op);

  /* Reset op + value when the field changes so we never carry an op
     incompatible with the new field type. */
  const onSetField = (fieldId) => {
    const f = fieldCatalog.find(x => x.id === fieldId);
    if (!f) return;
    const firstOp = (AF_OPS[f.type] || [])[0]?.id;
    onUpdate({ field: fieldId, op: firstOp, value: null });
  };
  const onSetOp = (opId) => {
    const o = opOptions.find(x => x.id === opId);
    /* Convert value when switching to/from multi-value or unary. */
    let next = clause.value;
    if (o?.unary) next = null;
    else if (o?.multiValue) next = Array.isArray(clause.value) ? clause.value : (clause.value ? [clause.value] : []);
    else if (o?.pairValue) next = clause.value && (clause.value.from != null || (Array.isArray(clause.value) && clause.value.length)) ? clause.value : { from: "", to: "" };
    else next = Array.isArray(clause.value) ? (clause.value[0] || "") : (clause.value ?? "");
    onUpdate({ op: opId, value: next });
  };

  return (
    <div style={{
      display: "grid",
      gridTemplateColumns: "auto 1fr 1fr 1.6fr auto",
      gap: 8, alignItems: "center",
      padding: 8, borderRadius: 9,
      background: "var(--surface)",
      border: "1px solid var(--line)",
    }}>
      <span style={{
        fontSize: 10.5, fontWeight: 600, color: "var(--ink-4)",
        textTransform: "uppercase", letterSpacing: "0.04em",
        minWidth: 26, textAlign: "center",
      }}>{index === 0 ? "Where" : "And"}</span>

      {/* Field picker (grouped) */}
      <AFFieldPicker
        value={clause.field}
        onChange={onSetField}
        fieldCatalog={fieldCatalog}
      />

      {/* Op picker (depends on field type) */}
      <select
        value={clause.op || ""}
        onChange={(e) => onSetOp(e.target.value)}
        disabled={!def}
        style={{
          height: 32, padding: "0 8px", borderRadius: 7,
          border: "1px solid var(--line-strong)", background: "var(--surface)",
          fontSize: 12.5, color: "var(--ink)",
        }}
      >
        {opOptions.map(o => <option key={o.id} value={o.id}>{o.label}</option>)}
      </select>

      {/* Value editor (depends on op + type) */}
      <div style={{ minWidth: 0 }}>
        <AFValueEditor clause={clause} def={def} opDef={opDef} onUpdate={onUpdate} />
      </div>

      <button
        onClick={onRemove}
        title="Remove condition"
        style={{
          width: 28, height: 28, borderRadius: 7,
          display: "inline-flex", alignItems: "center", justifyContent: "center",
          color: "var(--ink-4)", background: "transparent",
        }}
        onMouseEnter={(e) => { e.currentTarget.style.background = "var(--inset)"; e.currentTarget.style.color = "var(--ink-2)"; }}
        onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; e.currentTarget.style.color = "var(--ink-4)"; }}
      >
        <Icon name="trash" size={12} />
      </button>
    </div>
  );
}

function AFFieldPicker({ value, onChange, fieldCatalog }) {
  const [open, setOpen] = useStateAcc(false);
  const [q, setQ] = useStateAcc("");
  const triggerRef = React.useRef(null);
  const panelRef = React.useRef(null);
  const selected = fieldCatalog.find(f => f.id === value);
  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => {
      if (panelRef.current && panelRef.current.contains(e.target)) return;
      if (triggerRef.current && triggerRef.current.contains(e.target)) return;
      setOpen(false);
    };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onDoc);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);

  const filtered = q
    ? fieldCatalog.filter(f => f.label.toLowerCase().includes(q.toLowerCase()))
    : fieldCatalog;
  /* Group by `group` for the dropdown, preserving catalog order. */
  const groups = [];
  for (const f of filtered) {
    let g = groups.find(x => x.name === f.group);
    if (!g) { g = { name: f.group, items: [] }; groups.push(g); }
    g.items.push(f);
  }

  return (
    <div style={{ position: "relative" }}>
      <button
        ref={triggerRef}
        onClick={() => setOpen(o => !o)}
        style={{
          width: "100%", height: 32, padding: "0 8px", borderRadius: 7,
          border: "1px solid var(--line-strong)", background: "var(--surface)",
          fontSize: 12.5, color: selected ? "var(--ink)" : "var(--ink-4)",
          display: "inline-flex", alignItems: "center", justifyContent: "space-between",
          gap: 6,
        }}
      >
        <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
          {selected ? selected.label : "Choose a field…"}
        </span>
        <Icon name="chevron-down" size={11} style={{ color: "var(--ink-4)", flexShrink: 0 }} />
      </button>
      {open && (
        <div ref={panelRef} style={{
          position: "absolute", top: "calc(100% + 4px)", left: 0,
          zIndex: 9999,
          width: 260, maxHeight: 320, overflowY: "auto",
          background: "var(--surface)",
          border: "1px solid var(--line-strong)",
          borderRadius: 10, boxShadow: "var(--shadow-3)",
          padding: 6,
        }}>
          <div style={{ padding: "4px 6px 6px" }}>
            <input
              autoFocus
              value={q}
              onChange={(e) => setQ(e.target.value)}
              placeholder="Search fields…"
              style={{
                width: "100%", height: 28, padding: "0 8px", borderRadius: 6,
                border: "1px solid var(--line)", background: "var(--surface-2)",
                fontSize: 12, color: "var(--ink)",
              }}
            />
          </div>
          {groups.map(g => (
            <div key={g.name}>
              <div style={{
                padding: "4px 8px 2px", fontSize: 10, fontWeight: 600,
                color: "var(--ink-4)", textTransform: "uppercase", letterSpacing: "0.04em",
              }}>{g.name}</div>
              {g.items.map(f => {
                const isSel = f.id === value;
                return (
                  <button key={f.id}
                    onClick={() => { onChange(f.id); setOpen(false); setQ(""); }}
                    style={{
                      display: "flex", alignItems: "center", justifyContent: "space-between",
                      width: "100%", padding: "6px 8px", borderRadius: 6,
                      background: isSel ? "color-mix(in oklch, var(--accent) 12%, var(--surface))" : "transparent",
                      color: isSel ? "var(--accent)" : "var(--ink)",
                      fontSize: 12.5, fontWeight: isSel ? 600 : 500, textAlign: "left",
                    }}
                    onMouseEnter={(e) => { if (!isSel) e.currentTarget.style.background = "var(--inset)"; }}
                    onMouseLeave={(e) => { if (!isSel) e.currentTarget.style.background = "transparent"; }}
                  >
                    <span>{f.label}</span>
                    {isSel && <Icon name="check" size={10} />}
                  </button>
                );
              })}
            </div>
          ))}
          {filtered.length === 0 && (
            <div style={{ padding: "10px 8px", fontSize: 12, color: "var(--ink-4)", textAlign: "center" }}>
              No fields match "{q}".
            </div>
          )}
        </div>
      )}
    </div>
  );
}

function AFValueEditor({ clause, def, opDef, onUpdate }) {
  if (!def || !opDef) {
    return <div style={{ height: 32, fontSize: 12, color: "var(--ink-4)", display: "flex", alignItems: "center" }}>—</div>;
  }
  if (opDef.unary) {
    return <div style={{ height: 32, fontSize: 12, color: "var(--ink-4)", display: "flex", alignItems: "center" }}>—</div>;
  }
  const baseStyle = {
    width: "100%", height: 32, padding: "0 8px", borderRadius: 7,
    border: "1px solid var(--line-strong)", background: "var(--surface)",
    fontSize: 12.5, color: "var(--ink)",
  };

  if (def.type === "number") {
    if (opDef.pairValue) {
      const v = clause.value || { from: "", to: "" };
      const from = Array.isArray(v) ? v[0] : v.from;
      const to   = Array.isArray(v) ? v[1] : v.to;
      return (
        <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
          <input type="number" value={from ?? ""} placeholder="from"
            onChange={(e) => onUpdate({ value: { from: e.target.value, to } })}
            style={baseStyle} />
          <span style={{ color: "var(--ink-4)", fontSize: 11 }}>and</span>
          <input type="number" value={to ?? ""} placeholder="to"
            onChange={(e) => onUpdate({ value: { from, to: e.target.value } })}
            style={baseStyle} />
        </div>
      );
    }
    return (
      <input type="number" value={clause.value ?? ""} placeholder="value"
        onChange={(e) => onUpdate({ value: e.target.value })}
        style={baseStyle} />
    );
  }

  if (def.type === "date") {
    if (opDef.id === "in_last_days") {
      return (
        <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <input type="number" min={1} value={clause.value ?? ""} placeholder="7"
            onChange={(e) => onUpdate({ value: e.target.value })}
            style={{ ...baseStyle, width: 80 }} />
          <span style={{ color: "var(--ink-3)", fontSize: 12 }}>days</span>
        </div>
      );
    }
    return (
      <input type="date" value={clause.value ?? ""}
        onChange={(e) => onUpdate({ value: e.target.value })}
        style={baseStyle} />
    );
  }

  if (def.type === "enum") {
    /* Multi-select chip picker via a small dropdown. */
    return (
      <AFEnumChips
        options={def.options || []}
        renderOption={def.renderOption || ((v) => v)}
        value={Array.isArray(clause.value) ? clause.value : (clause.value ? [clause.value] : [])}
        onChange={(arr) => onUpdate({ value: arr })}
      />
    );
  }

  if (def.type === "array") {
    if (opDef.id === "contains_text") {
      return (
        <input type="text" value={clause.value ?? ""} placeholder="match any item…"
          onChange={(e) => onUpdate({ value: e.target.value })}
          style={baseStyle} />
      );
    }
    return (
      <AFTokenInput
        value={Array.isArray(clause.value) ? clause.value : []}
        onChange={(arr) => onUpdate({ value: arr })}
        placeholder="press Enter to add…"
      />
    );
  }

  /* text */
  return (
    <input type="text" value={clause.value ?? ""} placeholder="value"
      onChange={(e) => onUpdate({ value: e.target.value })}
      style={baseStyle} />
  );
}

function AFEnumChips({ options, value, onChange, renderOption }) {
  const [open, setOpen] = useStateAcc(false);
  const triggerRef = React.useRef(null);
  const panelRef = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => {
      if (panelRef.current && panelRef.current.contains(e.target)) return;
      if (triggerRef.current && triggerRef.current.contains(e.target)) return;
      setOpen(false);
    };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);
  const toggle = (v) => onChange(value.includes(v) ? value.filter(x => x !== v) : [...value, v]);
  return (
    <div style={{ position: "relative" }}>
      <button
        ref={triggerRef}
        onClick={() => setOpen(o => !o)}
        style={{
          width: "100%", minHeight: 32, padding: "4px 8px", borderRadius: 7,
          border: "1px solid var(--line-strong)", background: "var(--surface)",
          fontSize: 12.5, color: "var(--ink)",
          display: "flex", alignItems: "center", gap: 4, flexWrap: "wrap",
          textAlign: "left",
        }}
      >
        {value.length === 0 ? (
          <span style={{ color: "var(--ink-4)" }}>Select…</span>
        ) : (
          value.map(v => (
            <span key={v} style={{
              display: "inline-flex", alignItems: "center", gap: 3,
              padding: "1px 4px 1px 6px", borderRadius: 4,
              background: "color-mix(in oklch, var(--accent) 12%, var(--surface))",
              fontSize: 11, color: "var(--accent)",
            }}
            onClick={(e) => { e.stopPropagation(); toggle(v); }}
            >
              {renderOption(v)}
              <Icon name="x" size={8} />
            </span>
          ))
        )}
        <span style={{ flex: 1 }} />
        <Icon name="chevron-down" size={10} style={{ color: "var(--ink-4)" }} />
      </button>
      {open && (
        <div ref={panelRef} style={{
          position: "absolute", top: "calc(100% + 4px)", left: 0, zIndex: 9999,
          minWidth: "100%", maxHeight: 240, overflowY: "auto",
          background: "var(--surface)", border: "1px solid var(--line-strong)",
          borderRadius: 9, boxShadow: "var(--shadow-3)", padding: 4,
        }}>
          {options.length === 0 && (
            <div style={{ padding: "8px 10px", fontSize: 12, color: "var(--ink-4)" }}>
              No options available
            </div>
          )}
          {options.map(opt => {
            const on = value.includes(opt);
            return (
              <button key={opt}
                onClick={() => toggle(opt)}
                style={{
                  display: "flex", alignItems: "center", gap: 6,
                  width: "100%", padding: "5px 8px", borderRadius: 5,
                  background: on ? "color-mix(in oklch, var(--accent) 12%, var(--surface))" : "transparent",
                  color: on ? "var(--accent)" : "var(--ink)",
                  fontSize: 12.5, textAlign: "left",
                }}
                onMouseEnter={(e) => { if (!on) e.currentTarget.style.background = "var(--inset)"; }}
                onMouseLeave={(e) => { if (!on) e.currentTarget.style.background = "transparent"; }}
              >
                <span style={{
                  width: 14, height: 14, borderRadius: 3,
                  border: `1.5px solid ${on ? "var(--accent)" : "var(--line-strong)"}`,
                  background: on ? "var(--accent)" : "var(--surface)",
                  display: "inline-flex", alignItems: "center", justifyContent: "center",
                  flexShrink: 0,
                }}>{on && <Icon name="check" size={8} style={{ color: "#fff" }} />}</span>
                <span>{renderOption(opt)}</span>
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
}

function AFTokenInput({ value, onChange, placeholder }) {
  const [draft, setDraft] = useStateAcc("");
  const commit = () => {
    const v = draft.trim();
    if (!v) return;
    if (!value.includes(v)) onChange([...value, v]);
    setDraft("");
  };
  return (
    <div style={{
      display: "flex", alignItems: "center", gap: 4, flexWrap: "wrap",
      minHeight: 32, padding: "3px 6px", borderRadius: 7,
      border: "1px solid var(--line-strong)", background: "var(--surface)",
    }}>
      {value.map(v => (
        <span key={v} style={{
          display: "inline-flex", alignItems: "center", gap: 3,
          padding: "1px 4px 1px 6px", borderRadius: 4,
          background: "color-mix(in oklch, var(--accent) 12%, var(--surface))",
          fontSize: 11, color: "var(--accent)",
        }}>
          {v}
          <button onClick={() => onChange(value.filter(x => x !== v))}
            style={{ color: "var(--accent)", display: "inline-flex" }}>
            <Icon name="x" size={8} />
          </button>
        </span>
      ))}
      <input
        value={draft}
        onChange={(e) => setDraft(e.target.value)}
        onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); commit(); } if (e.key === "Backspace" && !draft && value.length) { onChange(value.slice(0, -1)); } }}
        onBlur={commit}
        placeholder={value.length ? "" : placeholder}
        style={{
          flex: 1, minWidth: 80, height: 24, padding: "0 4px",
          border: "none", background: "transparent", fontSize: 12.5, color: "var(--ink)",
          outline: "none",
        }}
      />
    </div>
  );
}

window.PageAccounts = PageAccounts;
window.BulkActionBar = BulkActionBar;
window.BulkToast = BulkToast;
window.ColumnHeader = ColumnHeader;
window.EditColumnsButton = EditColumnsButton;
window.SaveViewButton = SaveViewButton;
window.MyViewsTab = MyViewsTab;
window.FilterChips = FilterChips;
window.PaginationPill = PaginationPill;
window.describeViewState = describeViewState;
