// Unified Pipeline screen — Notion-style view tabs (Kanban / Table / Calendar)
// All views share the same candidates state, so a card moved in one view stays moved in another.

const { useState: useStateU, useMemo: useMemoU } = React;

// === Calendar view (interview schedule) ===
function PipelineCalendar({ candidates }) {
  // Build a fake April 2026 month grid (Apr 24, 2026 is "today")
  const dayHeaders = ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"];
  // Apr 1 2026 is a Wednesday → 2 leading blanks (Mon, Tue)
  const firstWeekdayOffset = 2;
  const daysInMonth = 30;
  const cells = [];
  for (let i = 0; i < firstWeekdayOffset; i++) cells.push({ off: true, date: 31 - firstWeekdayOffset + i + 1 });
  for (let d = 1; d <= daysInMonth; d++) cells.push({ off: false, date: d });
  while (cells.length % 7) cells.push({ off: true, date: cells.length - daysInMonth - firstWeekdayOffset + 1 });

  // Distribute interview events deterministically across late April
  const interviewStages = ["intro","tech1","tech2","tech3","onsite"];
  const eligible = candidates.filter(c => interviewStages.includes(c.stage));
  const eventsByDay = {};
  eligible.forEach((c, i) => {
    // Spread across days 14..29 (recent + upcoming)
    const day = 14 + (i * 3) % 16;
    (eventsByDay[day] ||= []).push(c);
  });

  const today = 24;

  return (
    <div className="ats-cal">
      <div className="ats-cal__head">
        {dayHeaders.map(d => <div key={d}>{d}</div>)}
      </div>
      <div className="ats-cal__grid">
        {cells.map((cell, i) => {
          const evs = !cell.off ? (eventsByDay[cell.date] || []) : [];
          return (
            <div key={i} className={"ats-cal__day" + (cell.off ? " ats-cal__day--off" : "")}>
              <div className={"ats-cal__date" + (!cell.off && cell.date === today ? " is-today" : "")}>
                {cell.date}{!cell.off && cell.date === today && " · today"}
              </div>
              {evs.slice(0, 3).map(c => {
                const stage = STAGE_BY_ID[c.stage];
                return (
                  <div
                    key={c.id}
                    className="ats-cal__event"
                    style={{
                      background: stage.color, color: stage.fg,
                      display: "flex", alignItems: "center", gap: 5,
                    }}
                    title={`${c.name} · ${stage.label}`}
                  >
                    <Avatar src={c.avatar} name={c.name} size={14} />
                    <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      {stage.short.toLowerCase()} · {c.name.split(" ")[0]} {c.last[0]}.
                    </span>
                  </div>
                );
              })}
              {evs.length > 3 && (
                <div style={{ fontSize: 10, color: "var(--fg-4)" }}>+{evs.length - 3} more</div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}

// === Table view (extracted from old PipelineSheet, parameterized) ===
// Sortable column definitions: how to extract a comparable value from a candidate row.
const SHEET_SORT_KEYS = {
  name:    (c) => c.name.toLowerCase(),
  stage:   (c) => STAGE_FLOW.indexOf(c.stage),
  role:    (c) => (JOBS.find(j => j.id === c.jobId)?.title || "").toLowerCase(),
  source:  (c) => (c.sourceLabel || c.source || "").toLowerCase(),
  days:    (c) => c.days ?? -Infinity,
  loc:     (c) => (c.location || "").toLowerCase(),
  comp:    (c) => c.salaryLo ?? -Infinity,
  owner:   (c) => (c.recruiterName || "").toLowerCase(),
  activity:(c) => (c.lastActivity || "").toLowerCase(),
};

function compareBy(getter, dir) {
  return (a, b) => {
    const x = getter(a), y = getter(b);
    if (x === y) return 0;
    const r = x < y ? -1 : 1;
    return dir === "desc" ? -r : r;
  };
}

function SortHeader({ id, label, sort, onSort, align }) {
  // sort is an array of {key,dir}; we light up if id appears anywhere, but show priority badge if multi.
  const idx = (sort.keys || []).findIndex(s => s.key === id);
  const active = idx >= 0;
  const dir = active ? sort.keys[idx].dir : null;
  const indicator = !active ? "↕" : dir === "asc" ? "↑" : "↓";
  const showPriority = active && sort.keys.length > 1;
  return (
    <th
      onClick={() => onSort(id)}
      className={"ats-sheet__sort-th" + (active ? " is-active" : "")}
      style={{ cursor: "pointer", userSelect: "none", textAlign: align || "left" }}
      title={active
        ? (dir === "asc" ? "Sorted ascending — click to reverse" : "Sorted descending — click to clear")
        : "Click to sort ascending"}
    >
      <span style={{ display: "inline-flex", alignItems: "center", gap: 4 }}>
        {label}
        <span
          className="ats-sheet__sort-ind"
          style={{
            fontSize: 9,
            color: active ? "var(--nl-ink)" : "var(--fg-4)",
            opacity: active ? 1 : 0.45,
            fontFamily: "var(--font-mono)",
          }}
        >{indicator}</span>
        {showPriority && (
          <span
            style={{
              fontSize: 8.5,
              fontFamily: "var(--font-mono)",
              fontWeight: 600,
              padding: "1px 4px",
              borderRadius: 999,
              background: "var(--nl-ink)",
              color: "var(--bg-1)",
              lineHeight: 1.1,
            }}
            title={`Sort priority ${idx + 1}`}
          >{idx + 1}</span>
        )}
      </span>
    </th>
  );
}

// === Filter system ===
// Each column declares a filter "kind". Predicates are applied AND-style.
const SHEET_FILTER_FIELDS = [
  { id: "name",     label: "Candidate",     kind: "text",   getter: (c) => c.name },
  { id: "stage",    label: "Stage",         kind: "multi",  getter: (c) => c.stage,
    options: () => STAGES.map(s => ({ value: s.id, label: s.label })) },
  { id: "role",     label: "Role",          kind: "multi",  getter: (c) => c.jobId,
    options: () => JOBS.map(j => ({ value: j.id, label: j.title })) },
  { id: "source",   label: "Source",        kind: "multi",  getter: (c) => c.source,
    options: () => SOURCES.map(s => ({ value: s.id, label: s.label })) },
  { id: "dup",      label: "Duplicate",    kind: "multi",  getter: (c) => c.duplicateIds && c.duplicateIds.length > 0 ? "yes" : "no",
    options: () => [{ value: "yes", label: "Duplicated" }, { value: "no", label: "Unique" }] },
  { id: "days",     label: "Days in stage", kind: "range",  getter: (c) => c.days, min: 0, max: 30, step: 1 },
  { id: "loc",      label: "Location",      kind: "text",   getter: (c) => c.location },
  { id: "owner",    label: "Owner",         kind: "multi",  getter: (c) => c.recruiter,
    options: () => RECRUITERS.map(r => ({ value: r.id, label: r.name })) },
];
const FILTER_FIELD_BY_ID = Object.fromEntries(SHEET_FILTER_FIELDS.map(f => [f.id, f]));

function applyFilter(filter, c) {
  const f = FILTER_FIELD_BY_ID[filter.field];
  if (!f) return true;
  const v = f.getter(c);
  if (f.kind === "text") {
    const q = (filter.value || "").trim().toLowerCase();
    if (!q) return true;
    return String(v ?? "").toLowerCase().includes(q);
  }
  if (f.kind === "multi") {
    const sel = filter.value || [];
    if (!sel.length) return true;
    return sel.includes(v);
  }
  if (f.kind === "range") {
    const [lo, hi] = filter.value || [f.min, f.max];
    return v != null && v >= lo && v <= hi;
  }
  return true;
}

function filterSummary(filter) {
  const f = FILTER_FIELD_BY_ID[filter.field];
  if (!f) return "";
  if (f.kind === "text") return filter.value ? `contains "${filter.value}"` : "any";
  if (f.kind === "multi") {
    const sel = filter.value || [];
    if (!sel.length) return "any";
    if (sel.length === 1) {
      const opt = f.options().find(o => o.value === sel[0]);
      return `is ${opt?.label || sel[0]}`;
    }
    return `is any of ${sel.length}`;
  }
  if (f.kind === "range") {
    const [lo, hi] = filter.value || [f.min, f.max];
    if (lo === f.min && hi === f.max) return "any";
    if (lo === f.min) return `≤ ${hi}`;
    if (hi === f.max) return `≥ ${lo}`;
    return `${lo}–${hi}`;
  }
  return "";
}

function FilterChip({ filter, onChange, onRemove }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);
  const f = FILTER_FIELD_BY_ID[filter.field];
  if (!f) return null;
  return (
    <span ref={ref} className="ats-filter-chip ats-filter-chip--interactive" style={{ position: "relative" }}>
      <span onClick={() => setOpen(o => !o)} style={{ cursor: "pointer" }}>
        <b style={{ color: "var(--nl-ink)", fontWeight: 500 }}>{f.label}</b>
        <span style={{ color: "var(--fg-3)", margin: "0 4px" }}>{filterSummary(filter)}</span>
        <span style={{ color: "var(--fg-4)", fontSize: 9 }}>▾</span>
      </span>
      <span className="ats-filter-chip__x" onClick={onRemove} style={{ cursor: "pointer", marginLeft: 6 }}>×</span>
      {open && <FilterPopover field={f} filter={filter} onChange={onChange} />}
    </span>
  );
}

function FilterPopover({ field, filter, onChange }) {
  if (field.kind === "text") {
    return (
      <div className="ats-filter-pop">
        <input
          autoFocus
          type="text"
          className="ats-filter-pop__input"
          placeholder={`${field.label} contains…`}
          value={filter.value || ""}
          onChange={(e) => onChange({ ...filter, value: e.target.value })}
        />
      </div>
    );
  }
  if (field.kind === "multi") {
    const opts = field.options();
    const sel = new Set(filter.value || []);
    return (
      <div className="ats-filter-pop">
        <div className="ats-filter-pop__head">{field.label} is any of</div>
        <div className="ats-filter-pop__list">
          {opts.map(o => (
            <label key={o.value} className="ats-filter-pop__row">
              <input
                type="checkbox"
                checked={sel.has(o.value)}
                onChange={() => {
                  const next = new Set(sel);
                  if (next.has(o.value)) next.delete(o.value); else next.add(o.value);
                  onChange({ ...filter, value: [...next] });
                }}
              />
              <span>{o.label}</span>
            </label>
          ))}
        </div>
      </div>
    );
  }
  if (field.kind === "range") {
    const [lo, hi] = filter.value || [field.min, field.max];
    return (
      <div className="ats-filter-pop">
        <div className="ats-filter-pop__head">{field.label} between</div>
        <div className="ats-filter-pop__range">
          <input type="number" value={lo} min={field.min} max={hi} step={field.step}
            onChange={(e) => onChange({ ...filter, value: [Math.min(+e.target.value, hi), hi] })} />
          <span style={{ color: "var(--fg-3)" }}>–</span>
          <input type="number" value={hi} min={lo} max={field.max} step={field.step}
            onChange={(e) => onChange({ ...filter, value: [lo, Math.max(+e.target.value, lo)] })} />
        </div>
        <div className="ats-filter-pop__hint">{field.min}–{field.max}</div>
      </div>
    );
  }
  return null;
}

function AddFilterButton({ existing, onAdd }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);
  const used = new Set(existing.map(f => f.field));
  const available = SHEET_FILTER_FIELDS.filter(f => !used.has(f.id));
  return (
    <span ref={ref} style={{ position: "relative" }}>
      <button className="ats-btn ats-btn--ghost-bordered" onClick={() => setOpen(o => !o)} disabled={!available.length}>
        + Filter
      </button>
      {open && available.length > 0 && (
        <div className="ats-filter-pop ats-filter-pop--menu">
          {available.map(f => (
            <button
              key={f.id}
              className="ats-filter-pop__menu-row"
              onClick={() => { onAdd(f); setOpen(false); }}
            >{f.label}</button>
          ))}
        </div>
      )}
    </span>
  );
}

// Standalone filter bar — drives a shared filter list used by both Table and Board views.
function PipelineFilterBar({ filters, setFilters, total, visibleCount, sort, setSortKeys, quickFilters, toggleQuickFilter }) {
  const addFilter = (field) => {
    const initial =
      field.kind === "text"  ? "" :
      field.kind === "multi" ? [] :
      field.kind === "range" ? [field.min, field.max] : null;
    setFilters(prev => [...prev, { field: field.id, value: initial }]);
  };
  const updateFilter = (idx, next) => setFilters(prev => prev.map((f, i) => i === idx ? next : f));
  const removeFilter = (idx) => setFilters(prev => prev.filter((_, i) => i !== idx));
  const clearAll = () => setFilters([]);

  const sortKeys = sort.keys || [];
  const addSort = (fieldId) => setSortKeys([...sortKeys, { key: fieldId, dir: "asc" }]);
  const removeSort = (idx) => setSortKeys(sortKeys.filter((_, i) => i !== idx));
  const toggleSortDir = (idx) =>
    setSortKeys(sortKeys.map((s, i) => i === idx ? { ...s, dir: s.dir === "asc" ? "desc" : "asc" } : s));
  const moveSort = (idx, delta) => {
    const next = [...sortKeys];
    const j = idx + delta;
    if (j < 0 || j >= next.length) return;
    [next[idx], next[j]] = [next[j], next[idx]];
    setSortKeys(next);
  };
  const clearSort = () => setSortKeys([]);

  const hasActivity = filters.length > 0 || sortKeys.length > 0;

  return (
    <div className="ats-sheet-filterbar">
      <AddFilterButton existing={filters} onAdd={addFilter} />
      <AddSortButton existing={sortKeys} onAdd={addSort} />
      <span className="ats-sheet-filterbar__divider" aria-hidden="true" />
      {QUICK_FILTERS.map(qf => {
        const active = !!(quickFilters && quickFilters[qf.id]);
        return (
          <button
            key={qf.id}
            type="button"
            className={"ats-btn ats-btn--ghost-bordered ats-quick-filter" + (active ? " is-active" : "")}
            onClick={() => toggleQuickFilter && toggleQuickFilter(qf.id)}
            title={qf.desc}
          >
            <span className="ats-quick-filter__icon">{qf.icon}</span>
            <span className="ats-quick-filter__label">{qf.label}</span>
            {active && <span className="ats-quick-filter__x" aria-hidden="true">×</span>}
          </button>
        );
      })}
      {filters.map((f, i) => (
        <FilterChip
          key={`${f.field}-${i}`}
          filter={f}
          onChange={(next) => updateFilter(i, next)}
          onRemove={() => removeFilter(i)}
        />
      ))}
      {sortKeys.map((s, i) => (
        <SortChip
          key={`${s.key}-${i}`}
          sort={s}
          index={i}
          total={sortKeys.length}
          onToggleDir={() => toggleSortDir(i)}
          onRemove={() => removeSort(i)}
          onMoveUp={i > 0 ? () => moveSort(i, -1) : null}
          onMoveDown={i < sortKeys.length - 1 ? () => moveSort(i, 1) : null}
        />
      ))}
      {hasActivity && (
        <span className="ats-sheet-filterbar__count">{visibleCount} of {total}</span>
      )}
      {filters.length > 0 && (
        <button className="ats-btn" onClick={clearAll}>Clear filters</button>
      )}
      {sortKeys.length > 0 && (
        <button className="ats-btn" onClick={clearSort}>Clear sort</button>
      )}
    </div>
  );
}

function AddSortButton({ existing, onAdd }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);
  const used = new Set(existing.map(s => s.key));
  const available = SHEET_FILTER_FIELDS.filter(f => !used.has(f.id) && SHEET_SORT_KEYS[f.id]);
  return (
    <span ref={ref} style={{ position: "relative" }}>
      <button className="ats-btn ats-btn--ghost-bordered" onClick={() => setOpen(o => !o)} disabled={!available.length}>
        + Sort
      </button>
      {open && available.length > 0 && (
        <div className="ats-filter-pop ats-filter-pop--menu">
          {available.map(f => (
            <button
              key={f.id}
              className="ats-filter-pop__menu-row"
              onClick={() => { onAdd(f.id); setOpen(false); }}
            >{f.label}</button>
          ))}
        </div>
      )}
    </span>
  );
}

function SortChip({ sort, index, total, onToggleDir, onRemove, onMoveUp, onMoveDown }) {
  const field = SHEET_FILTER_FIELDS.find(f => f.id === sort.key);
  const label = field ? field.label : sort.key;
  return (
    <span className="ats-sheet-filterbar__sort">
      {total > 1 && (
        <span
          className="ats-sheet-filterbar__sort-prio"
          title={`Priority ${index + 1} of ${total}`}
        >{index + 1}</span>
      )}
      <span className="ats-sheet-filterbar__sort-label">Sort:</span>
      <button
        className="ats-sheet-filterbar__sort-value"
        onClick={onToggleDir}
        title="Click to toggle direction"
      >{label} {sort.dir === "asc" ? "↑" : "↓"}</button>
      {total > 1 && onMoveUp && (
        <button className="ats-sheet-filterbar__sort-move" onClick={onMoveUp} title="Increase priority">‹</button>
      )}
      {total > 1 && onMoveDown && (
        <button className="ats-sheet-filterbar__sort-move" onClick={onMoveDown} title="Decrease priority">›</button>
      )}
      <button
        className="ats-sheet-filterbar__sort-clear"
        onClick={onRemove}
        aria-label="Remove sort"
        title="Remove sort"
      >×</button>
    </span>
  );
}

function PipelineSheetView({ candidates, onOpen, sort, onSort }) {
  const handleSort = onSort;
  // Sorting is applied upstream in PipelineUnified so it persists across views.
  const sorted = candidates;

  return (
    <div className="ats-sheet">
      <table>
        <thead>
          <tr>
            <SortHeader id="name"     label="Candidate"     sort={sort} onSort={handleSort} />
            <SortHeader id="stage"    label="Stage"         sort={sort} onSort={handleSort} />
            <SortHeader id="role"     label="Role"          sort={sort} onSort={handleSort} />
            <SortHeader id="source"   label="Source"        sort={sort} onSort={handleSort} />
            <SortHeader id="days"     label="Days in stage" sort={sort} onSort={handleSort} />
            <SortHeader id="loc"      label="Location"      sort={sort} onSort={handleSort} />
            <SortHeader id="comp"     label="Comp ask"      sort={sort} onSort={handleSort} />
            <SortHeader id="owner"    label="Owner"         sort={sort} onSort={handleSort} />
            <SortHeader id="activity" label="Last activity" sort={sort} onSort={handleSort} />
          </tr>
        </thead>
        <tbody>
          {sorted.map(c => {
            const isStale = c.days >= 12 && !["hired","rejected","not_interested","declined","backburner"].includes(c.stage);
            const job = JOBS.find(j => j.id === c.jobId);
            return (
              <tr key={c.id} onClick={() => onOpen && onOpen(c.id)} style={{ cursor: onOpen ? "pointer" : "default" }}>
                <td>
                  <div className="ats-sheet__name">
                    <Avatar src={c.avatar} name={c.name} size={20} />
                    <div style={{ display: "flex", flexDirection: "column", lineHeight: 1.2 }}>
                      <b>{c.name} {c.starred && <span style={{ color: "var(--nl-amber)" }}>★</span>}{c.duplicateIds && c.duplicateIds.length > 0 && <span className="ats-card__dup" title={`${c.duplicateIds.length} duplicate profile${c.duplicateIds.length > 1 ? "s" : ""}`} style={{ marginLeft: 4 }}><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></span>}</b>
                      <span className="ats-sheet__company">{c.title} · {c.company}</span>
                    </div>
                  </div>
                </td>
                <td><StageChip stage={STAGE_BY_ID[c.stage]} dense /></td>
                <td style={{ color: "var(--fg-2)" }}>{job ? job.title.replace("Real-time Systems", "RT Systems") : "—"}</td>
                <td><SourcePill source={c.source} />{c.duplicateIds && c.duplicateIds.length > 0 && <span className="ats-source-pill" data-src="duplicate" style={{ marginLeft: 4 }}>Duplicate</span>}</td>
                <td><span className={"ats-card__days" + (isStale ? " is-stale" : "")}>{c.days}d</span></td>
                <td style={{ color: "var(--fg-3)" }}>{c.location}</td>
                <td style={{ color: "var(--fg-3)", fontFamily: "var(--font-mono)", fontSize: 11 }}>{c.salary}</td>
                <td><InitialDisc initials={c.recruiterInitials} color={c.recruiterColor} size={18} title={c.recruiterName} avatar={c.recruiterAvatar} /></td>
                <td className="ats-card__activity" style={{ maxWidth: 180 }}>{c.lastActivity}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

// === Sourced sub-columns by source ===
const SOURCED_SPLITS = [
  { id: "inbound",    label: "Inbound",    match: (c) => c.source === "inbound" },
  { id: "outbound",   label: "Outbound",   match: (c) => c.source === "outbound" },
  { id: "paraform",   label: "Paraform",   match: (c) => c.source === "paraform" },
  { id: "jon_luzha",  label: "Jon Luzha",  match: (c) => c.source === "Jon Luzha" },
];

function SourcedGroup({ candidates, onDragStart, onDragEnd, onDrop, onDragOver, draggingId, hoverStageId, onOpen, onRemind, onAdd }) {
  const sourcedStage = STAGE_BY_ID["sourced"];
  const matched = new Set();
  const splits = SOURCED_SPLITS.map(sp => {
    const filtered = candidates.filter(c => { if (sp.match(c)) { matched.add(c.id); return true; } return false; });
    return { ...sp, candidates: filtered };
  });
  const other = candidates.filter(c => !matched.has(c.id));
  if (other.length > 0) splits.push({ id: "other", label: "Other", candidates: other });

  return (
    <div className="ats-sourced-group">
      <div className="ats-sourced-group__head">
        <span className="ats-col__dot" style={{ background: sourcedStage.accent }} />
        <span className="ats-col__title">Sourced</span>
        <span className="ats-col__count">{candidates.length}</span>
      </div>
      <div className="ats-sourced-group__cols">
        {splits.map(sp => (
          <KanbanColumn
            key={"sourced-" + sp.id}
            stage={{ ...sourcedStage, id: "sourced", label: sp.label, short: sp.label }}
            candidates={sp.candidates}
            onDragStart={onDragStart}
            onDragEnd={onDragEnd}
            onDrop={onDrop}
            onDragOver={onDragOver}
            draggingId={draggingId}
            hoverStageId={hoverStageId}
            onOpen={onOpen}
            onRemind={onRemind}
            onAdd={onAdd}
            compact
          />
        ))}
      </div>
    </div>
  );
}

// === Kanban view (extracted from old PipelineKanban) ===
function PipelineKanbanView({ candidates, onDragStart, onDragEnd, onDrop, onDragOver, draggingId, hoverStageId, onOpen, onRemind, onAdd }) {
  const sourcedCandidates = candidates.filter(c => c.stage === "sourced");
  return (
    <div className="ats-kanban">
      <SourcedGroup
        candidates={sourcedCandidates}
        onDragStart={onDragStart}
        onDragEnd={onDragEnd}
        onDrop={onDrop}
        onDragOver={onDragOver}
        draggingId={draggingId}
        hoverStageId={hoverStageId}
        onOpen={onOpen}
        onRemind={onRemind}
        onAdd={onAdd}
      />
      {STAGES.filter(s => s.id !== "sourced").map(s => (
        <KanbanColumn
          key={s.id}
          stage={s}
          candidates={candidates.filter(c => c.stage === s.id)}
          onDragStart={onDragStart}
          onDragEnd={onDragEnd}
          onDrop={onDrop}
          onDragOver={onDragOver}
          draggingId={draggingId}
          hoverStageId={hoverStageId}
          onOpen={onOpen}
          onRemind={onRemind}
          onAdd={onAdd}
        />
      ))}
    </div>
  );
}

// === Notion-style view tabs ===
const PIPELINE_VIEWS_LABEL = {
  board: "Board view",
  table: "Table view",
  calendar: "Calendar view",
};

const CalendarIcon = () => (
  <svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" style={{ display: "block" }}>
    <rect x="2" y="3.5" width="12" height="11" rx="1.5" />
    <path d="M2 7h12" />
    <path d="M5.5 2v3M10.5 2v3" strokeLinecap="round" />
  </svg>
);

const ClockIcon = () => (
  <svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" style={{ display: "block" }}>
    <circle cx="8" cy="8" r="6" />
    <path d="M8 4.5V8l2.5 1.5" strokeLinecap="round" />
  </svg>
);

const SourceIcon = () => (
  <svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" style={{ display: "block" }}>
    <circle cx="4" cy="4" r="2" />
    <circle cx="4" cy="12" r="2" />
    <circle cx="12" cy="8" r="2" />
    <path d="M6 4h2a2 2 0 0 1 2 2v0M6 12h2a2 2 0 0 0 2-2v0" />
  </svg>
);

// Quick-filter chips that live in the filter bar (independent of the active view).
// Each is a boolean toggle; multiple can be active at once and they AND together
// with the column filters.
const QUICK_FILTERS = [
  {
    id: "stalled",
    label: "Stalled",
    desc: "In stage > 7 days",
    icon: <ClockIcon />,
    predicate: (c) => c.days > 7 && !["hired","rejected","not_interested","declined","backburner"].includes(c.stage),
  },
  {
    id: "referrals",
    label: "Sourced by referral",
    desc: "Source = referral",
    icon: <SourceIcon />,
    predicate: (c) => c.source === "referral",
  },
  {
    id: "duplicates",
    label: "Duplicated submissions",
    desc: "Has duplicate profiles",
    icon: <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>,
    predicate: (c) => c.duplicateIds && c.duplicateIds.length > 0,
  },
];

const VIEW_DEFS = [
  { id: "board",    label: "Board view",    icon: "▦", desc: "Kanban grouped by stage" },
  { id: "table",    label: "Table view", icon: "≡", desc: "Grouped table · 30+ rows" },
  { id: "calendar", label: "Calendar view", icon: <CalendarIcon />, desc: "Interviews on a calendar" },
];

function ViewTabs({ activeId, onSelect, onAction }) {
  return (
    <div className="ats-view-tabs">
      {VIEW_DEFS.map(v => (
        <button
          key={v.id}
          className={"ats-view-tab" + (activeId === v.id ? " is-active" : "")}
          onClick={() => onSelect(v.id)}
          title={v.desc}
        >
          <span className="ats-view-tab__icon">{v.icon}</span>
          {v.label}
          {v.starred && <span className="ats-view-tab__star">★</span>}
        </button>
      ))}
      <div className="ats-view-tabs__spacer" />
      <button className="ats-view-tabs__menu" onClick={() => onAction && onAction("Filter panel — coming soon")}>Filter</button>
      <button className="ats-view-tabs__menu" onClick={() => onAction && onAction("Sort panel — coming soon")}>Sort</button>
      <button className="ats-view-tabs__menu" onClick={() => onAction && onAction("Group panel — coming soon")}>Group</button>
      <button className="ats-view-tabs__menu" onClick={() => onAction && onAction("More options — coming soon")}>⋯</button>
    </div>
  );
}

// === Toolbar — view-specific controls ===
function ViewToolbar({ viewId, onAction }) {
  const [calMonthIdx, setCalMonthIdx] = useStateU(3); // 0=Jan…3=Apr 2026
  const [calZoom, setCalZoom] = useStateU("Month");
  const months = ["January 2026","February 2026","March 2026","April 2026","May 2026","June 2026"];
  const [active, setActive] = useStateU({}); // filter id -> true

  const toggleFilter = (id, label) => {
    setActive(prev => {
      const n = { ...prev };
      if (n[id]) delete n[id]; else n[id] = label;
      return n;
    });
    onAction && onAction(active[id] ? `Cleared ${label}` : `Filter applied: ${label}`);
  };

  if (viewId === "calendar") {
    return (
      <div className="ats-toolbar">
        <div className="ats-toolbar__group">
          <button className="ats-btn ats-btn--ghost-bordered" onClick={() => setCalMonthIdx(i => Math.max(0, i - 1))}>‹</button>
          <button className="ats-btn ats-btn--ghost-bordered" style={{ minWidth: 128 }}>{months[calMonthIdx]}</button>
          <button className="ats-btn ats-btn--ghost-bordered" onClick={() => setCalMonthIdx(i => Math.min(months.length - 1, i + 1))}>›</button>
          <button className="ats-btn" onClick={() => { setCalMonthIdx(3); onAction && onAction("Jumped to today"); }}>Today</button>
        </div>
        <span className="ats-filter-chip">Stage: interviewing <span className="ats-filter-chip__x">×</span></span>
        <div className="ats-toolbar__spacer" />
        <div className="ats-toolbar__group">
          {["Day","Week","Month"].map(z => (
            <button key={z}
              className={"ats-btn" + (calZoom === z ? " is-active" : "")}
              onClick={() => setCalZoom(z)}>{z}</button>
          ))}
        </div>
      </div>
    );
  }
  const filters = [
    { id: "stage", label: "Stage" },
    { id: "recruiter", label: "Recruiter" },
    { id: "source", label: "Source" },
  ];
  return (
    <div className="ats-toolbar">
      <div className="ats-toolbar__group">
        <button className="ats-btn ats-btn--ghost-bordered is-active" onClick={() => onAction && onAction("Showing all open roles")}>
          All open roles<span className="ats-btn__caret">▾</span>
        </button>
        {filters.map(f => (
          <button key={f.id}
            className={"ats-btn" + (active[f.id] ? " is-active" : "")}
            onClick={() => toggleFilter(f.id, f.label)}>
            {f.label} <span className="ats-btn__caret">▾</span>
          </button>
        ))}
      </div>
      {viewId === "stalled" && (
        <span className="ats-filter-chip">Days in stage &gt; 7 <span className="ats-filter-chip__x">×</span></span>
      )}
      {viewId === "referrals" && (
        <span className="ats-filter-chip">Source = referral <span className="ats-filter-chip__x">×</span></span>
      )}
      {Object.entries(active).map(([id, label]) => (
        <span key={id} className="ats-filter-chip">
          {label}: any
          <span className="ats-filter-chip__x" onClick={() => toggleFilter(id, label)} style={{ cursor: "pointer" }}>×</span>
        </span>
      ))}
      <div className="ats-toolbar__spacer" />
      <div className="ats-toolbar__group">
        <button className="ats-btn" onClick={() => onAction && onAction("Grouped by Stage")}>
          Group: Stage <span className="ats-btn__caret">▾</span>
        </button>
        <button className="ats-btn" onClick={() => onAction && onAction("Sorted by Days in stage")}>
          Sort: Days in stage <span className="ats-btn__caret">▾</span>
        </button>
      </div>
    </div>
  );
}

function SharePopover({ url, onClose, onCopied }) {
  const inputRef = React.useRef(null);
  const popRef = React.useRef(null);

  React.useEffect(() => {
    // Auto-select the URL so user can ⌘C immediately
    if (inputRef.current) {
      inputRef.current.focus();
      inputRef.current.select();
    }
    const onDoc = (e) => {
      if (popRef.current && !popRef.current.contains(e.target)) onClose();
    };
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onDoc);
      document.removeEventListener("keydown", onKey);
    };
  }, [onClose]);

  const [copyState, setCopyState] = React.useState("idle"); // "idle" | "copied" | "manual"

  const handleCopy = async () => {
    // Try the modern async API first — it returns a Promise that REJECTS in
    // sandboxed iframes, so we must actually await it before claiming success.
    let copied = false;
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(url);
        copied = true;
      }
    } catch (e) { copied = false; }

    // Fallback: select the visible input and try execCommand. This still
    // fails inside many sandboxed iframes, so we check the return value.
    if (!copied) {
      try {
        const el = inputRef.current;
        if (el) {
          el.focus();
          el.select();
          copied = !!document.execCommand("copy");
        }
      } catch (e) { copied = false; }
    }

    if (copied) {
      setCopyState("copied");
      setTimeout(() => onCopied(), 600); // brief "Copied" flash, then close + toast
    } else {
      // Programmatic copy is blocked (likely a sandboxed preview).
      // Keep the popover open with the URL selected so ⌘C / Ctrl+C works —
      // that path is allowed because it's a real user keystroke.
      if (inputRef.current) { inputRef.current.focus(); inputRef.current.select(); }
      setCopyState("manual");
    }
  };

  return (
    <div ref={popRef} className="ats-share-pop" role="dialog" aria-label="Share view">
      <div className="ats-share-pop__head">
        <div className="ats-share-pop__title">Share this view</div>
        <div className="ats-share-pop__sub">Anyone with the link can open this pipeline view</div>
      </div>

      <div className="ats-share-pop__row">
        <input
          ref={inputRef}
          className="ats-share-pop__url"
          value={url}
          readOnly
          onFocus={(e) => e.target.select()}
        />
        <button
          className={"ats-btn ats-btn--primary ats-share-pop__copy" + (copyState === "copied" ? " is-copied" : "")}
          onClick={handleCopy}
        >{copyState === "copied" ? "✓ Copied" : "Copy link"}</button>
      </div>
      {copyState === "manual" && (
        <div className="ats-share-pop__manual-hint">
          Clipboard access is blocked in this preview. The link is selected — press <kbd>⌘C</kbd> (or <kbd>Ctrl+C</kbd>) to copy.
        </div>
      )}
    </div>
  );
}

// === Save view popover — small naming dialog anchored to the Save view button ===
function SaveViewPopover({ defaultName, onClose, onSave }) {
  const inputRef = React.useRef(null);
  const popRef = React.useRef(null);
  const [name, setName] = React.useState(defaultName || "");

  React.useEffect(() => {
    if (inputRef.current) {
      inputRef.current.focus();
      inputRef.current.select();
    }
    const onDoc = (e) => {
      if (popRef.current && !popRef.current.contains(e.target)) onClose();
    };
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onDoc);
      document.removeEventListener("keydown", onKey);
    };
  }, [onClose]);

  const submit = () => {
    const trimmed = name.trim();
    if (!trimmed) return;
    onSave(trimmed);
  };

  return (
    <div ref={popRef} className="ats-share-pop ats-save-pop" role="dialog" aria-label="Save view">
      <div className="ats-share-pop__head">
        <div className="ats-share-pop__title">Save this view</div>
        <div className="ats-share-pop__sub">Pin to your sidebar — current filters &amp; sort are saved</div>
      </div>
      <div className="ats-share-pop__row">
        <input
          ref={inputRef}
          className="ats-share-pop__url ats-save-pop__name"
          value={name}
          onChange={(e) => setName(e.target.value)}
          onKeyDown={(e) => { if (e.key === "Enter") submit(); }}
          placeholder="Untitled view"
          maxLength={48}
        />
        <button
          className="ats-btn ats-btn--primary ats-share-pop__copy"
          onClick={submit}
          disabled={!name.trim()}
        >Save</button>
      </div>
    </div>
  );
}

function PipelineUnified({ defaultView = "board" }) {
  const viewer = useViewer();
  const [view, setView] = useStateU(defaultView);
  const [activeJob, setActiveJob] = useStateU(null); // null = open roles, "__all__" = all, jobId, or saved-view id
  const [candidates, setCandidates] = useStateU(() => CANDIDATES.map(c => ({ ...c })));
  React.useEffect(() => {
    const sync = () => setCandidates(prev => {
      return CANDIDATES.map(g => {
        const local = prev.find(p => p.id === g.id);
        return local ? { ...local, ...g } : { ...g };
      });
    });
    window.addEventListener("candidates-updated", sync);
    return () => window.removeEventListener("candidates-updated", sync);
  }, []);
  const [filters, setFilters] = useStateU([]); // shared by Board + Table + Calendar
  const [quickFilters, setQuickFilters] = useStateU({}); // boolean toggles: stalled / referrals / highrating
  const toggleQuickFilter = (id) => setQuickFilters(prev => {
    const n = { ...prev };
    if (n[id]) delete n[id]; else n[id] = true;
    return n;
  });
  const [sort, setSort] = useStateU({ keys: [] }); // shared multi-level sort: { keys: [{key,dir},...] }
  // Click on a column header: if it's the only/top sort, cycle asc → desc → remove. Otherwise promote it to top with asc.
  const handleSort = (key) => {
    setSort(prev => {
      const keys = prev.keys || [];
      const idx = keys.findIndex(s => s.key === key);
      // Top-priority single behavior preserved when this key is alone or already at top.
      if (idx === 0 && keys.length >= 1) {
        const cur = keys[0];
        if (cur.dir === "asc") return { keys: [{ key, dir: "desc" }, ...keys.slice(1)] };
        // desc → remove this key
        return { keys: keys.slice(1) };
      }
      // Already in list but not top: promote to top, keep its dir
      if (idx > 0) {
        const moved = keys[idx];
        return { keys: [moved, ...keys.filter((_, i) => i !== idx)] };
      }
      // Not in list yet: append at lowest priority (so existing multi-sort isn't disrupted)
      return { keys: [...keys, { key, dir: "asc" }] };
    });
  };
  const setSortKeys = (next) => setSort({ keys: next });
  const [draggingId, setDraggingId] = useStateU(null);
  const [hoverStageId, setHoverStageId] = useStateU(null);
  const [toast, setToast] = useStateU(null);
  const [, showToast, ToastHost] = useAtsToast();
  const [openCandidateId, setOpenCandidateId] = useStateU(null);
  const [reminderTarget, setReminderTarget] = useStateU(null); // candidate
  const [shareOpen, setShareOpen] = useStateU(false);
  const [saveOpen, setSaveOpen] = useStateU(false);
  const [savedViews, setSavedViews] = useStateU([]); // [{id, name, view, jobId}]
  const [renamingId, setRenamingId] = useStateU(null);
  const [addModalOpen, setAddModalOpen] = useStateU(false);
  const openCandidate = (id) => { setOpenCandidateId(id); window.location.hash = "candidate/" + id; };

  // Deep link: listen for ats-open-candidate events (from hash routing)
  React.useEffect(() => {
    function handleDeepLink(e) {
      setOpenCandidateId(e.detail);
    }
    // Check if there's a pending deep link from initial page load
    if (window.__atsOpenCandidate) {
      setOpenCandidateId(window.__atsOpenCandidate);
      window.__atsOpenCandidate = null;
    }
    window.addEventListener('ats-open-candidate', handleDeepLink);
    return () => window.removeEventListener('ats-open-candidate', handleDeepLink);
  }, []);

  const openReminder = (id) => {
    const c = candidates.find(x => x.id === id);
    if (c) setReminderTarget(c);
  };

  const SAVED_VIEW_TO_VIEW = { awaiting: "board", onsite: "calendar" };
  const handleSidebarSelect = (id) => {
    // User-saved views: restore the saved view+jobId
    const saved = savedViews.find(v => v.id === id);
    if (saved) {
      setActiveJob(saved.jobId);
      setView(saved.view);
      showToast(`Opened saved view “${saved.name}”`);
      return;
    }
    setActiveJob(id);
    if (SAVED_VIEW_TO_VIEW[id]) {
      setView(SAVED_VIEW_TO_VIEW[id]);
      const labelMap = { awaiting: "Awaiting my review", onsite: "Onsite this week" };
      showToast(`Showing: ${labelMap[id]}`);
    } else if (id === "__all__") {
      showToast("Showing all roles (open + closed)");
    } else if (id === null) {
      showToast("Showing all open roles");
    } else {
      const job = JOBS.find(j => j.id === id);
      if (job) showToast(`Filtered to ${job.title}`);
    }
  };

  // Apply viewer access first, then sidebar filter, then view-level filter, then user column filters
  const preFilter = useMemoU(() => {
    let rows = visibleCandidates(viewer, candidates);
    if (activeJob === "__all__") {
      // no filter — include all
    } else if (activeJob === null) {
      const openIds = new Set(JOBS.filter(j => j.status === "open" || j.status === "active").map(j => j.id));
      rows = rows.filter(c => openIds.has(c.jobId));
    } else if (activeJob === "awaiting") {
      rows = rows.filter(c => ["intro","tech1","tech2","tech3"].includes(c.stage));
    } else if (activeJob === "onsite") {
      rows = rows.filter(c => c.stage === "onsite");
    } else if (activeJob && !SAVED_VIEW_TO_VIEW[activeJob]) {
      rows = rows.filter(c => c.jobId === activeJob);
    }
    // Quick-filter chips (independent of view) — AND together
    for (const qf of QUICK_FILTERS) {
      if (quickFilters[qf.id]) rows = rows.filter(qf.predicate);
    }
    return rows;
  }, [viewer, view, candidates, activeJob, quickFilters]);
  const visible = useMemoU(
    () => {
      const filtered = filters.length ? preFilter.filter(c => filters.every(f => applyFilter(f, c))) : preFilter;
      const arr = [...filtered];
      const sortKeys = (sort.keys && sort.keys.length) ? sort.keys : [{ key: "stage", dir: "asc" }];
      // Build chained comparator: lowest priority first (stable sort, so we sort by least significant first).
      // Easier: single comparator that walks the keys array.
      arr.sort((a, b) => {
        for (const { key, dir } of sortKeys) {
          const getter = SHEET_SORT_KEYS[key];
          if (!getter) continue;
          const x = getter(a), y = getter(b);
          if (x === y) continue;
          const r = x < y ? -1 : 1;
          return dir === "desc" ? -r : r;
        }
        return 0;
      });
      return arr;
    },
    [preFilter, filters, sort]
  );

  // Dynamic page title based on selection
  const titleSuffix = (() => {
    if (activeJob === "__all__") return "all roles";
    if (activeJob === null) return "all open roles";
    if (activeJob === "awaiting") return "awaiting my review";
    if (activeJob === "onsite") return "onsite this week";
    const job = JOBS.find(j => j.id === activeJob);
    return job ? job.title : "all open roles";
  })();

  const handleDrop = (id, stageId, ownerOverride = null) => {
    const moved = candidates.find(c => c.id === id);
    // Stage anchor: non-admins can't move candidates out of offer/hired/declined.
    // Note: backend's `declined_offer` is remapped to `declined` on the frontend
    // via STAGE_MAP in api-data.jsx.
    if (moved && viewer.role !== "admin" && moved.stage !== stageId &&
        (moved.stage === "offer" || moved.stage === "hired" || moved.stage === "declined")) {
      setDraggingId(null); setHoverStageId(null);
      showToast(`${STAGE_BY_ID[moved.stage].label} stage is locked — ask an admin to change it.`, { tone: "warn" });
      return;
    }
    setCandidates(prev => prev.map(c => {
      if (c.id !== id || (c.stage === stageId && !ownerOverride)) return c;
      const update = {
        ...c, stage: stageId, days: 0,
        lastActivity: `Moved to ${STAGE_BY_ID[stageId].label} just now`,
      };
      if (ownerOverride) {
        update.recruiterName = ownerOverride.name;
        update.recruiterInitials = ownerOverride.initials;
        update.recruiterColor = ownerOverride.color;
        update.recruiterRole = ownerOverride.role;
        update.recruiterAvatar = ownerOverride.avatar_url || null;
      }
      return update;
    }));
    if (moved && moved.stage !== stageId) {
      setToast({
        name: moved.name,
        from: STAGE_BY_ID[moved.stage].label,
        to: STAGE_BY_ID[stageId].label,
        id,
        fromStageId: moved.stage,
      });
      setTimeout(() => setToast(null), 3500);
      updateCandidateStage(id, stageId);
    }
    if (ownerOverride && ownerOverride.id) {
      updateCandidateProfile(id, { assigned_recruiter_id: ownerOverride.id });
    }
    setDraggingId(null); setHoverStageId(null);
  };

  const handleAdvance = (id, owner) => {
    const c = candidates.find(x => x.id === id);
    if (!c) return;
    const next = nextStage(c.stage);
    if (!next) return;
    handleDrop(id, next, owner);
  };
  const handleReject = (id) => {
    const c = candidates.find(x => x.id === id);
    if (!c) return;
    setCandidates(prev => prev.map(x => x.id === id ? { ...x, stage: "rejected", days: 0, lastActivity: "Rejected just now" } : x));
    setToast({
      name: c.name,
      from: STAGE_BY_ID[c.stage].label,
      to: "Rejected by us",
      id,
      fromStageId: c.stage,
    });
    setTimeout(() => setToast(null), 3500);
    updateCandidateStage(id, "rejected");
  };
  const handleSetStage = (id, stageId, owner) => handleDrop(id, stageId, owner);

  // Revert the most recent stage move advertised in the toast. Bypasses the
  // anchor check so an interviewer who just moved a candidate INTO Offer can
  // still take it back. Once the stage lands in Offer/Hired/Declined, only
  // admins can move it again, but the immediate undo path stays open.
  const handleUndoToast = () => {
    if (!toast || !toast.id || !toast.fromStageId) return;
    const { id, fromStageId } = toast;
    setCandidates(prev => prev.map(c => c.id === id
      ? { ...c, stage: fromStageId, days: 0, lastActivity: `Reverted to ${STAGE_BY_ID[fromStageId].label} just now` }
      : c));
    updateCandidateStage(id, fromStageId);
    setToast(null);
  };

  // Listen for page changes to update active tab
  const [currentPage, setCurrentPage] = React.useState(
    typeof window.__atsCurrentPage === 'string' ? window.__atsCurrentPage : 'pipeline'
  );

  React.useEffect(() => {
    const handlePageChange = (e) => setCurrentPage(e.detail);
    window.addEventListener('ats-page-change', handlePageChange);
    return () => window.removeEventListener('ats-page-change', handlePageChange);
  }, []);

  const activeTabMap = {
    'pipeline': 'Pipeline',
    'my-candidates': 'My candidates',
    'settings': 'Permissions'
  };

  // Compute modal content separately to ensure stable rendering
  const currentCandidate = openCandidateId ? candidates.find(x => x.id === openCandidateId) : null;

  return (
    <div className="ats-shell ats-shell--frame">
      <RoleBanner />
      <TopNav activeTab={activeTabMap[currentPage] || "Pipeline"} />
      <div className="ats-body">
        <JobSidebar
          activeJobId={activeJob}
          onSelect={handleSidebarSelect}
          view={view}
          onViewSelect={(id) => { setView(id); showToast(`Switched to ${PIPELINE_VIEWS_LABEL[id] || id}`); }}
          savedViews={savedViews}
          activeSavedViewId={savedViews.find(v => v.jobId === activeJob && v.view === view)?.id || null}
          onSavedViewRename={(id, name) => {
            setSavedViews(prev => prev.map(v => v.id === id ? { ...v, name } : v));
            showToast(`Renamed to “${name}”`, { tone: "success" });
          }}
          onSavedViewDelete={(id) => {
            const removed = savedViews.find(v => v.id === id);
            setSavedViews(prev => prev.filter(v => v.id !== id));
            if (removed) showToast(`Removed “${removed.name}”`);
          }}
        />
        <main className="ats-main">
          <div className="ats-page-head">
            <div>
              <h1 className="ats-page-title">
                Pipeline <em>—  {titleSuffix}</em>
              </h1>
              <div className="ats-page-sub">
                {visible.length} candidates {activeJob && activeJob !== "__all__" ? "in this view" : `across ${JOBS.length} roles`} · Last synced from LinkedIn 4 minutes ago · <span style={{ color: "var(--fg-2)" }}>Switch views below — same data, different lens.</span>
              </div>
            </div>
            <div className="ats-page-actions" style={{ position: "relative" }}>
              <button
                className={"ats-btn ats-btn--ghost-bordered ats-btn--page-action" + (shareOpen ? " is-active" : "")}
                onClick={() => { setShareOpen(v => !v); setSaveOpen(false); }}
              >Share view</button>
              {shareOpen && (
                <SharePopover
                  url="https://nuance.app/p/pipeline/all-open"
                  onClose={() => setShareOpen(false)}
                  onCopied={() => { setShareOpen(false); showToast("Share link copied to clipboard", { tone: "success" }); }}
                />
              )}
              <button
                className={"ats-btn ats-btn--ghost-bordered ats-btn--page-action" + (saveOpen ? " is-active" : "")}
                onClick={() => { setSaveOpen(v => !v); setShareOpen(false); }}
              >Save view</button>
              {saveOpen && (
                <SaveViewPopover
                  defaultName={(() => {
                    const base = activeJob === "__all__" ? "All roles"
                      : activeJob && activeJob !== "__all__" ? (JOBS.find(j => j.id === activeJob)?.title || "View")
                      : "Open roles";
                    const viewLabel = PIPELINE_VIEWS_LABEL[view] || "";
                    return `${base} · ${viewLabel.replace(" view", "")}`;
                  })()}
                  onClose={() => setSaveOpen(false)}
                  onSave={(name) => {
                    const id = `saved-${Date.now()}`;
                    setSavedViews(prev => [...prev, { id, name, view, jobId: activeJob }]);
                    setSaveOpen(false);
                    showToast(`Saved view “${name}” to your sidebar`, { tone: "success" });
                  }}
                />
              )}
              <button className="ats-btn ats-btn--primary ats-btn--page-action" onClick={() => setAddModalOpen(true)}>＋ Add candidate</button>
            </div>
          </div>
          <PipelineMetrics candidates={visible} />
          {(view === "board" || view === "table" || view === "calendar") && (
            <PipelineFilterBar
              filters={filters}
              setFilters={setFilters}
              total={preFilter.length}
              visibleCount={visible.length}
              sort={sort}
              setSortKeys={setSortKeys}
              quickFilters={quickFilters}
              toggleQuickFilter={toggleQuickFilter}
            />
          )}

          {view === "board" && (
            <PipelineKanbanView
              candidates={visible}
              onDragStart={setDraggingId}
              onDragEnd={() => { setDraggingId(null); setHoverStageId(null); }}
              onDrop={handleDrop}
              onDragOver={setHoverStageId}
              draggingId={draggingId}
              hoverStageId={hoverStageId}
              onOpen={openCandidate}
              onRemind={openReminder}
              onAdd={() => setAddModalOpen(true)}
            />
          )}
          {view === "table" && (
            <PipelineSheetView candidates={visible} onOpen={openCandidate} onRemind={openReminder} sort={sort} onSort={handleSort} />
          )}
          {view === "calendar" && <PipelineCalendar candidates={visible} onOpen={openCandidate} />}

          {ToastHost}
          {toast && (
            <div className="ats-toast">
              <span className="ats-toast__dot" />
              Moved <b>{toast.name}</b> from <span style={{ color: "var(--fg-3)" }}>{toast.from}</span> → <b>{toast.to}</b>
              <button className="ats-toast__undo" onClick={handleUndoToast}>Undo</button>
            </div>
          )}
        </main>
      </div>
      {currentCandidate && (
        <CandidateModal
          candidate={currentCandidate}
          onClose={() => { setOpenCandidateId(null); history.replaceState(null, "", window.location.pathname + window.location.search); }}
          onAdvance={(owner) => handleAdvance(openCandidateId, owner)}
          onReject={() => handleReject(openCandidateId)}
          onSetStage={(stageId, owner) => handleSetStage(openCandidateId, stageId, owner)}
          onDelete={() => {
            setCandidates(prev => prev.filter(x => x.id !== openCandidateId));
            setOpenCandidateId(null);
            history.replaceState(null, "", window.location.pathname + window.location.search);
          }}
          siblings={candidates.filter(x => x.jobId === currentCandidate.jobId)}
          onNavigate={(dir) => {
            const jobCandidates = candidates.filter(x => x.jobId === currentCandidate.jobId);
            const idx = jobCandidates.findIndex(x => x.id === openCandidateId);
            if (idx === -1) return;
            const next = jobCandidates[(idx + dir + jobCandidates.length) % jobCandidates.length];
            setOpenCandidateId(next.id);
            window.location.hash = "candidate/" + next.id;
          }}
        />
      )}
      {reminderTarget && (
        <window.PingDriModal
          c={reminderTarget}
          job={JOBS.find(j => j.id === reminderTarget.jobId)}
          sender={viewer}
          onClose={() => setReminderTarget(null)}
        />
      )}
      {addModalOpen && (
        <AddCandidateModal onClose={() => setAddModalOpen(false)} />
      )}
    </div>
  );
}

Object.assign(window, { PipelineUnified, PipelineKanbanView, PipelineSheetView, PipelineCalendar, ViewTabs });
