/* Contacts list view — matches the feature surface of the Accounts page:
   sort/group/edit-columns/save-view/filter-chips/infinite-scroll/pagination-pill. */

const { useState: useStateCN } = React;

function PageContacts({ onNavigate }) {
  const apiTotals = window.useApiTotals ? window.useApiTotals() : null;
  const [filter, setFilter] = useStateCN("all");
  /* "Find people" → currently opens a minimal "Add contact" modal that
     POSTs to the live /contacts endpoint. (Discovery search isn't backed
     by an endpoint yet; this is the working subset.) */
  const [addOpen, setAddOpen] = useStateCN(false);
  const [extraContacts, setExtraContacts] = useStateCN([]);
  const [search, setSearch] = useStateCN("");
  const [sortBy, setSortBy] = useStateCN("engagement"); /* engagement | relationship | recent | stale | name */
  const [groupBy, setGroupBy] = useStateCN("none");     /* none | account | seniority | department | status */

  /* Multi-selection */
  const [selectedIds, setSelectedIds] = useStateCN(() => new Set());
  const [bulkToast, setBulkToast] = useStateCN(null);
  const [peekId, setPeekId] = useStateCN(null); /* contact 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 n = new Set(prev); ids.forEach(i => n.add(i)); return n; });
  const deselectMany = (ids) => setSelectedIds(prev => { const n = new Set(prev); ids.forEach(i => n.delete(i)); return n; });
  const clearSelection = () => setSelectedIds(new Set());
  const flashToast = (msg) => { setBulkToast(msg); setTimeout(() => setBulkToast(null), 2400); };

  /* Per-column filters */
  const [columnFilters, setColumnFilters] = useStateCN({
    companyId: [], seniority: [], department: [], linkedinStatus: [],
  });
  const setColumnFilter = (key, values) => setColumnFilters(prev => ({ ...prev, [key]: values }));
  const clearAllColumnFilters = () => setColumnFilters({
    companyId: [], seniority: [], department: [], linkedinStatus: [],
  });
  const activeColumnFilterCount = Object.values(columnFilters).reduce((n, arr) => n + (arr.length ? 1 : 0), 0);

  /* Column visibility + order — Name/avatar are required; everything
     else can be toggled in the "Edit columns" popover. Default visible
     set stays the same; the new columns are opt-in. */
  const ALL_COLUMNS = [
    /* Defaults visible */
    { key: "headline",   label: "Headline"   },
    { key: "account",    label: "Account"    },
    { key: "seniority",  label: "Seniority"  },
    { key: "linkedin",   label: "LinkedIn"   },
    { key: "engagement", label: "Engagement" },
    /* Position */
    { key: "position",   label: "Position"   },
    { key: "department", label: "Department" },
    { key: "tenure",     label: "Tenure"     },
    /* Identity / contact */
    { key: "email",          label: "Primary email" },
    { key: "linkedinUrl",    label: "LinkedIn URL" },
    { key: "publicId",       label: "LinkedIn handle" },
    { key: "companyWebsite", label: "Company website" },
    /* Status flags */
    { key: "verified",    label: "Verified" },
    { key: "premium",     label: "Premium" },
    { key: "openToWork",  label: "Open to work" },
    { key: "hiring",      label: "Hiring" },
    { key: "influencer",  label: "Influencer" },
    { key: "composeType", label: "Compose option" },
    /* Location */
    { key: "location",        label: "Location" },
    { key: "locationCountry", label: "Country" },
    /* Network strength */
    { key: "connectionsCount", label: "Connections" },
    { key: "followerCount",    label: "Followers" },
    /* Skills / languages */
    { key: "topSkills",  label: "Top skills" },
    { key: "languages",  label: "Languages" },
    /* Profile depth (counts) */
    { key: "experienceCount", label: "Experience #" },
    { key: "educationCount",  label: "Education #" },
    { key: "skillCount",      label: "Skills #" },
    { key: "certCount",       label: "Certs #" },
    /* About */
    { key: "about",       label: "About" },
    /* ABM (mock-derived for completeness) */
    { key: "relationship",  label: "Relationship" },
    { key: "intent",        label: "Intent" },
    { key: "persona",       label: "Persona" },
    { key: "buyingRole",    label: "Buying role" },
    { key: "lastSignal",    label: "Last signal" },
    { key: "nextAction",    label: "Next action" },
    /* Metadata */
    { key: "registeredAt", label: "Joined LinkedIn" },
    { key: "updatedAt",    label: "Updated" },
  ];
  const DEFAULT_VISIBLE = { headline: true, account: true, seniority: true, linkedin: true, engagement: true };
  const DEFAULT_ORDER = ALL_COLUMNS.map(c => c.key);
  const [visibleCols, setVisibleCols] = useStateCN(DEFAULT_VISIBLE);
  const [columnOrder, setColumnOrder] = useStateCN(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;

  /* Saved views */
  const [savedViews, setSavedViews] = useStateCN([]);
  const [activeViewId, setActiveViewId] = useStateCN(null);
  const captureViewState = () => ({
    filter, search, sortBy, groupBy,
    columnFilters: { ...columnFilters },
    visibleCols:   { ...visibleCols },
    columnOrder:   columnOrder.slice(),
  });
  const saveCurrentView = (name) => {
    const id = "vw-" + 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 || "engagement"); setGroupBy(s.groupBy || "none");
    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);
  };

  /* Filter pipeline — locally-added contacts come from POST /contacts and
     appear at the top until a live refresh picks them up. */
  let list = extraContacts.concat(window.CONTACTS);
  if (search) {
    const s = search.toLowerCase();
    list = list.filter(c => (c.firstName + " " + c.lastName + " " + (c.headline || "")).toLowerCase().includes(s));
  }
  if (filter === "connected")       list = list.filter(c => c.linkedinStatus === "accepted");
  if (filter === "pending")         list = list.filter(c => c.linkedinStatus === "pending_sent");
  if (filter === "decision_makers") list = list.filter(c => ["c_level", "vp", "director"].includes(c.seniority));
  if (columnFilters.companyId.length)      list = list.filter(c => columnFilters.companyId.includes(c.companyId));
  if (columnFilters.seniority.length)      list = list.filter(c => columnFilters.seniority.includes(c.seniority));
  if (columnFilters.department.length)     list = list.filter(c => columnFilters.department.includes(c.department));
  if (columnFilters.linkedinStatus.length) list = list.filter(c => columnFilters.linkedinStatus.includes(c.linkedinStatus));

  /* Sort */
  if (sortBy === "engagement")   list.sort((a, b) => (b.abm.engagementScore || 0) - (a.abm.engagementScore || 0));
  if (sortBy === "relationship") list.sort((a, b) => (b.abm.relationshipStrength || 0) - (a.abm.relationshipStrength || 0));
  if (sortBy === "recent")       list.sort((a, b) => (b.abm.intentScore || 0) - (a.abm.intentScore || 0));
  if (sortBy === "stale")        list.sort((a, b) => ((a.abm.engagementScore || 0) + (a.abm.relationshipStrength || 0))
                                                  - ((b.abm.engagementScore || 0) + (b.abm.relationshipStrength || 0)));
  if (sortBy === "name")         list.sort((a, b) => `${a.firstName} ${a.lastName}`.localeCompare(`${b.firstName} ${b.lastName}`));

  /* Option pools — derived from the unfiltered universe so options don't vanish. */
  /* Option pools — derived from the unfiltered universe (live + static +
     locally-added) so options don't vanish once you pick one. */
  const universeContacts = extraContacts.concat(window.CONTACTS);
  const filterOptions = {
    companyId: Array.from(new Set(universeContacts.map(c => c.companyId))).filter(Boolean)
                 .sort((a, b) => (getCompany(a)?.name || "").localeCompare(getCompany(b)?.name || "")),
    seniority: ["c_level", "vp", "director", "head", "manager", "individual_contributor", "owner", "unknown"]
                 .filter(v => universeContacts.some(c => c.seniority === v)),
    department: Array.from(new Set(universeContacts.map(c => c.department))).filter(Boolean).sort(),
    linkedinStatus: ["accepted", "pending_sent", "pending_received", "declined", "removed", "withdrawn", "none", "unknown"]
                 .filter(v => universeContacts.some(c => c.linkedinStatus === v)),
  };
  const STATUS_LABEL = {
    accepted: "Connected", pending_sent: "Pending", pending_received: "Received",
    declined: "Declined", removed: "Removed", none: "No relation", withdrawn: "Withdrawn", unknown: "Unknown",
  };
  const DEPT_LABEL_FULL = window.DEPT_LABEL || {};

  /* Tab counts off the unfiltered universe */
  const counts = {
    all: universeContacts.length,
    connected: universeContacts.filter(c => c.linkedinStatus === "accepted").length,
    pending:   universeContacts.filter(c => c.linkedinStatus === "pending_sent").length,
    decision_makers: universeContacts.filter(c => ["c_level", "vp", "director"].includes(c.seniority)).length,
  };

  /* Infinite-scroll pagination */
  const PAGE_SIZE = 30;
  const [visibleCount, setVisibleCount] = useStateCN(PAGE_SIZE);
  const filterSignature = JSON.stringify([filter, search, sortBy, groupBy, columnFilters]);
  React.useEffect(() => { setVisibleCount(PAGE_SIZE); }, [filterSignature]);

  /* Mix server total in for the unfiltered case so the pill + sentinel
     reflect the true DB ceiling (~2 910 contacts), not just what's
     loaded into memory. */
  const filtersActive = filter !== "all" || search.trim() !== "" ||
    Object.values(columnFilters || {}).some(v => Array.isArray(v) ? v.length : v);
  const serverTotal = filtersActive ? null : (apiTotals?.contacts ?? 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;

  /* Continuous prefetch — see comment in page-accounts.jsx. */
  React.useEffect(() => {
    if (filtersActive) return;
    const target = (apiTotals?.contacts ?? Infinity);
    const HEADROOM_PAGES = 3;
    const ahead = list.length - visibleCount;
    if (ahead < HEADROOM_PAGES * PAGE_SIZE && list.length < target) {
      window.BusinessTeamBoot?.loadMoreContactsFromLive();
    }
  }, [visibleCount, list.length, filtersActive, apiTotals?.contacts]);

  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;
        /* Cap at loaded records — see comment in page-accounts.jsx for
           the runaway-observer bug this avoids. */
        setVisibleCount(v => Math.min(v + PAGE_SIZE, list.length));
        /* Keep 2 pages of headroom loaded ahead so the user never sees
           a visible pause between pages. */
        const headroom = 2 * PAGE_SIZE;
        if (!filtersActive
            && list.length < (apiTotals?.contacts ?? Infinity)
            && (visibleCount + headroom) >= list.length) {
          window.BusinessTeamBoot?.loadMoreContactsFromLive();
        }
      });
    }, { rootMargin: "600px 0px" });
    io.observe(el);
    return () => io.disconnect();
  }, [hasMore, totalCount, list.length, visibleCount, filtersActive, apiTotals?.contacts, filterSignature]);

  /* Inline loading flag for jump-to-far-page fetches. */
  const [paging, setPaging] = useStateCN(false);
  const scrollToTop = () => {
    const scroller = document.querySelector(".page-scroll");
    if (scroller) scroller.scrollTo({ top: 0, behavior: "smooth" });
    else window.scrollTo({ top: 0, behavior: "smooth" });
  };
  const jumpToPage = async (pageIndex) => {
    const lastRow = pageIndex * PAGE_SIZE;
    const firstRowIdx = (pageIndex - 1) * PAGE_SIZE;
    /* If the jump exceeds what's loaded and we're not filtering, pull
       enough pages from the API first. */
    if (!filtersActive && lastRow > window.CONTACTS.length) {
      setPaging(true);
      try { await window.BusinessTeamBoot?.ensureContactsLoaded(lastRow); }
      finally { setPaging(false); }
    }
    setVisibleCount(v => Math.max(v, Math.min(lastRow, totalCount)));
    requestAnimationFrame(() => requestAnimationFrame(() => {
      const scroller = document.querySelector(".page-scroll");
      const rows = document.querySelectorAll("[data-contact-row]");
      const target = rows[firstRowIdx];
      if (!scroller || !target) return;
      const scrollerRect = scroller.getBoundingClientRect();
      const targetRect   = target.getBoundingClientRect();
      const delta = targetRect.top - scrollerRect.top - 80;
      scroller.scrollTo({ top: scroller.scrollTop + delta, behavior: "smooth" });
    }));
  };

  /* Custom labels/render for FilterChips & MyViews/SaveView describer */
  const filterChipLabels = {
    companyId: "Account", seniority: "Seniority",
    department: "Department", linkedinStatus: "LinkedIn",
  };
  const renderChipValue = (key, v) => {
    if (key === "companyId")      return getCompany(v)?.name || v;
    if (key === "seniority")      return SENIORITY_LABEL[v] || v;
    if (key === "department")     return DEPT_LABEL_FULL[v] || v;
    if (key === "linkedinStatus") return STATUS_LABEL[v] || v;
    return v;
  };
  const describe = (s) => {
    if (!s) return "";
    const parts = [];
    if (s.filter && s.filter !== "all") parts.push({ connected: "Connected", pending: "Pending", decision_makers: "Decision makers" }[s.filter] || s.filter);
    if (s.sortBy && s.sortBy !== "engagement") parts.push(`Sort: ${({ relationship: "Relationship", 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(" · ");
  };

  return (
    <>
      <PageHeader
        title="Contacts"
        actions={<>
          <Button kind="secondary" size="md" icon="user-plus" onClick={() => setAddOpen(true)}>Add contact</Button>
          <Button kind="primary"   size="md" icon="send">Start campaign</Button>
        </>}
        tabs={<>
          <Tab active={filter === "all"} onClick={() => setFilter("all")} badge={counts.all}>All</Tab>
          <Tab active={filter === "connected"} onClick={() => setFilter("connected")} badge={counts.connected}>Connected</Tab>
          <Tab active={filter === "pending"} onClick={() => setFilter("pending")} badge={counts.pending}>Pending</Tab>
          <Tab active={filter === "decision_makers"} onClick={() => setFilter("decision_makers")} badge={counts.decision_makers}>Decision makers</Tab>
          <window.MyViewsTab
            views={savedViews}
            activeViewId={activeViewId}
            onApply={applyView}
            onDelete={deleteView}
            describe={describe}
          />
        </>}
      />

      <div className="page-pad" style={{ paddingTop: 20, display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
        <TextField icon="search" placeholder="Filter…" value={search} onChange={(e) => setSearch(e.target.value)} style={{ width: "min(180px, 100%)" }} />
        <window.Menu
          trigger={<Button kind="ghost" size="md" icon="sort">Sort: {{ engagement: "Engagement", relationship: "Relationship", recent: "Recent activity", stale: "Lack of activity", name: "Name (A–Z)" }[sortBy]}</Button>}
          items={[
            { icon: "trending-up", label: "Engagement",       active: sortBy === "engagement",   onClick: () => setSortBy("engagement") },
            { icon: "user",        label: "Relationship",     active: sortBy === "relationship", onClick: () => setSortBy("relationship") },
            { icon: "clock",       label: "Recent activity",  active: sortBy === "recent",       onClick: () => setSortBy("recent") },
            { icon: "alert",       label: "Lack of activity", active: sortBy === "stale",        onClick: () => setSortBy("stale") },
            { 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", account: "Account", seniority: "Seniority", department: "Department", status: "LinkedIn status" })[groupBy]}`}</Button>}
          items={[
            { icon: "circle",      label: "No grouping",    active: groupBy === "none",       onClick: () => setGroupBy("none") },
            { icon: "building",    label: "Account",        active: groupBy === "account",    onClick: () => setGroupBy("account") },
            { icon: "user",        label: "Seniority",      active: groupBy === "seniority",  onClick: () => setGroupBy("seniority") },
            { icon: "briefcase",   label: "Department",     active: groupBy === "department", onClick: () => setGroupBy("department") },
            { icon: "linkedin",    label: "LinkedIn status",active: groupBy === "status",     onClick: () => setGroupBy("status") },
          ]}
        />
        <window.EditColumnsButton
          allColumns={ALL_COLUMNS}
          visibleCols={visibleCols}
          columnOrder={columnOrder}
          onToggle={toggleCol}
          onReorder={reorderCols}
          onReset={resetCols}
          hiddenCount={hiddenColCount}
        />
        <window.SaveViewButton
          views={savedViews}
          activeViewId={activeViewId}
          onSave={saveCurrentView}
          onApply={applyView}
          onDelete={deleteView}
          describe={describe}
        />
        {activeColumnFilterCount > 0 && (
          <window.FilterChips
            columnFilters={columnFilters}
            filterOptions={filterOptions}
            onClear={(key) => setColumnFilter(key, [])}
            onClearAll={clearAllColumnFilters}
            labels={filterChipLabels}
            renderValue={renderChipValue}
          />
        )}
        <div style={{ flex: 1 }} />
      </div>

      <div className="page-pad-y" style={{ paddingTop: 16 }}>
        <div className="scroll-x">
          <ContactsTable
            list={visibleList}
            groupBy={groupBy}
            onOpen={(c) => setPeekId(c.id)}
            columnFilters={columnFilters}
            filterOptions={filterOptions}
            statusLabel={STATUS_LABEL}
            deptLabel={DEPT_LABEL_FULL}
            onColumnFilter={setColumnFilter}
            selectedIds={selectedIds}
            onToggleSelected={toggleSelected}
            onSelectMany={selectMany}
            onDeselectMany={deselectMany}
            visibleCols={visibleCols}
            columnOrder={columnOrder}
          />
        </div>
        <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 contacts…</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} contact{totalCount === 1 ? "" : "s"}
            </div>
          </div>
        ) : null}
      </div>

      {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 contacts from server…</span>
        </div>
      )}
      <window.PaginationPill
        shown={shownCount}
        total={totalCount}
        displayTotal={apiTotals?.contacts}
        onScrollTop={scrollToTop}
        hidden={selectedIds.size > 0}
        pageSize={PAGE_SIZE}
        onJumpToPage={jumpToPage}
      />

      <window.BulkActionBar
        count={selectedIds.size}
        onClear={clearSelection}
        onAction={(action) => {
          const ids = Array.from(selectedIds);
          flashToast({
            "assign":   `Owner reassigned for ${ids.length} contact${ids.length === 1 ? "" : "s"}`,
            "priority": `Priority updated for ${ids.length} contact${ids.length === 1 ? "" : "s"}`,
            "tag":      `Tag added to ${ids.length} contact${ids.length === 1 ? "" : "s"}`,
            "campaign": `Added ${ids.length} contact${ids.length === 1 ? "" : "s"} to campaign`,
            "export":   `Exported ${ids.length} contact${ids.length === 1 ? "" : "s"} to CSV`,
            "delete":   `Removed ${ids.length} contact${ids.length === 1 ? "" : "s"}`,
          }[action] || "Done");
          if (action === "delete") clearSelection();
        }}
      />
      {bulkToast && <window.BulkToast message={bulkToast} />}

      <ContactQuickLookDrawer
        contactId={peekId}
        onClose={() => setPeekId(null)}
        onOpenFull={(id) => { setPeekId(null); onNavigate("contact-detail", { contactId: id }); }}
        onNavigate={onNavigate}
      />

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

function NewContactModal({ open, onClose, onCreate }) {
  /* Minimum-viable contact create form. The backend requires
     `linkedinContactId` and accepts everything else as optional. We
     guide the user to paste a LinkedIn profile URL and parse the public
     identifier out of it — but also accept a raw id. The full
     LinkedIn-side enrichment isn't triggered server-side today, so the
     row persists with exactly the fields we send. */
  const [linkedinUrl, setLinkedinUrl] = useStateCN("");
  const [firstName, setFirstName]     = useStateCN("");
  const [lastName, setLastName]       = useStateCN("");
  const [headline, setHeadline]       = useStateCN("");
  const [companyId, setCompanyId]     = useStateCN("");
  const [companyName, setCompanyName] = useStateCN("");
  const [position, setPosition]       = useStateCN("");
  const [submitting, setSubmitting]   = useStateCN(false);
  const [submitError, setSubmitError] = useStateCN(null);

  React.useEffect(() => {
    if (open) {
      setLinkedinUrl(""); setFirstName(""); setLastName(""); setHeadline("");
      setCompanyId(""); setCompanyName(""); setPosition("");
      setSubmitting(false); setSubmitError(null);
    }
  }, [open]);

  /* Derive a contact id. Order of preference:
     1. A linkedin URN-style id pasted directly (e.g. "ACoAAA...").
     2. The publicIdentifier slug parsed out of a linkedin URL.
     3. A locally-generated slug from first+last+timestamp. */
  const deriveLinkedinContactId = () => {
    const raw = linkedinUrl.trim();
    if (!raw) {
      const slug = `${firstName}-${lastName}`.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/(^-|-$)/g, "");
      return `manual-${slug || "contact"}-${Date.now()}`;
    }
    /* Already a URN-style id. */
    if (/^[A-Za-z0-9_-]{15,}$/.test(raw) && !raw.includes("/")) return raw;
    /* linkedin.com/in/<slug>/... */
    const m = raw.match(/linkedin\.com\/in\/([^/?#]+)/i);
    if (m) return m[1];
    /* Fall back to the entire trimmed string. */
    return raw;
  };

  const handle = async () => {
    if (!firstName.trim() || submitting) return;
    setSubmitting(true); setSubmitError(null);
    const linkedinContactId = deriveLinkedinContactId();
    const trimmedHeadline = headline.trim();
    const trimmedPosition = position.trim();
    const trimmedCompany  = companyName.trim();
    /* Build the payload using the backend's camelCase contact schema. */
    const live_payload = {
      linkedinContactId,
      firstName: firstName.trim(),
      lastName: lastName.trim() || undefined,
      headline: trimmedHeadline || undefined,
      linkedinUrl: linkedinUrl.trim() || undefined,
      currentPosition: trimmedPosition || trimmedCompany || companyId
        ? [{
            position: trimmedPosition || trimmedHeadline || undefined,
            companyId: companyId.trim() || undefined,
            companyName: trimmedCompany || undefined,
          }]
        : undefined,
    };
    /* Strip undefined keys so we don't send sparse JSON. */
    for (const k of Object.keys(live_payload)) {
      if (live_payload[k] === undefined) delete live_payload[k];
    }

    try {
      const created = await window.ApiClient.createContact(live_payload);
      const adapted = created && (created.linkedinContactId || created.id)
        ? window.adaptApiContact(created)
        : window.adaptApiContact(live_payload);
      onCreate(adapted);
    } catch (err) {
      console.warn("[BusinessTeam] createContact failed, adding locally:", err && err.message);
      setSubmitError(err && err.message ? err.message : "Could not save to backend — added locally.");
      onCreate(window.adaptApiContact(live_payload));
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <window.Modal
      open={open} onClose={onClose}
      icon="user-plus"
      title="Add contact"
      subtitle="Saves to the live contacts API. Paste a LinkedIn URL or a contact URN."
      footer={<>
        <Button kind="ghost" onClick={onClose} disabled={submitting}>Cancel</Button>
        <Button kind="primary" icon={submitting ? null : "plus"} onClick={handle} disabled={!firstName.trim() || submitting}>
          {submitting && <window.Spinner size={13} />}
          {submitting ? "Saving…" : "Create contact"}
        </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="LinkedIn profile URL or URN" span={2} optional>
          <window.FormInput
            autoFocus value={linkedinUrl}
            onChange={(e) => setLinkedinUrl(e.target.value)}
            placeholder="https://www.linkedin.com/in/jane-doe — or ACoAAA…"
          />
        </window.FormField>
        <window.FormField label="First name">
          <window.FormInput value={firstName} onChange={(e) => setFirstName(e.target.value)} placeholder="Jane" />
        </window.FormField>
        <window.FormField label="Last name" optional>
          <window.FormInput value={lastName} onChange={(e) => setLastName(e.target.value)} placeholder="Doe" />
        </window.FormField>
        <window.FormField label="Headline" span={2} optional>
          <window.FormInput value={headline} onChange={(e) => setHeadline(e.target.value)} placeholder="HR Director at Acme Group" />
        </window.FormField>
        <window.FormField label="Current company" optional>
          <window.FormInput value={companyName} onChange={(e) => setCompanyName(e.target.value)} placeholder="Acme Group" />
        </window.FormField>
        <window.FormField label="Position" optional>
          <window.FormInput value={position} onChange={(e) => setPosition(e.target.value)} placeholder="HR Director" />
        </window.FormField>
        <window.FormField label="Company id (optional)" span={2} optional>
          <window.FormInput
            value={companyId}
            onChange={(e) => setCompanyId(e.target.value)}
            placeholder="LinkedIn numeric company id, e.g. 9320564"
          />
        </window.FormField>
      </div>
    </window.Modal>
  );
}

function ContactsTable({
  list, onOpen, groupBy = "none",
  columnFilters = {}, filterOptions = {}, onColumnFilter,
  selectedIds = new Set(), onToggleSelected, onSelectMany, onDeselectMany,
  visibleCols, columnOrder, statusLabel = {}, deptLabel = {},
}) {
  /* Build the grid template from current visibility + order */
  const COL_DEFS = {
    /* defaults */
    headline:   { width: "minmax(220px, 320px)" },
    account:    { width: "minmax(150px, 200px)" },
    seniority:  { width: "110px" },
    linkedin:   { width: "140px" },
    engagement: { width: "140px" },
    /* position */
    position:   { width: "minmax(140px, 200px)" },
    department: { width: "140px" },
    tenure:     { width: "120px" },
    /* contact */
    email:          { width: "minmax(180px, 240px)" },
    linkedinUrl:    { width: "minmax(140px, 200px)" },
    publicId:       { width: "minmax(120px, 180px)" },
    companyWebsite: { width: "minmax(140px, 200px)" },
    /* flags */
    verified:    { width: "80px" },
    premium:     { width: "80px" },
    openToWork:  { width: "100px" },
    hiring:      { width: "80px" },
    influencer:  { width: "90px" },
    composeType: { width: "120px" },
    /* location */
    location:        { width: "minmax(140px, 200px)" },
    locationCountry: { width: "110px" },
    /* network */
    connectionsCount: { width: "110px" },
    followerCount:    { width: "100px" },
    /* skills */
    topSkills: { width: "minmax(160px, 240px)" },
    languages: { width: "minmax(140px, 200px)" },
    /* depth counts */
    experienceCount: { width: "100px" },
    educationCount:  { width: "100px" },
    skillCount:      { width: "90px" },
    certCount:       { width: "90px" },
    /* about */
    about: { width: "minmax(220px, 320px)" },
    /* abm */
    relationship: { width: "100px" },
    intent:       { width: "100px" },
    persona:      { width: "minmax(140px, 200px)" },
    buyingRole:   { width: "140px" },
    lastSignal:   { width: "minmax(180px, 260px)" },
    nextAction:   { width: "minmax(220px, 320px)" },
    /* metadata */
    registeredAt: { width: "120px" },
    updatedAt:    { width: "110px" },
  };
  const orderedKeys = (columnOrder || Object.keys(COL_DEFS)).filter(k => visibleCols[k] && COL_DEFS[k]);
  const dynCols = orderedKeys.map(k => COL_DEFS[k].width).join(" ");
  const gridTemplate = `36px 32px minmax(160px, 220px) ${dynCols} 40px`;

  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);
  };

  /* Group the rows when requested */
  const groupOf = (c) => {
    if (groupBy === "account")    return getCompany(c.companyId)?.name || "Unknown";
    if (groupBy === "seniority")  return SENIORITY_LABEL[c.seniority] || c.seniority;
    if (groupBy === "department") return deptLabel[c.department] || c.department || "Other";
    if (groupBy === "status")     return statusLabel[c.linkedinStatus] || c.linkedinStatus;
    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] }));
  })();

  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, color: "var(--ink-3)",
        textTransform: "uppercase", letterSpacing: "0.04em",
      }}>
        <div style={{ display: "flex", alignItems: "center" }}>
          <window.SelectionCheckbox
            state={headerState}
            onChange={toggleHeader}
            title={headerState === "all" ? "Deselect all" : "Select all on this page"}
          />
        </div>
        <div />
        <div>Name</div>
        {orderedKeys.map(key => {
          if (key === "headline") return <div key={key}>Headline</div>;
          if (key === "account") return (
            <window.ColumnHeader key={key} label="Account"
              options={filterOptions.companyId || []}
              renderOption={(id) => getCompany(id)?.name || id}
              selected={columnFilters.companyId || []}
              onChange={(v) => onColumnFilter && onColumnFilter("companyId", v)}
              searchable />
          );
          if (key === "seniority") return (
            <window.ColumnHeader key={key} label="Seniority"
              options={filterOptions.seniority || []}
              renderOption={(v) => SENIORITY_LABEL[v] || v}
              selected={columnFilters.seniority || []}
              onChange={(v) => onColumnFilter && onColumnFilter("seniority", v)} />
          );
          if (key === "linkedin") return (
            <window.ColumnHeader key={key} label="LinkedIn"
              options={filterOptions.linkedinStatus || []}
              renderOption={(v) => statusLabel[v] || v}
              selected={columnFilters.linkedinStatus || []}
              onChange={(v) => onColumnFilter && onColumnFilter("linkedinStatus", v)} />
          );
          if (key === "engagement") return <div key={key}>Engagement</div>;
          /* Static-label headers for new columns. */
          if (key === "position")         return <div key={key}>Position</div>;
          if (key === "department")       return <div key={key}>Department</div>;
          if (key === "tenure")           return <div key={key}>Tenure</div>;
          if (key === "email")            return <div key={key}>Email</div>;
          if (key === "linkedinUrl")      return <div key={key}>LinkedIn URL</div>;
          if (key === "publicId")         return <div key={key}>Handle</div>;
          if (key === "companyWebsite")   return <div key={key}>Company website</div>;
          if (key === "verified")         return <div key={key}>Verified</div>;
          if (key === "premium")          return <div key={key}>Premium</div>;
          if (key === "openToWork")       return <div key={key}>Open to work</div>;
          if (key === "hiring")           return <div key={key}>Hiring</div>;
          if (key === "influencer")       return <div key={key}>Influencer</div>;
          if (key === "composeType")      return <div key={key}>Compose option</div>;
          if (key === "location")         return <div key={key}>Location</div>;
          if (key === "locationCountry")  return <div key={key}>Country</div>;
          if (key === "connectionsCount") return <div key={key}>Connections</div>;
          if (key === "followerCount")    return <div key={key}>Followers</div>;
          if (key === "topSkills")        return <div key={key}>Top skills</div>;
          if (key === "languages")        return <div key={key}>Languages</div>;
          if (key === "experienceCount")  return <div key={key}>Exp.</div>;
          if (key === "educationCount")   return <div key={key}>Edu.</div>;
          if (key === "skillCount")       return <div key={key}>Skills</div>;
          if (key === "certCount")        return <div key={key}>Certs</div>;
          if (key === "about")            return <div key={key}>About</div>;
          if (key === "relationship")     return <div key={key}>Relationship</div>;
          if (key === "intent")           return <div key={key}>Intent</div>;
          if (key === "persona")          return <div key={key}>Persona</div>;
          if (key === "buyingRole")       return <div key={key}>Buying role</div>;
          if (key === "lastSignal")       return <div key={key}>Last signal</div>;
          if (key === "nextAction")       return <div key={key}>Next action</div>;
          if (key === "registeredAt")     return <div key={key}>Joined</div>;
          if (key === "updatedAt")        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",
                }}>
                  <window.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 => (
                  <ContactRow
                    key={c.id} c={c}
                    selected={selectedIds.has(c.id)}
                    onToggle={() => onToggleSelected && onToggleSelected(c.id)}
                    onOpen={() => onOpen(c)}
                    orderedKeys={orderedKeys}
                    gridTemplate={gridTemplate}
                    statusLabel={statusLabel}
                  />
                ))}
              </React.Fragment>
            );
          })
        : list.map(c => (
            <ContactRow
              key={c.id} c={c}
              selected={selectedIds.has(c.id)}
              onToggle={() => onToggleSelected && onToggleSelected(c.id)}
              onOpen={() => onOpen(c)}
              orderedKeys={orderedKeys}
              gridTemplate={gridTemplate}
              statusLabel={statusLabel}
            />
          ))}
    </Card>
  );
}

function ContactRow({ c, onOpen, selected = false, onToggle, orderedKeys = [], gridTemplate, statusLabel = {} }) {
  const [hover, setHover] = useStateCN(false);
  const company = getCompany(c.companyId);
  const statusTone = {
    accepted: "green", pending_sent: "warm", pending_received: "blue",
    declined: "red", removed: "red", none: "neutral", withdrawn: "neutral", unknown: "neutral",
  }[c.linkedinStatus];

  const renderCell = (key) => {
    if (key === "headline") return (
      <div key={key} style={{
        fontSize: 12, color: "var(--ink-2)", minWidth: 0,
        display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
        overflow: "hidden", textOverflow: "ellipsis",
        lineHeight: 1.35, overflowWrap: "anywhere",
      }}>{c.headline}</div>
    );
    if (key === "account") return (
      <div key={key} style={{ display: "flex", alignItems: "center", gap: 6, minWidth: 0 }}>
        <CompanyLogo company={company} size={18} />
        <span style={{
          fontSize: 12, color: "var(--ink-2)", minWidth: 0,
          display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
          overflow: "hidden", textOverflow: "ellipsis",
          lineHeight: 1.3, overflowWrap: "anywhere",
        }}>{company?.name}</span>
      </div>
    );
    if (key === "seniority") return (
      <div key={key}>
        <Badge tone={c.seniority === "c_level" ? "warm" : c.seniority === "director" || c.seniority === "vp" ? "violet" : "neutral"} size="sm">
          {SENIORITY_LABEL[c.seniority]}
        </Badge>
      </div>
    );
    if (key === "linkedin") return (
      <div key={key}>
        <Badge tone={statusTone} dot size="sm">{statusLabel[c.linkedinStatus] || c.linkedinStatus}</Badge>
      </div>
    );
    if (key === "engagement") return (
      <div key={key} style={{ display: "flex", alignItems: "center", gap: 8 }}>
        <ScoreRing value={c.abm.engagementScore} size={22} stroke={3} tone="violet" />
        <ScoreRing value={c.abm.relationshipStrength} size={22} stroke={3} tone="green" />
      </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 dash = <span style={{ color: "var(--ink-5)" }}>—</span>;
    const stop = (e) => e.stopPropagation();
    const flagBadge = (on, label, tone = "violet") => on ? (
      <Badge tone={tone} size="sm">{label}</Badge>
    ) : <span style={{ fontSize: 11, color: "var(--ink-5)" }}>—</span>;

    if (key === "position") return (
      <div key={key} style={{ ...mono, ...truncOne }} title={c.position}>{c.position || dash}</div>
    );
    if (key === "department") return (
      <div key={key} style={muted} title={c.department || ""}>
        {c.department ? (window.DEPT_LABEL?.[c.department] || c.department) : dash}
      </div>
    );
    if (key === "tenure") return (
      <div key={key} style={muted}>{c.tenure || dash}</div>
    );

    if (key === "email") {
      const primary = c.emails?.find(e => e?.email)?.email || null;
      return (
        <div key={key} style={{ ...mono, ...truncOne }} title={primary || ""}>
          {primary ? (
            <a href={`mailto:${primary}`} onClick={stop}
               style={{ color: "var(--accent)", textDecoration: "none", display: "inline-flex", alignItems: "center", gap: 4 }}>
              <Icon name="mail" size={11} style={{ flexShrink: 0 }} />
              <span style={truncOne}>{primary}</span>
            </a>
          ) : dash}
        </div>
      );
    }
    if (key === "linkedinUrl") {
      const url = c.linkedinUrl || c.raw?.linkedinUrl;
      return (
        <div key={key} style={mono}>
          {url ? (
            <a href={url} target="_blank" rel="noopener noreferrer" onClick={(e) => {
              /* Preview iframes intercept normal navigation and load
                 the URL inline, but LinkedIn refuses to be framed
                 (X-Frame-Options: DENY → ERR_BLOCKED_BY_RESPONSE).
                 Force a top-level window.open so the link escapes the
                 iframe and lands in a real tab. */
              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>
          ) : dash}
        </div>
      );
    }
    if (key === "publicId") return (
      <div key={key} className="mono" style={{ ...mono, ...truncOne }} title={c.publicIdentifier || ""}>
        {c.publicIdentifier ? `@${c.publicIdentifier}` : dash}
      </div>
    );
    if (key === "companyWebsite") {
      const site = c.companyWebsites?.[0];
      const url = site?.url || (site?.domain ? `https://${site.domain}` : null);
      return (
        <div key={key} style={{ ...mono, ...truncOne }}>
          {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" }}>
              <Icon name="external-link" size={10} style={{ flexShrink: 0 }} />
              <span style={truncOne}>{(site.domain || url).replace(/^https?:\/\/(www\.)?/, "")}</span>
            </a>
          ) : dash}
        </div>
      );
    }

    if (key === "verified")   return <div key={key}>{flagBadge(c.verified,   "Verified",   "blue")}</div>;
    if (key === "premium")    return <div key={key}>{flagBadge(c.premium,    "Premium",    "warm")}</div>;
    if (key === "openToWork") return <div key={key}>{flagBadge(c.openToWork, "Open",       "green")}</div>;
    if (key === "hiring")     return <div key={key}>{flagBadge(c.hiring,     "Hiring",     "green")}</div>;
    if (key === "influencer") return <div key={key}>{flagBadge(c.influencer, "Influencer", "violet")}</div>;
    if (key === "composeType") return (
      <div key={key} style={muted}>
        {c.composeOptionType ? (
          <Badge tone={c.composeOptionType === "PREMIUM_INMAIL" ? "warm" : "neutral"} size="sm">
            {c.composeOptionType === "PREMIUM_INMAIL" ? "InMail" :
             c.composeOptionType === "UPSELL"         ? "Upsell"  :
             c.composeOptionType}
          </Badge>
        ) : dash}
      </div>
    );

    if (key === "location") return (
      <div key={key} style={{ ...muted, ...truncOne }} title={c.location || ""}>{c.location || dash}</div>
    );
    if (key === "locationCountry") {
      const country = c.raw?.location?.parsed?.country || c.raw?.primaryLocale?.country;
      return <div key={key} className="mono" style={muted}>{country ? country.toUpperCase() : dash}</div>;
    }

    if (key === "connectionsCount") return (
      <div key={key} className="mono num" style={mono}>{c.connectionsCount ? c.connectionsCount.toLocaleString() : dash}</div>
    );
    if (key === "followerCount") return (
      <div key={key} className="mono num" style={mono}>{c.followerCount ? c.followerCount.toLocaleString() : dash}</div>
    );

    if (key === "topSkills") {
      const skills = c.topSkills || [];
      if (!skills.length) return <div key={key}>{dash}</div>;
      return (
        <div key={key} style={{ display: "flex", flexWrap: "wrap", gap: 4, alignContent: "center" }} title={skills.join(", ")}>
          {skills.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: 90, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
            }}>{s}</span>
          ))}
          {skills.length > 3 && <span style={{ fontSize: 10.5, color: "var(--ink-4)" }}>+{skills.length - 3}</span>}
        </div>
      );
    }
    if (key === "languages") {
      const langs = c.languagesRich?.length ? c.languagesRich : (c.languages || []).map(l => ({ name: l }));
      if (!langs.length) return <div key={key}>{dash}</div>;
      return (
        <div key={key} style={{ display: "flex", flexWrap: "wrap", gap: 4, alignContent: "center" }}
             title={langs.map(l => l.proficiency ? `${l.name} · ${l.proficiency}` : l.name).join(", ")}>
          {langs.slice(0, 3).map((l, i) => (
            <span key={i} style={{
              padding: "1px 6px", borderRadius: 4, background: "var(--inset)",
              fontSize: 10.5, color: "var(--ink-2)",
            }}>{l.name}</span>
          ))}
          {langs.length > 3 && <span style={{ fontSize: 10.5, color: "var(--ink-4)" }}>+{langs.length - 3}</span>}
        </div>
      );
    }

    if (key === "experienceCount") return <div key={key} className="mono num" style={mono}>{c.experience?.length ?? 0}</div>;
    if (key === "educationCount")  return <div key={key} className="mono num" style={mono}>{c.education?.length ?? 0}</div>;
    if (key === "skillCount")      return <div key={key} className="mono num" style={mono}>{c.skills?.length ?? 0}</div>;
    if (key === "certCount")       return <div key={key} className="mono num" style={mono}>{c.certifications?.length ?? 0}</div>;

    if (key === "about") return (
      <div key={key} style={{
        ...muted, lineHeight: 1.35,
        display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
        overflow: "hidden", textOverflow: "ellipsis",
      }} title={c.about || ""}>
        {c.about || dash}
      </div>
    );

    if (key === "relationship") return (
      <div key={key}><ScoreRing value={c.abm.relationshipStrength} size={22} stroke={2.5} tone="green" /></div>
    );
    if (key === "intent") return (
      <div key={key}><ScoreRing value={c.abm.intentScore} size={22} stroke={2.5} tone="warm" /></div>
    );
    if (key === "persona") return (
      <div key={key} style={{ ...muted, ...truncOne }} title={c.abm.persona || ""}>{c.abm.persona || dash}</div>
    );
    if (key === "buyingRole") return (
      <div key={key}>
        {c.abm.buyingCommitteeRole ? (
          <Badge tone={c.abm.buyingCommitteeRole === "decision_maker" ? "warm" : "neutral"} size="sm">
            {String(c.abm.buyingCommitteeRole).replace(/_/g, " ")}
          </Badge>
        ) : dash}
      </div>
    );
    if (key === "lastSignal") return (
      <div key={key} style={{
        ...muted, lineHeight: 1.35,
        display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
        overflow: "hidden", textOverflow: "ellipsis",
      }} title={c.abm.lastSignal || ""}>{c.abm.lastSignal || dash}</div>
    );
    if (key === "nextAction") return (
      <div key={key} style={{
        ...muted, lineHeight: 1.35,
        display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
        overflow: "hidden", textOverflow: "ellipsis",
      }} title={c.abm.recommendedNextAction || ""}>{c.abm.recommendedNextAction || dash}</div>
    );

    if (key === "registeredAt") return (
      <div key={key} style={muted}>{c.registeredAt ? window.fmtDate(c.registeredAt) : dash}</div>
    );
    if (key === "updatedAt") return (
      <div key={key} style={muted}>{c.updatedAt ? window.fmtRelative(c.updatedAt) : dash}</div>
    );

    return null;
  };

  return (
    <button
      onClick={onOpen}
      data-contact-row={c.id}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        display: "grid",
        gridTemplateColumns: gridTemplate,
        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 style={{ display: "flex", alignItems: "center", height: "100%" }}>
        <window.SelectionCheckbox
          state={selected ? "all" : "none"}
          onChange={onToggle}
          visible={hover || selected}
        />
      </div>
      <Avatar name={`${c.firstName} ${c.lastName}`} src={c.profilePictureUrl} size={28} />
      <div style={{ minWidth: 0, overflow: "hidden" }}>
        <div style={{
          fontSize: 13, fontWeight: 600, color: "var(--ink)",
          display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
          overflow: "hidden", textOverflow: "ellipsis",
          lineHeight: 1.3, overflowWrap: "anywhere",
        }}>{c.firstName} {c.lastName}</div>
        <div style={{
          fontSize: 11.5, color: "var(--ink-3)",
          overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
        }}>{c.location}</div>
      </div>
      {orderedKeys.map(renderCell)}
      <div style={{ opacity: hover ? 1 : 0, transition: "opacity 120ms" }}>
        <IconButton icon="more-horizontal" />
      </div>
    </button>
  );
}

/* ────────────────────────────────────────────────────────────────────────
   ContactQuickLookDrawer
   ─────────────────────────────────────────────────────────────────
   Side panel that pops in from the right when a contact row is clicked.
   Mirrors the AccountQuickLookDrawer pattern: high-signal subset of the
   contact profile so the user can scan without leaving the list, with a
   primary button to open the full contact-detail page. */
function ContactQuickLookDrawer({ contactId, onClose, onOpenFull, onNavigate }) {
  const contact = contactId ? window.getContact(contactId) : null;
  if (!contact) return (
    <window.SlideOver open={false} onClose={onClose} title="" />
  );
  const company = window.getCompany(contact.companyId);
  const raw = contact.raw;
  const conv = window.CONVERSATIONS.find(c => c.contactId === contact.id);

  const statusTone = ({
    accepted: "green", pending_sent: "warm", pending_received: "blue",
    declined: "red", removed: "red", none: "neutral", withdrawn: "neutral", unknown: "neutral",
  })[contact.linkedinStatus] || "neutral";
  const statusLabel = ({
    accepted: "Connected", pending_sent: "Pending", pending_received: "Received",
    declined: "Declined", removed: "Removed", none: "No relation", withdrawn: "Withdrawn", unknown: "Unknown",
  })[contact.linkedinStatus] || contact.linkedinStatus;

  return (
    <window.SlideOver
      open={!!contactId}
      onClose={onClose}
      width={460}
      icon="user"
      title={
        <span style={{ display: "inline-flex", alignItems: "center", gap: 10, minWidth: 0 }}>
          <Avatar name={`${contact.firstName} ${contact.lastName}`} src={contact.profilePictureUrl} size={28} />
          <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
            {contact.firstName} {contact.lastName}
          </span>
          {contact.verified && (
            <span title="Verified profile" style={{
              width: 16, height: 16, borderRadius: "50%",
              background: "var(--blue-soft, var(--inset))",
              color: "var(--blue, var(--accent))",
              display: "inline-flex", alignItems: "center", justifyContent: "center",
              flexShrink: 0,
            }}>
              <Icon name="check" size={11} />
            </span>
          )}
        </span>
      }
      subtitle={[contact.position, company?.name, contact.location].filter(Boolean).join(" · ") || " "}
      footer={
        <>
          <Button kind="ghost" size="md" onClick={onClose}>Close</Button>
          {contact.linkedinUrl && (
            <Button size="md" icon="linkedin"
              onClick={() => window.open(contact.linkedinUrl, "_blank")}>
              LinkedIn
            </Button>
          )}
          <Button kind="primary" size="md" iconRight="arrow-right"
            onClick={() => onOpenFull(contact.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" }}>
          <Badge tone={statusTone} dot size="sm">{statusLabel}</Badge>
          {contact.premium && <Badge tone="warm" size="sm">Premium</Badge>}
          {contact.openToWork && <Badge tone="green" size="sm" dot>Open to work</Badge>}
          {contact.hiring && <Badge tone="violet" size="sm" dot>Hiring</Badge>}
          {contact.influencer && <Badge tone="blue" size="sm">Influencer</Badge>}
          {contact.composeOptionType === "PREMIUM_INMAIL" && (
            <Badge tone="warm" size="sm">Premium InMail</Badge>
          )}
        </div>

        {contact.headline && (
          <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.5 }}>
            {contact.headline}
          </div>
        )}

        {/* Current role at company */}
        {company && (
          <button
            onClick={() => { onClose && onClose(); onNavigate && onNavigate("account-detail", { companyId: company.id }); }}
            style={{
              display: "flex", gap: 10, alignItems: "center",
              padding: "10px 12px",
              background: "var(--surface-2)",
              border: "1px solid var(--line)",
              borderRadius: 8, textAlign: "left", width: "100%",
            }}>
            <CompanyLogo company={company} size={32} />
            <div style={{ minWidth: 0, flex: 1 }}>
              <div style={{ fontSize: 13, fontWeight: 600, color: "var(--ink)",
                overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                {contact.position || "—"}
              </div>
              <div style={{ fontSize: 11.5, color: "var(--ink-3)",
                overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                {company.name}{contact.tenure ? ` · ${contact.tenure}` : ""}
              </div>
            </div>
            <Icon name="arrow-right" size={12} style={{ color: "var(--ink-3)", flexShrink: 0 }} />
          </button>
        )}

        {/* Relationship scores */}
        <div style={{
          padding: "14px 14px",
          background: "var(--inset)",
          border: "1px solid var(--line)",
          borderRadius: 10,
          display: "flex", flexDirection: "column", gap: 10,
        }}>
          <ScoreBar label="Relationship" value={contact.abm.relationshipStrength} tone="green" />
          <ScoreBar label="Engagement"   value={contact.abm.engagementScore}     tone="violet" />
          <ScoreBar label="Intent"       value={contact.abm.intentScore}         tone="warm" />
        </div>

        {/* Role in committee */}
        <CLSection title="Role in buying committee">
          <div style={{ display: "flex", gap: 6, alignItems: "center", flexWrap: "wrap" }}>
            <Badge tone="violet" icon="target">{contact.abm.persona}</Badge>
            <Badge tone="blue">{contact.abm.buyingCommitteeRole.replace(/_/g, " ")}</Badge>
            <Badge tone="neutral" size="sm">
              {window.DEPT_LABEL?.[contact.department] || contact.department} · {window.SENIORITY_LABEL?.[contact.seniority] || contact.seniority}
            </Badge>
          </div>
        </CLSection>

        {/* About */}
        {contact.about && (
          <CLSection title="About">
            <div style={{ fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.55,
              display: "-webkit-box", WebkitLineClamp: 5, WebkitBoxOrient: "vertical",
              overflow: "hidden" }}>
              {contact.about}
            </div>
          </CLSection>
        )}

        {/* Contact info */}
        {(contact.primaryEmail || contact.linkedinUrl || contact.connectionsCount) && (
          <CLSection title="Contact info">
            {contact.primaryEmail && (
              <CLRow label="Email" value={
                <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
                  <a href={`mailto:${contact.primaryEmail.email}`} style={{ color: "var(--ink)", fontWeight: 500 }}>
                    {contact.primaryEmail.email}
                  </a>
                  <StatusDot tone={contact.primaryEmail.status === "valid" ? "green" : contact.primaryEmail.status === "risky" ? "warm" : "neutral"} size={6} />
                </span>
              } />
            )}
            {contact.linkedinUrl && (
              <CLRow label="LinkedIn" value={
                <a href={contact.linkedinUrl} target="_blank" rel="noreferrer"
                   style={{ color: "var(--accent)", fontSize: 12 }}>
                  /{contact.publicIdentifier || contact.linkedinUrl.replace(/^https?:\/\/(www\.)?linkedin\.com\/in\//, "")}
                </a>
              } />
            )}
            {contact.connectionsCount && (
              <CLRow label="Connections" value={<span className="mono num">{contact.connectionsCount.toLocaleString()}</span>} />
            )}
            {contact.followerCount && (
              <CLRow label="Followers" value={<span className="mono num">{contact.followerCount.toLocaleString()}</span>} />
            )}
            {contact.locationCity && (
              <CLRow label="Location" value={[contact.locationCity, contact.locationCountry].filter(Boolean).join(", ")} />
            )}
          </CLSection>
        )}

        {/* Latest signal / next action */}
        {(contact.abm.lastSignal || contact.abm.recommendedNextAction) && (
          <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 }}>
              {contact.abm.lastSignal && (
                <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginBottom: 4 }}>
                  Latest signal · <span style={{ color: "var(--ink-2)" }}>{contact.abm.lastSignal}</span>
                </div>
              )}
              {contact.abm.recommendedNextAction && (
                <div style={{ fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.5 }}>
                  {contact.abm.recommendedNextAction}
                </div>
              )}
            </div>
          </div>
        )}

        {/* Top skills */}
        {contact.topSkills?.length > 0 && (
          <CLSection title="Top skills">
            <div style={{ display: "flex", flexWrap: "wrap", gap: 5 }}>
              {contact.topSkills.slice(0, 8).map(s => <Badge key={s} tone="neutral" size="sm">{s}</Badge>)}
            </div>
          </CLSection>
        )}

        {/* Languages */}
        {contact.languages?.length > 0 && (
          <CLSection title="Languages">
            <div style={{ display: "flex", flexWrap: "wrap", gap: 5 }}>
              {contact.languages.slice(0, 6).map((l, i) => <Badge key={i} tone="violet" size="sm">{l}</Badge>)}
            </div>
          </CLSection>
        )}

        {/* Latest experience preview */}
        {contact.experience?.length > 0 && (
          <CLSection title={`Experience (${contact.experience.length})`}>
            <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
              {contact.experience.slice(0, 3).map((e, i) => (
                <div key={i} style={{ display: "flex", gap: 10 }}>
                  <div style={{
                    width: 28, height: 28, borderRadius: 6, flexShrink: 0,
                    background: "var(--inset)", color: "var(--ink-3)",
                    display: "flex", alignItems: "center", justifyContent: "center",
                    overflow: "hidden",
                  }}>
                    {e.companyLogo
                      ? <img src={e.companyLogo} alt="" style={{ width: "100%", height: "100%", objectFit: "cover" }} />
                      : <Icon name="briefcase" size={12} />}
                  </div>
                  <div style={{ minWidth: 0, flex: 1 }}>
                    <div style={{ fontSize: 12.5, fontWeight: 600, color: "var(--ink)",
                      overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      {e.position}
                    </div>
                    <div style={{ fontSize: 11.5, color: "var(--ink-3)",
                      overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      {e.company}{e.duration ? ` · ${e.duration}` : ""}
                    </div>
                  </div>
                </div>
              ))}
              {contact.experience.length > 3 && (
                <div style={{ fontSize: 11.5, color: "var(--ink-3)" }}>
                  +{contact.experience.length - 3} more — open full profile
                </div>
              )}
            </div>
          </CLSection>
        )}

        {/* Conversation link */}
        {conv && (
          <button
            onClick={() => { onClose && onClose(); onNavigate && onNavigate("conversations", { conversationId: conv.id }); }}
            style={{
              display: "flex", gap: 10, alignItems: "center",
              padding: "10px 12px",
              background: "var(--surface-2)",
              border: "1px solid var(--line)",
              borderRadius: 8, textAlign: "left", width: "100%",
            }}>
            <div style={{
              width: 28, height: 28, borderRadius: 6,
              background: "var(--inset)", color: "var(--ink-2)",
              display: "flex", alignItems: "center", justifyContent: "center",
              flexShrink: 0,
            }}>
              <Icon name="messages" size={14} />
            </div>
            <div style={{ minWidth: 0, flex: 1 }}>
              <div style={{ fontSize: 12.5, fontWeight: 600, color: "var(--ink)" }}>
                Open conversation
              </div>
              <div style={{ fontSize: 11, color: "var(--ink-3)" }}>
                {conv.messages?.length || 0} message{(conv.messages?.length || 0) === 1 ? "" : "s"} · last activity {window.fmtRelative(conv.lastActivityAt)}
              </div>
            </div>
            <Icon name="arrow-right" size={12} style={{ color: "var(--ink-3)", flexShrink: 0 }} />
          </button>
        )}
      </div>
    </window.SlideOver>
  );
}

function CLSection({ 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 CLRow({ 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", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>{value}</span>
    </div>
  );
}

window.PageContacts = PageContacts;
