// ============ LEADS TAB ============
// Tenant-scoped CRM-lite view: every conversation where the agent has
// captured a customer name or company shows up here as a "lead". Inline
// edit of lifecycle status + free-text notes; both fields PATCH the server
// and rebroadcast via SSE so other dashboard tabs (and the master-admin
// rollup) stay in sync without a refetch.

const LD_SERVER = () =>
  (typeof window !== "undefined" && window.CITRUS_CONFIG && window.CITRUS_CONFIG.SERVER_URL) ||
  "http://localhost:3001";

// Stage palette — matches the server's VALID_STAGE set in routes/leads-conv.js.
// "contacted" was the pre-migration name for "engaged"; kept as fallback alias
// in the lookup so any legacy rows display correctly until migrated.
const LD_LIFECYCLE = [
  { id: "new",       label: "New",       bg: "#EEF4FF", fg: "#1F4FB5", bd: "#A6D8FF" },
  { id: "engaged",   label: "Engaged",   bg: "#FFF7ED", fg: "#B45309", bd: "#FCD34D" },
  { id: "qualified", label: "Qualified", bg: "#F5EEFB", fg: "#742855", bd: "#F2C8E0" },
  { id: "quoted",    label: "Quoted",    bg: "#FEF9C3", fg: "#92400E", bd: "#FDE68A" },
  { id: "won",       label: "Won",       bg: "#ECFDF5", fg: "#065F46", bd: "#6EE7B7" },
  { id: "lost",      label: "Lost",      bg: "#FEF2F2", fg: "#9F1239", bd: "#FCA5A5" },
];
const LD_LIFECYCLE_BY_ID = Object.fromEntries(LD_LIFECYCLE.map((s) => [s.id, s]));
// Backward-compat alias for pre-migration rows still carrying "contacted"
LD_LIFECYCLE_BY_ID["contacted"] = LD_LIFECYCLE_BY_ID["engaged"];

// Lost reason options — must match server VALID_LOST_REASON
const LD_LOST_REASONS = [
  { id: "price",               label: "Price" },
  { id: "timeline",            label: "Timeline" },
  { id: "went_with_competitor",label: "Went with competitor" },
  { id: "went_cold",           label: "Went cold" },
  { id: "other",               label: "Other" },
];

const LD_SUMMARY = {
  lead:      { label: "Lead",      fg: "#1F4FB5" },
  booked:    { label: "Booked",    fg: "#065F46" },
  closed:    { label: "Closed",    fg: "#6B7280" },
  escalated: { label: "Escalated", fg: "#B45309" },
};

// Classification taxonomy — the agent assigns these per conversation (multi-
// label). Mirrors the server whitelist in server/lead-categories.js: keep the
// ids in sync, never rename one (stored tags + the server enum depend on it).
const LD_CATEGORIES = [
  { id: "spare_parts", label: "Spare parts",           bg: "#EEF4FF", fg: "#1F4FB5", bd: "#A6D8FF" },
  { id: "maintenance", label: "Maintenance / Service", bg: "#FFF7ED", fg: "#B45309", bd: "#FCD34D" },
  { id: "new_project", label: "New project",           bg: "#ECFDF5", fg: "#065F46", bd: "#6EE7B7" },
  { id: "general",     label: "General / Other",       bg: "#F3F4F6", fg: "#4B5563", bd: "#D1D5DB" },
];
const LD_CATEGORY_BY_ID = Object.fromEntries(LD_CATEGORIES.map((c) => [c.id, c]));
const LD_CATEGORY_ORDER = LD_CATEGORIES.map((c) => c.id);

// Small read-only chips for a lead's classification tags. Renders nothing when
// the agent hasn't tagged the conversation yet.
function LdCategoryTags({ categories }) {
  const cats = Array.isArray(categories) ? categories : [];
  if (!cats.length) return null;
  return (
    <React.Fragment>
      {cats.map((k) => {
        const c = LD_CATEGORY_BY_ID[k];
        if (!c) return null;
        return (
          <span key={k} style={{ fontSize: 10.5, fontWeight: 600, padding: "1px 8px", borderRadius: 999, whiteSpace: "nowrap", color: c.fg, background: c.bg, border: `1px solid ${c.bd}` }}>
            {c.label}
          </span>
        );
      })}
    </React.Fragment>
  );
}

// Contact-log vocabulary — how the operator reached out + how it went. Mirrors
// the server whitelists in routes/leads-conv.js; each maps to an icon/label so
// the activity timeline stays readable.
const LD_CONTACT_TYPES = [
  { id: "call",    label: "Call",    icon: "📞" },
  { id: "message", label: "Message", icon: "💬" },
  { id: "email",   label: "Email",   icon: "✉️" },
  { id: "meeting", label: "Meeting", icon: "🤝" },
];
const LD_CONTACT_TYPE_BY_ID = Object.fromEntries(LD_CONTACT_TYPES.map((t) => [t.id, t]));
const LD_OUTCOMES = [
  { id: "reached",        label: "Reached" },
  { id: "no_answer",      label: "No answer" },
  { id: "left_message",   label: "Left message" },
  { id: "interested",     label: "Interested" },
  { id: "not_interested", label: "Not interested" },
  { id: "callback",       label: "Wants callback" },
  { id: "scheduled",      label: "Scheduled" },
  { id: "other",          label: "Other" },
];
const LD_OUTCOME_BY_ID = Object.fromEntries(LD_OUTCOMES.map((o) => [o.id, o]));

// Pipeline order shown as clickable stages (Lost is offered separately as a
// terminal state, so it's excluded from the linear track).
const LD_PIPELINE = ["new", "engaged", "qualified", "quoted", "won"];
// A distinct colour per journey stage (Showed up · Engaged · Qualified · Quoted · Outcome)
const LD_STAGE_COLORS = ["#2D6CDF", "#E07B1A", "#8B5CF6", "#B45309", "#0B7A57"];

// AC11: suppress conversion rate when sample is too small to be meaningful.
const MIN_RATE_THRESHOLD = 5;

// AC5 — Priority/temperature badge sourced from the same fields as the daily
// recap email (server/daily-recap.js). Must stay in sync: hot/warm/cold values
// read c.lead.temperature || c.summary?.temperature || c.lead.priority, then
// derive from stage if none is set. Never a separately maintained value.
function leadTemperature(lead) {
  const raw = String(lead.temperature || lead.summaryTemperature || lead.priority || "").toLowerCase();
  if (["hot", "warm", "cold"].includes(raw)) return raw;
  const stage = lead.stage || lead.lifecycleStatus || "new";
  if (["qualified", "won"].includes(stage)) return "hot";
  if (stage === "quoted") return "warm";
  return null;
}
const TEMP_STYLE = {
  hot:  { label: "High",  bg: "#FF4B2B22", fg: "#CC2200", bd: "#FF4B2B55" },
  warm: { label: "Warm",  bg: "#F59E0B22", fg: "#92400E", bd: "#F59E0B55" },
  cold: { label: "Cold",  bg: "#3B82F622", fg: "#1D4ED8", bd: "#3B82F655" },
};

// YYYY-MM-DD for an <input type="date"> from an ISO/date string (or "").
function ldDateInputValue(iso) {
  if (!iso) return "";
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return "";
  return d.toISOString().slice(0, 10);
}
// True when a follow-up date is today or in the past (needs attention).
function ldFollowUpDue(iso) {
  if (!iso) return false;
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return false;
  const today = new Date(); today.setHours(0, 0, 0, 0);
  return d.getTime() <= today.getTime();
}

function ldRelative(iso) {
  if (!iso) return "—";
  const diffMs = Date.now() - new Date(iso).getTime();
  if (Number.isNaN(diffMs)) return "—";
  const min = Math.floor(diffMs / 60000);
  if (min < 1) return "just now";
  if (min < 60) return `${min}m ago`;
  const hr = Math.floor(min / 60);
  if (hr < 24) return `${hr}h ago`;
  const day = Math.floor(hr / 24);
  if (day < 30) return `${day}d ago`;
  return new Date(iso).toLocaleDateString();
}

function ldCustomerLabel(lead) {
  if (lead.customerName) return lead.customerName;
  if (lead.from && lead.from.startsWith("persona:")) return "Test persona";
  if (lead.from) return lead.from.replace(/^whatsapp:/i, "");
  return "Unknown";
}

function LeadsTab({ onSwitchTab }) {
  const [leads, setLeads]     = useState(null); // null = loading; [] = empty
  const [filter, setFilter]   = useState("all");
  const [catFilter, setCatFilter] = useState("all"); // classification tag filter
  const [savingId, setSavingId] = useState(null);
  const [noteDraft, setNoteDraft] = useState({});
  const noteTimers = useRef({});
  const [logForm, setLogForm] = useState({});
  // Lost-reason modal: { from, pendingStage } while open, null when closed.
  const [lostModal, setLostModal] = useState(null);
  const [lostReasonDraft, setLostReasonDraft] = useState("");
  const [wonConfirmFrom, setWonConfirmFrom] = useState(null); // AC2 — Won confirm gate
  const [editTagsFor, setEditTagsFor] = useState(null);       // AC4 — tags edit toggle per card
  const [accounts, setAccounts] = useState([]);
  const [openCards, setOpenCards] = useState({});
  const toggleCard = (from) => setOpenCards((s) => ({ ...s, [from]: !s[from] }));

  const load = () => {
    fetch(`${LD_SERVER()}/leads-conv`)
      .then((r) => (r.ok ? r.json() : []))
      .then((rows) => setLeads(Array.isArray(rows) ? rows : []))
      .catch(() => setLeads([]));
  };

  useEffect(() => {
    load();
    fetch(`${LD_SERVER()}/accounts`)
      .then((r) => (r.ok ? r.json() : []))
      .then((rows) => setAccounts(Array.isArray(rows) ? rows : []))
      .catch(() => {});
  }, []);

  const patchAccount = (id, patch) => {
    fetch(`${LD_SERVER()}/accounts/${encodeURIComponent(id)}`, {
      method: "PATCH",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(patch),
    })
      .then((r) => (r.ok ? r.json() : Promise.reject(r)))
      .then((updated) => setAccounts((prev) => prev.map((a) => a.id === id ? updated : a)))
      .catch(() => window.toast && window.toast("Couldn't update account", "warn"));
  };

  // Live updates: when ANY tab (or the server itself) emits a lead_updated,
  // merge into our local state. The app-level SSE listener re-dispatches the
  // event on window so we don't need our own EventSource here.
  useEffect(() => {
    const handler = (e) => {
      const payload = (e && e.detail) || null;
      if (!payload || !payload.from) return;
      setLeads((prev) => {
        if (!prev) return prev;
        const idx = prev.findIndex((l) => l.from === payload.from);
        if (idx === -1) return prev;
        const next = prev.slice();
        next[idx] = {
          ...next[idx],
          stage:      payload.stage      ?? next[idx].stage,
          lostReason: payload.lostReason ?? next[idx].lostReason,
          notes:      payload.notes      ?? next[idx].notes,
          categories: payload.categories ?? next[idx].categories,
        };
        return next;
      });
      setNoteDraft((d) => {
        if (!(payload.from in d)) return d;
        if (d[payload.from] === payload.notes) return d;
        return { ...d, [payload.from]: payload.notes ?? "" };
      });
    };
    window.addEventListener("citrus-lead-updated", handler);
    return () => window.removeEventListener("citrus-lead-updated", handler);
  }, []);

  const patchLead = (from, patch) => {
    setSavingId(from);
    return fetch(`${LD_SERVER()}/leads-conv/${encodeURIComponent(from)}`, {
      method: "PATCH",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(patch),
    })
      .then((r) => (r.ok ? r.json() : Promise.reject(r)))
      .then((updated) => {
        setLeads((prev) => {
          if (!prev) return prev;
          const idx = prev.findIndex((l) => l.from === from);
          if (idx === -1) return prev;
          const next = prev.slice();
          next[idx] = { ...next[idx], ...updated };
          return next;
        });
      })
      .catch(() => {
        window.toast && window.toast("Couldn't save lead — retrying may help", "warn");
      })
      .finally(() => setSavingId(null));
  };

  const changeStage = (from, stage) => {
    if (stage === "lost") {
      setLostReasonDraft("");
      setLostModal({ from, pendingStage: "lost" });
      return;
    }
    if (stage === "won") {
      setWonConfirmFrom(from);
      return;
    }
    setLeads((prev) => prev && prev.map((l) => l.from === from ? { ...l, stage } : l));
    patchLead(from, { stage });
  };

  const confirmWon = () => {
    if (!wonConfirmFrom) return;
    const from = wonConfirmFrom;
    setLeads((prev) => prev && prev.map((l) => l.from === from ? { ...l, stage: "won" } : l));
    patchLead(from, { stage: "won" })
      .then(() => {
        // Refresh accounts list after handoff creates a new record
        fetch(`${LD_SERVER()}/accounts`)
          .then((r) => r.ok ? r.json() : [])
          .then((rows) => setAccounts(Array.isArray(rows) ? rows : []))
          .catch(() => {});
      });
    setWonConfirmFrom(null);
  };

  const confirmLost = () => {
    if (!lostModal || !lostReasonDraft) return;
    const { from } = lostModal;
    setLeads((prev) => prev && prev.map((l) => l.from === from ? { ...l, stage: "lost", lostReason: lostReasonDraft } : l));
    patchLead(from, { stage: "lost", lostReason: lostReasonDraft });
    setLostModal(null);
    setLostReasonDraft("");
  };

  // Toggle one classification tag on a lead — lets the operator correct the
  // agent's auto-classification. Keeps taxonomy order so chips stay stable.
  const toggleCategory = (from, key, current) => {
    const set = new Set(Array.isArray(current) ? current : []);
    if (set.has(key)) set.delete(key); else set.add(key);
    const next = LD_CATEGORY_ORDER.filter((k) => set.has(k));
    setLeads((prev) => prev && prev.map((l) => l.from === from ? { ...l, categories: next } : l));
    patchLead(from, { categories: next });
  };

  // Notes textarea: keep the draft local and PATCH on blur. Operators
  // type slowly and rethink wording — debouncing on keystroke would
  // produce a thrash of writes. Blur is the natural commit moment.
  const onNoteChange = (from, value) => {
    setNoteDraft((d) => ({ ...d, [from]: value }));
  };
  const flushNote = (from, currentServerValue) => {
    const draft = noteDraft[from];
    if (draft === undefined) return;
    if ((draft || "") === (currentServerValue || "")) return;
    patchLead(from, { notes: draft });
  };
  // On unmount, flush any pending timers so we don't lose typed notes.
  useEffect(() => {
    return () => {
      const timers = noteTimers.current || {};
      Object.values(timers).forEach((t) => clearTimeout(t));
    };
  }, []);

  // Open the conversation in the inbox. Two-step handshake:
  //   1. publish a "citrus-open-thread" event with the from id, which the
  //      inbox listens for to select that specific thread,
  //   2. switch the tab to inbox.
  // Falls back to onSwitchTab + sessionStorage if the inbox listener isn't
  // wired yet (the inbox might mount only after the switch).
  const openConversation = (from) => {
    try {
      sessionStorage.setItem("citrus_pending_inbox_thread", from);
    } catch { /* ignore */ }
    try {
      window.dispatchEvent(new CustomEvent("citrus-open-thread", { detail: { from } }));
    } catch { /* ignore */ }
    if (onSwitchTab) onSwitchTab("inbox");
  };

  const clearAllLeads = () => {
    if (!leads || leads.length === 0) return;
    if (!window.confirm(`Clear all ${leads.length} lead${leads.length !== 1 ? "s" : ""}?\n\nThis removes the captured name, company, stage, notes, and contact history from every conversation. The conversations themselves stay in the inbox. Learnings are not affected. Cannot be undone.`)) return;
    fetch(`${LD_SERVER()}/leads-conv`, { method: "DELETE" }).catch(() => {});
    setLeads([]);
    window.toast && window.toast(`Leads cleared — ${leads.length} lead${leads.length !== 1 ? "s" : ""} removed`, "warn");
  };

  // Export the currently-shown leads as a CSV that opens directly in Excel.
  // A UTF-8 BOM makes Excel read Arabic names correctly; CRLF line endings keep
  // Excel happy. Exports whatever the active filter shows (All by default).
  const exportCsv = () => {
    const rows = filtered;
    if (!rows.length) return;
    const STAGE = { new: "New", engaged: "Engaged", contacted: "Engaged", qualified: "Qualified", quoted: "Quoted", won: "Won", lost: "Lost" };
    const cols = ["Name", "Company", "Channel", "Agent", "Stage", "Tags", "Messages", "Captured", "Last contact", "Follow up", "Notes", "Contacts logged"];
    const esc = (v) => `"${String(v == null ? "" : v).replace(/"/g, '""')}"`;
    const fmt = (iso) => { if (!iso) return ""; const d = new Date(iso); return Number.isNaN(d.getTime()) ? "" : d.toLocaleDateString(); };
    const lines = [cols.map(esc).join(",")];
    for (const l of rows) {
      lines.push([
        ldCustomerLabel(l),
        l.customerCompany || "",
        l.channel || "",
        l.agentName || "",
        STAGE[l.stage || l.lifecycleStatus || "new"] || l.stage || "new",
        (Array.isArray(l.categories) ? l.categories : []).map((k) => (LD_CATEGORY_BY_ID[k] ? LD_CATEGORY_BY_ID[k].label : k)).join("; "),
        l.messageCount || 0,
        fmt(l.capturedAt),
        fmt(l.lastMessageAt),
        l.nextFollowUp || "",
        (l.notes || "").replace(/\s+/g, " ").trim(),
        (Array.isArray(l.contactLog) ? l.contactLog : []).map((c) => `${c.type}${c.outcome ? "/" + c.outcome : ""}`).join("; "),
      ].map(esc).join(","));
    }
    const blob = new Blob(["﻿" + lines.join("\r\n")], { type: "text/csv;charset=utf-8;" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `leads-${new Date().toISOString().slice(0, 10)}.csv`;
    document.body.appendChild(a); a.click(); a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 0);
  };

  // ---- Contact log + follow-up ----
  // Open the inline "log a contact" form for a lead, pre-set to a type.
  const openLog   = (from, type) => setLogForm((s) => ({ ...s, [from]: { type, outcome: "", note: "" } }));
  const closeLog  = (from) => setLogForm((s) => { const n = { ...s }; delete n[from]; return n; });
  const updateLog = (from, patch) => setLogForm((s) => ({ ...s, [from]: { ...(s[from] || {}), ...patch } }));
  const saveLog   = (from) => {
    const f = logForm[from];
    if (!f || !f.type) return;
    patchLead(from, { logContact: { type: f.type, outcome: f.outcome || null, note: (f.note || "").trim() } });
    closeLog(from);
    window.toast && window.toast("Contact logged", "good");
  };
  // Set / clear a lead's next follow-up date (YYYY-MM-DD from the date input).
  const setFollowUp = (from, dateStr) => patchLead(from, { nextFollowUp: dateStr || null });

  const matchesLifecycle = (l) => {
    const s = l.stage || l.lifecycleStatus || "new";
    if (filter === "all")     return true;
    if (filter === "active")  return !l.wrappedUp && s !== "won" && s !== "lost";
    if (filter === "wrapped") return !!l.wrappedUp;
    if (filter === "won")     return s === "won";
    if (filter === "lost")    return s === "lost";
    return true;
  };
  const matchesCategory = (l) =>
    catFilter === "all" || (Array.isArray(l.categories) && l.categories.includes(catFilter));

  const filtered = (leads || []).filter((l) => matchesLifecycle(l) && matchesCategory(l));

  const counts = useMemo(() => {
    const all = leads || [];
    return {
      all:     all.length,
      active:  all.filter((l) => { const s = l.stage || l.lifecycleStatus || "new"; return !l.wrappedUp && s !== "won" && s !== "lost"; }).length,
      wrapped: all.filter((l) => !!l.wrappedUp).length,
      won:     all.filter((l) => (l.stage || l.lifecycleStatus) === "won").length,
      lost:    all.filter((l) => (l.stage || l.lifecycleStatus) === "lost").length,
    };
  }, [leads]);

  // Per-category counts for the tag filter row — respects the active lifecycle
  // filter so the numbers match what clicking a tag would actually show.
  const catCounts = useMemo(() => {
    const base = (leads || []).filter(matchesLifecycle);
    const out = { all: base.length };
    for (const c of LD_CATEGORIES) out[c.id] = 0;
    for (const l of base) {
      for (const k of (Array.isArray(l.categories) ? l.categories : [])) {
        if (k in out) out[k]++;
      }
    }
    return out;
  }, [leads, filter]);

  const FILTERS = [
    { id: "all",     label: "All" },
    { id: "active",  label: "Active" },
    { id: "wrapped", label: "Wrapped" },
    { id: "won",     label: "Won" },
    { id: "lost",    label: "Lost" },
  ];

  if (leads === null) {
    return <div className="act-feed" style={{ padding: 24, color: "var(--ink-2)" }}>Loading leads…</div>;
  }

  return (
    <div className="act-page" style={{ padding: "16px 20px 40px" }}>

      {/* Won confirm modal — AC2: Won triggers account handoff so requires explicit confirmation */}
      {wonConfirmFrom && (
        <div style={{ position: "fixed", inset: 0, zIndex: 9999, display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(0,0,0,0.35)" }}
          onClick={(e) => { if (e.target === e.currentTarget) setWonConfirmFrom(null); }}>
          <div style={{ background: "var(--paper)", borderRadius: 14, padding: "24px 28px", width: 320, maxWidth: "90vw", boxShadow: "0 8px 32px rgba(0,0,0,0.18)" }}>
            <div style={{ fontFamily: "var(--display-font)", fontWeight: 700, fontSize: 17, marginBottom: 8 }}>Mark as Won?</div>
            <div style={{ fontSize: 13, color: "var(--ink-2)", marginBottom: 20, lineHeight: 1.5 }}>
              This creates an account record and hands the contact off to the post-sale agent. The lead stage will be locked as Won.
            </div>
            <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
              <button className="btn btn-ghost btn-sm" onClick={() => setWonConfirmFrom(null)}>Cancel</button>
              <button className="btn btn-sm" onClick={confirmWon}
                style={{ background: "#16A34A", color: "#fff", border: "none", fontWeight: 700, padding: "7px 18px", borderRadius: 8, cursor: "pointer" }}>
                ✓ Confirm Won
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Lost-reason modal — shown when operator clicks "Lost". Server rejects
          stage:lost without a lostReason so we gate it client-side too. */}
      {lostModal && (
        <div style={{ position: "fixed", inset: 0, zIndex: 9999, display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(0,0,0,0.35)" }}
          onClick={(e) => { if (e.target === e.currentTarget) { setLostModal(null); setLostReasonDraft(""); } }}>
          <div style={{ background: "var(--paper)", borderRadius: 14, padding: "24px 28px", width: 340, maxWidth: "90vw", boxShadow: "0 8px 32px rgba(0,0,0,0.18)" }}>
            <div style={{ fontFamily: "var(--display-font)", fontWeight: 700, fontSize: 17, marginBottom: 6 }}>Why did this lead not convert?</div>
            <div style={{ fontSize: 13, color: "var(--ink-2)", marginBottom: 16 }}>Required to mark as Lost. Helps track where leads fall off.</div>
            <div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 20 }}>
              {LD_LOST_REASONS.map((r) => (
                <label key={r.id} style={{ display: "flex", alignItems: "center", gap: 10, padding: "8px 12px", borderRadius: 8, border: `1.5px solid ${lostReasonDraft === r.id ? "var(--accent)" : "var(--border)"}`, background: lostReasonDraft === r.id ? "color-mix(in oklab, var(--accent) 8%, transparent)" : "var(--paper)", cursor: "pointer", fontSize: 13 }}>
                  <input type="radio" name="lostReason" value={r.id} checked={lostReasonDraft === r.id} onChange={() => setLostReasonDraft(r.id)} style={{ accentColor: "var(--accent)" }} />
                  {r.label}
                </label>
              ))}
            </div>
            <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
              <button className="btn btn-ghost btn-sm" onClick={() => { setLostModal(null); setLostReasonDraft(""); }}>Cancel</button>
              <button className="btn btn-primary btn-sm" disabled={!lostReasonDraft} onClick={confirmLost}>Confirm Lost</button>
            </div>
          </div>
        </div>
      )}

      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, marginBottom: 18, flexWrap: "wrap" }}>
        <div className="act-filters" style={{ marginBottom: 0 }}>
          {FILTERS.map((f) => (
            <button
              key={f.id}
              className={`act-chip ${filter === f.id ? "is-on" : ""}`}
              onClick={() => setFilter(f.id)}
            >
              {f.label}
              <span className="act-chip-c">{counts[f.id]}</span>
            </button>
          ))}
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          <button className="btn btn-ghost btn-sm" onClick={exportCsv} disabled={!(leads && leads.length)}
            title="Download the shown leads as a CSV (opens in Excel)">
            ⤓ Export
          </button>
          {leads && leads.length > 0 && (
            <button className="btn btn-ghost btn-sm" onClick={clearAllLeads}
              style={{ color: "var(--red, #dc2626)", opacity: 0.7 }}
              title="Remove all lead data — conversations stay in inbox">
              Clear all
            </button>
          )}
        </div>
      </div>

      {/* Classification filter — tag chips the agent assigns per conversation. */}
      <div className="act-filters" style={{ marginBottom: 16, display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center" }}>
        <span style={{ fontSize: 12, color: "var(--ink-soft)", marginRight: 2 }}>Tag:</span>
        <button
          className={`act-chip ${catFilter === "all" ? "is-on" : ""}`}
          onClick={() => setCatFilter("all")}
        >
          All
          <span className="act-chip-c">{catCounts.all}</span>
        </button>
        {LD_CATEGORIES.map((c) => (
          <button
            key={c.id}
            className={`act-chip ${catFilter === c.id ? "is-on" : ""}`}
            onClick={() => setCatFilter(catFilter === c.id ? "all" : c.id)}
            style={catFilter === c.id ? undefined : { color: c.fg, borderColor: c.bd }}
          >
            {c.label}
            <span className="act-chip-c">{catCounts[c.id] || 0}</span>
          </button>
        ))}
      </div>

      {/* ── Pipeline funnel (AC3, AC4, AC11) ── */}
      {leads && leads.length > 0 && (
        <PipelineFunnel leads={leads} onStageClick={(s) => setFilter(s)} />
      )}

      {/* ── Account lifecycle kanban ── */}
      {accounts.length > 0 && (
        <AccountKanban accounts={accounts} onPatch={patchAccount} />
      )}

      {filtered.length === 0 ? (
        <div className="kn-empty" style={{ padding: 32 }}>
          {leads.length === 0
            ? "No leads yet. Once your agents capture a customer name or company on a conversation, that thread will show up here."
            : "No leads match this filter."}
        </div>
      ) : null}

      <div style={{ display: "grid", gap: 12 }}>
        {filtered.map((lead, idx) => {
          const summary = lead.summaryStatus ? LD_SUMMARY[lead.summaryStatus] : null;
          const noteValue = noteDraft[lead.from] !== undefined ? noteDraft[lead.from] : (lead.notes || "");
          const isSaving = savingId === lead.from;
          const log = Array.isArray(lead.contactLog) ? lead.contactLog : [];
          const form = logForm[lead.from];
          const cur = lead.stage || lead.lifecycleStatus || "new";
          const isWon = cur === "won";
          const isLost = cur === "lost";
          const decided = isWon || isLost;
          const followDue = ldFollowUpDue(lead.nextFollowUp);
          const initial = (ldCustomerLabel(lead) || "?").trim().charAt(0).toUpperCase() || "?";
          const arrival = lead.channel || "message";
          // Pipeline stepper: 5 stages (new/engaged/qualified/quoted/won) + outcome node.
          const nodeIdx = decided ? LD_PIPELINE.length : Math.max(0, LD_PIPELINE.indexOf(cur === "contacted" ? "engaged" : cur));
          const journeyNodes = [
            { label: "New",       sub: `via ${arrival}` },
            { label: "Engaged",   sub: `${lead.messageCount || 0} msg${(lead.messageCount || 0) === 1 ? "" : "s"}` },
            { label: "Qualified", sub: log.length ? `${log.length} touch${log.length === 1 ? "" : "es"}` : "—" },
            { label: "Quoted",    sub: "—" },
            { label: "Won",       sub: isWon ? "✓" : "—" },
            { label: "Outcome",   sub: isWon ? "Won" : isLost ? "Lost" : "—" },
          ];
          const isOpen = !!openCards[lead.from];
          const temp = leadTemperature(lead);
          const ts   = temp ? TEMP_STYLE[temp] : null;
          const summaryText = lead.need || lead.summary || lead.lastMessage || "";
          const stageObj = LD_LIFECYCLE_BY_ID[cur] || LD_LIFECYCLE_BY_ID["new"];
          const stageLabel = isLost ? `Lost${lead.lostReason ? " · " + lead.lostReason.replace(/_/g, " ") : ""}` : stageObj.label;
          const stageCol = isWon ? "#0B7A57" : isLost ? "#B0234A" : (LD_STAGE_COLORS[nodeIdx] || LD_STAGE_COLORS[0]);
          return (
            <div key={lead.from} className="ld-card" style={{ background: "var(--paper)", border: "1px solid var(--border)", borderRadius: 12, padding: "16px 18px", animationDelay: `${Math.min(idx, 10) * 55}ms` }}>
              {/* Collapsed header — identity + priority badge + stage chip; click to expand */}
              <div onClick={() => toggleCard(lead.from)} style={{ display: "flex", alignItems: "flex-start", gap: 12, cursor: "pointer" }}>
                <div style={{ width: 38, height: 38, borderRadius: "50%", flexShrink: 0, display: "grid", placeItems: "center", fontFamily: "var(--display-font)", fontSize: 17, fontWeight: 600, color: "var(--paper)", background: "var(--accent)" }}>{initial}</div>
                <div style={{ minWidth: 0, flex: 1 }}>
                  {/* Identity row: name · company + priority badge + stage chip — AC5, AC8 */}
                  <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                    <span dir="auto" style={{ fontFamily: "var(--display-font)", fontWeight: 600, fontSize: 17, color: "var(--ink)" }}>{ldCustomerLabel(lead)}</span>
                    {lead.customerCompany ? <span dir="auto" style={{ fontSize: 13, color: "var(--ink-2)" }}>· {lead.customerCompany}</span> : null}
                    {ts ? (
                      <span style={{ fontSize: 11, fontWeight: 700, padding: "2px 9px", borderRadius: 999, whiteSpace: "nowrap", color: ts.fg, background: ts.bg, border: `1px solid ${ts.bd}` }}>
                        {ts.label === "High" ? "🔥 " : ""}{ts.label}
                      </span>
                    ) : null}
                    <span style={{ fontSize: 11, fontWeight: 600, padding: "2px 9px", borderRadius: 999, whiteSpace: "nowrap", color: stageCol, background: `color-mix(in oklab, ${stageCol} 13%, transparent)`, border: `1px solid color-mix(in oklab, ${stageCol} 32%, transparent)` }}>{stageLabel}</span>
                    {lead.takenOver ? <span className="ix-takeover-pill">you've taken over</span> : null}
                    {/* AC4: tags in collapsed header only; expanded body owns them when open */}
                    {!isOpen && <LdCategoryTags categories={lead.categories} />}
                    {Array.isArray(lead.assignedEmails) && lead.assignedEmails.length ? <span style={{ fontSize: 11, fontWeight: 600, padding: "2px 9px", borderRadius: 999, whiteSpace: "nowrap", color: "#065F46", background: "#ECFDF5", border: "1px solid #6EE7B7" }}>Assigned team</span> : null}
                  </div>
                  {summaryText ? (
                    <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.4, marginTop: 4, ...(isOpen ? {} : { overflow: "hidden", display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical" }) }}>{summaryText}</div>
                  ) : (
                    <div style={{ fontSize: 12, color: "var(--ink-soft)", fontStyle: "italic", marginTop: 4 }}>No summary yet — it's written when the chat wraps up.</div>
                  )}
                  <div style={{ display: "flex", gap: 10, fontSize: 12, color: "var(--ink-soft)", marginTop: 5, flexWrap: "wrap" }}>
                    <span>{lead.channel || "WhatsApp"}</span>
                    {lead.agentName ? <span>· {lead.agentName}</span> : null}
                    <span>· {lead.messageCount || 0} msg{(lead.messageCount || 0) === 1 ? "" : "s"}</span>
                    <span>· {ldRelative(lead.capturedAt)}</span>
                  </div>
                </div>
                <span aria-hidden="true" style={{ fontSize: 18, lineHeight: 1, color: "var(--ink-soft)", transform: isOpen ? "rotate(180deg)" : "none", transition: "transform .2s", flexShrink: 0, marginTop: 2 }}>⌄</span>
              </div>

              {isOpen ? (
              <React.Fragment>

              {/* ── AC3: Stage tracker — single accent color, progressive fill ── */}
              <div style={{ borderTop: "1px solid var(--border)", margin: "14px -2px 0", padding: "14px 2px 0" }}>
                <div style={{ display: "grid", gridTemplateColumns: `repeat(${journeyNodes.length}, 1fr)` }}>
                  {journeyNodes.map((node, i) => {
                    const reached = i <= nodeIdx;
                    const current = i === nodeIdx;
                    const isOutcomeNode = i === journeyNodes.length - 1;
                    const clickable = !isOutcomeNode && i < LD_PIPELINE.length;
                    const accentCol = isOutcomeNode && isLost ? "#DC2626" : isOutcomeNode && isWon ? "#16A34A" : "var(--accent, #2D6CDF)";
                    return (
                      <div key={i} style={{ position: "relative", display: "grid", justifyItems: "center", textAlign: "center", padding: "0 2px" }}>
                        {i > 0 && (
                          <div style={{ position: "absolute", top: 7, right: "50%", width: "100%", height: 2, background: reached ? "var(--accent, #2D6CDF)" : "var(--border)", zIndex: 0 }} />
                        )}
                        <button onClick={() => { if (clickable) changeStage(lead.from, LD_PIPELINE[i]); }}
                          disabled={isSaving || !clickable} title={clickable ? `Set to ${node.label}` : node.label}
                          style={{ position: "relative", zIndex: 1, width: current ? 18 : 14, height: current ? 18 : 14, borderRadius: "50%", padding: 0, cursor: clickable && !isSaving ? "pointer" : "default", background: reached ? accentCol : "var(--paper)", border: `2px solid ${reached ? accentCol : "var(--border)"}`, boxShadow: current && !decided ? `0 0 0 4px color-mix(in oklab, var(--accent, #2D6CDF) 18%, transparent)` : "none", transition: "all .15s" }} />
                        <div style={{ fontSize: 10, fontWeight: current ? 700 : 500, color: reached ? (isOutcomeNode ? accentCol : "var(--accent, #2D6CDF)") : "var(--ink-soft)", marginTop: 6 }}>{node.label}</div>
                        <div style={{ fontSize: 10, color: "var(--ink-soft)", marginTop: 1 }}>{node.sub}</div>
                      </div>
                    );
                  })}
                </div>
              </div>

              {/* Assigned team info block (from routing feature) */}
              {Array.isArray(lead.assignedEmails) && lead.assignedEmails.length ? (
                <div style={{ margin: "12px 0 0", padding: "10px 12px", border: "1px solid #BBF7D0", borderRadius: 10, background: "#F0FDF4" }}>
                  <div style={{ fontSize: 11, color: "#166534", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 6 }}>Assigned Team</div>
                  <div style={{ display: "flex", gap: 6, flexWrap: "wrap", alignItems: "center" }}>
                    {lead.assignedEmails.map((email) => <span key={email} style={{ fontSize: 12, fontWeight: 600, color: "#065F46", background: "#ECFDF5", border: "1px solid #86EFAC", borderRadius: 999, padding: "3px 9px" }}>{email}</span>)}
                    {lead.matchedKeyword ? <span style={{ fontSize: 12, color: "#166534" }}>Matched Topic: {lead.matchedKeyword}</span> : null}
                  </div>
                </div>
              ) : null}

              {/* ── AC2: Decision row — high-contrast Won/Lost, visually distinct ── */}
              <div style={{ borderTop: "1px solid var(--border)", margin: "14px 0 0", padding: "14px 0 0" }}>
                {decided ? (
                  <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap" }}>
                    <div style={{ fontWeight: 600, fontSize: 13, color: isWon ? "#16A34A" : "#DC2626" }}>
                      {isWon ? "✓ Won" : `✗ Lost${lead.lostReason ? " · " + lead.lostReason.replace(/_/g, " ") : ""}`}
                    </div>
                    <button className="btn btn-ghost btn-sm" onClick={() => changeStage(lead.from, "qualified")} disabled={isSaving} style={{ marginLeft: "auto" }}>Reopen</button>
                  </div>
                ) : (
                  <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
                    <button onClick={() => changeStage(lead.from, "won")} disabled={isSaving}
                      style={{ padding: "10px", fontSize: 13, fontWeight: 700, borderRadius: 8, cursor: isSaving ? "default" : "pointer", color: "#fff", background: "#16A34A", border: "none", display: "flex", alignItems: "center", justifyContent: "center", gap: 6 }}>
                      ✓ Won
                    </button>
                    <button onClick={() => changeStage(lead.from, "lost")} disabled={isSaving}
                      style={{ padding: "10px", fontSize: 13, fontWeight: 700, borderRadius: 8, cursor: isSaving ? "default" : "pointer", color: "#fff", background: "#DC2626", border: "none", display: "flex", alignItems: "center", justifyContent: "center", gap: 6 }}>
                      ✗ Lost
                    </button>
                  </div>
                )}
                {/* Follow-up date — lower weight than Won/Lost */}
                <div style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 12, color: followDue ? "#DC2626" : "var(--ink-soft)", marginTop: 10 }}>
                  <span style={{ whiteSpace: "nowrap" }}>{followDue ? "⏰ Follow up due" : "Follow up"}</span>
                  <input type="date" value={ldDateInputValue(lead.nextFollowUp)} onChange={(e) => setFollowUp(lead.from, e.target.value)}
                    style={{ padding: "3px 6px", fontSize: 12, borderRadius: 6, color: "var(--ink)", border: `1px solid ${followDue ? "#FCA5A5" : "var(--border)"}`, background: followDue ? "#FEF2F2" : "var(--paper)" }} />
                </div>
              </div>

              {/* ── AC4: Tags — ONE row with Edit toggle; not shown again in collapsed header ── */}
              <div style={{ borderTop: "1px solid var(--border)", margin: "12px 0 0", padding: "12px 0 0", display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                <LdCategoryTags categories={lead.categories} />
                <button className="btn btn-ghost btn-sm" style={{ marginLeft: "auto", fontSize: 11 }}
                  onClick={() => setEditTagsFor(editTagsFor === lead.from ? null : lead.from)}>
                  Edit tags
                </button>
                {editTagsFor === lead.from && (
                  <div style={{ width: "100%", display: "flex", gap: 6, flexWrap: "wrap", marginTop: 6 }}>
                    {LD_CATEGORIES.map((c) => {
                      const on = Array.isArray(lead.categories) && lead.categories.includes(c.id);
                      return (
                        <button key={c.id} onClick={() => toggleCategory(lead.from, c.id, lead.categories)} disabled={isSaving}
                          style={{ padding: "4px 10px", fontSize: 12, fontWeight: 600, borderRadius: 999, cursor: isSaving ? "default" : "pointer", color: on ? c.fg : "var(--ink-soft)", background: on ? c.bg : "var(--paper)", border: `1px solid ${on ? c.bd : "var(--border)"}` }}>
                          {on ? "✓ " : ""}{c.label}
                        </button>
                      );
                    })}
                  </div>
                )}
              </div>

              {/* ── AC1: "Their story" — Log contact collapsed behind ▾ button ── */}
              <div style={{ borderTop: "1px solid var(--border)", margin: "12px 0 0", padding: "12px 0 0" }}>
                <div style={{ display: "flex", alignItems: "center", marginBottom: 10 }}>
                  <span style={{ fontSize: 11, fontWeight: 700, color: "var(--ink-2)", textTransform: "uppercase", letterSpacing: "0.06em" }}>Their story</span>
                  <div style={{ marginLeft: "auto", position: "relative" }}>
                    <button className="btn btn-ghost btn-sm" style={{ fontSize: 12 }} disabled={isSaving}
                      onClick={() => setLogForm((s) => s[lead.from] ? ((() => { const n = {...s}; delete n[lead.from]; return n; })()) : { ...s, [lead.from]: { type: "call", outcome: "", note: "" } })}>
                      Log contact ▾
                    </button>
                  </div>
                </div>
                {form && (
                  <div style={{ marginBottom: 12, padding: "10px 12px", background: "color-mix(in oklab, var(--ink) 3%, transparent)", borderRadius: 8 }}>
                    <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 8 }}>
                      {LD_CONTACT_TYPES.map((t) => (
                        <button key={t.id} onClick={() => updateLog(lead.from, { type: t.id })}
                          style={{ display: "inline-flex", alignItems: "center", gap: 4, padding: "4px 10px", fontSize: 12, borderRadius: 8, cursor: "pointer", background: form.type === t.id ? "var(--ink)" : "var(--paper)", color: form.type === t.id ? "var(--paper)" : "var(--ink)", border: "1px solid var(--border)" }}>
                          <span>{t.icon}</span> {t.label}
                        </button>
                      ))}
                    </div>
                    <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
                      <select value={form.outcome} onChange={(e) => updateLog(lead.from, { outcome: e.target.value })}
                        style={{ padding: "5px 8px", fontSize: 12, border: "1px solid var(--border)", borderRadius: 6, background: "var(--paper)", color: "var(--ink)" }}>
                        <option value="">Outcome…</option>
                        {LD_OUTCOMES.map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
                      </select>
                      <input value={form.note} onChange={(e) => updateLog(lead.from, { note: e.target.value })} placeholder="Note (optional)"
                        onKeyDown={(e) => { if (e.key === "Enter") saveLog(lead.from); }}
                        style={{ flex: 1, minWidth: 140, padding: "5px 8px", fontSize: 12, border: "1px solid var(--border)", borderRadius: 6, background: "var(--paper)", color: "var(--ink)" }} />
                      <button className="btn btn-primary btn-sm" onClick={() => saveLog(lead.from)}>Save</button>
                      <button className="btn btn-ghost btn-sm" onClick={() => closeLog(lead.from)}>✕</button>
                    </div>
                  </div>
                )}
                <div style={{ display: "grid", gap: 8 }}>
                  {log.slice().reverse().map((ev) => {
                    const tm = LD_CONTACT_TYPE_BY_ID[ev.type];
                    const oc = ev.outcome ? LD_OUTCOME_BY_ID[ev.outcome] : null;
                    return (
                      <div key={ev.id || ev.at} style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
                        <span style={{ fontSize: 14, lineHeight: "18px" }}>{tm ? tm.icon : "•"}</span>
                        <div style={{ minWidth: 0, flex: 1 }}>
                          <div style={{ fontSize: 13, color: "var(--ink)" }}>
                            {tm ? tm.label : ev.type}
                            {oc ? <span style={{ color: "var(--ink-2)" }}> · {oc.label}</span> : null}
                            <span style={{ color: "var(--ink-2)", fontSize: 12 }}> · {ldRelative(ev.at)}</span>
                          </div>
                          {ev.note ? <div style={{ fontSize: 12, color: "var(--ink-2)", marginTop: 2 }}>{ev.note}</div> : null}
                        </div>
                      </div>
                    );
                  })}
                  <div style={{ display: "flex", gap: 10, alignItems: "center", color: "var(--ink-soft)" }}>
                    <span style={{ fontSize: 14 }}>✦</span>
                    <span style={{ fontSize: 12 }}>Showed up via {arrival} · {ldRelative(lead.capturedAt)}</span>
                  </div>
                </div>
              </div>

              {/* Notes + open conversation */}
              <div style={{ display: "flex", gap: 10, alignItems: "flex-end" }}>
                <textarea value={noteValue} placeholder="Notes — context, what they care about…" onChange={(e) => onNoteChange(lead.from, e.target.value)} onBlur={() => flushNote(lead.from, lead.notes)} rows={2}
                  style={{ flex: 1, boxSizing: "border-box", padding: "8px 10px", fontSize: 13, fontFamily: "inherit", border: "1px solid var(--border)", borderRadius: 8, background: "var(--paper-2, var(--paper))", color: "var(--ink)", resize: "vertical" }} />
                <button className="btn btn-ghost btn-sm" onClick={() => openConversation(lead.from)} style={{ whiteSpace: "nowrap" }}>Open conversation</button>
              </div>
              </React.Fragment>
              ) : null}
            </div>
          );
        })}
      </div>
    </div>
  );
}

window.LeadsTab = LeadsTab;

// ============ PIPELINE FUNNEL (AC3, AC4, AC11) ============
// Shows per-stage counts + conversion rates computed from real stageHistory.
// AC11: suppresses conversion % when stage count < MIN_RATE_THRESHOLD.

const FUNNEL_STAGES = [
  { id: "new",       label: "New",       col: "#2D6CDF" },
  { id: "engaged",   label: "Engaged",   col: "#E07B1A" },
  { id: "qualified", label: "Qualified", col: "#8B5CF6" },
  { id: "quoted",    label: "Quoted",    col: "#B45309" },
  { id: "won",       label: "Won",       col: "#0B7A57" },
];

function avgTimeInStage(leads, stageId) {
  const times = [];
  for (const l of leads) {
    const hist = Array.isArray(l.stageHistory) ? l.stageHistory : [];
    const entry = hist.find((h) => h.stage === stageId);
    if (!entry) continue;
    const next = hist.find((h, i) => i > hist.indexOf(entry));
    const endMs = next ? new Date(next.enteredAt).getTime() : Date.now();
    const startMs = new Date(entry.enteredAt).getTime();
    if (!isNaN(startMs) && !isNaN(endMs) && endMs > startMs) {
      times.push(endMs - startMs);
    }
  }
  if (!times.length) return null;
  const avg = times.reduce((a, b) => a + b, 0) / times.length;
  const hours = avg / 3600000;
  if (hours < 24) return `${Math.round(hours)}h avg`;
  return `${Math.round(hours / 24)}d avg`;
}

function PipelineFunnel({ leads, onStageClick }) {
  const stageCounts = {};
  const lostCounts  = {};
  for (const l of leads) {
    const s = l.stage || l.lifecycleStatus || "new";
    stageCounts[s] = (stageCounts[s] || 0) + 1;
    if (s === "lost" && l.lostReason) {
      lostCounts[l.lostReason] = (lostCounts[l.lostReason] || 0) + 1;
    }
  }
  const maxCount = Math.max(...FUNNEL_STAGES.map((s) => stageCounts[s.id] || 0), 1);

  return (
    <div style={{ background: "var(--paper)", border: "1px solid var(--border)", borderRadius: 12, padding: "18px 20px", marginBottom: 18 }}>
      <div style={{ fontFamily: "var(--display-font)", fontWeight: 700, fontSize: 15, marginBottom: 14 }}>Acquisition funnel</div>
      <div style={{ display: "grid", gap: 10 }}>
        {FUNNEL_STAGES.map((stage, i) => {
          const count = stageCounts[stage.id] || 0;
          const pct   = maxCount > 0 ? (count / maxCount) * 100 : 0;
          const prevCount = i > 0 ? (stageCounts[FUNNEL_STAGES[i - 1].id] || 0) : null;
          const convRate = (prevCount !== null && prevCount >= MIN_RATE_THRESHOLD && count >= 0)
            ? Math.round((count / prevCount) * 100) + "%"
            : null;
          const timeLabel = avgTimeInStage(leads, stage.id);
          return (
            <div key={stage.id}>
              {i > 0 && convRate ? (
                <div style={{ fontSize: 11, color: "var(--ink-soft)", paddingLeft: 8, marginBottom: 4 }}>
                  {convRate} converted from {FUNNEL_STAGES[i - 1].label}
                </div>
              ) : i > 0 && (stageCounts[FUNNEL_STAGES[i - 1].id] || 0) < MIN_RATE_THRESHOLD ? (
                <div style={{ fontSize: 11, color: "var(--ink-soft)", paddingLeft: 8, marginBottom: 4 }}>
                  — (too few leads for a reliable rate)
                </div>
              ) : null}
              <button
                onClick={() => onStageClick && onStageClick(stage.id === "won" ? "won" : "all")}
                style={{ display: "flex", alignItems: "center", gap: 12, width: "100%", background: "none", border: "none", padding: 0, cursor: "pointer", textAlign: "left" }}>
                <div style={{ width: 80, fontSize: 12, fontWeight: 600, color: stage.col, flexShrink: 0 }}>{stage.label}</div>
                <div style={{ flex: 1, position: "relative", height: 22, borderRadius: 4, background: "var(--border)" }}>
                  <div style={{ position: "absolute", left: 0, top: 0, height: "100%", width: `${pct}%`, minWidth: count > 0 ? 4 : 0, borderRadius: 4, background: stage.col, opacity: 0.8, transition: "width .4s" }} />
                </div>
                <div style={{ width: 36, fontSize: 13, fontWeight: 700, color: count > 0 ? stage.col : "var(--ink-soft)", textAlign: "right", flexShrink: 0 }}>{count}</div>
                {timeLabel ? <div style={{ width: 60, fontSize: 11, color: "var(--ink-soft)", flexShrink: 0 }}>{timeLabel}</div> : <div style={{ width: 60 }} />}
              </button>
            </div>
          );
        })}
        {/* Lost row */}
        {(stageCounts["lost"] || 0) > 0 && (
          <div>
            <button onClick={() => onStageClick && onStageClick("lost")}
              style={{ display: "flex", alignItems: "center", gap: 12, width: "100%", background: "none", border: "none", padding: 0, cursor: "pointer", textAlign: "left" }}>
              <div style={{ width: 80, fontSize: 12, fontWeight: 600, color: "#B0234A", flexShrink: 0 }}>Lost</div>
              <div style={{ flex: 1, position: "relative", height: 22, borderRadius: 4, background: "var(--border)" }}>
                <div style={{ position: "absolute", left: 0, top: 0, height: "100%", width: `${((stageCounts["lost"] || 0) / maxCount) * 100}%`, minWidth: 4, borderRadius: 4, background: "#B0234A", opacity: 0.6 }} />
              </div>
              <div style={{ width: 36, fontSize: 13, fontWeight: 700, color: "#B0234A", textAlign: "right", flexShrink: 0 }}>{stageCounts["lost"] || 0}</div>
              <div style={{ width: 60 }} />
            </button>
            {Object.keys(lostCounts).length > 0 && (
              <div style={{ paddingLeft: 92, marginTop: 6, display: "flex", gap: 8, flexWrap: "wrap" }}>
                {Object.entries(lostCounts).sort((a, b) => b[1] - a[1]).map(([reason, n]) => (
                  <span key={reason} style={{ fontSize: 11, padding: "1px 8px", borderRadius: 999, background: "#FEF2F2", color: "#9F1239", border: "1px solid #FCA5A5" }}>
                    {reason.replace(/_/g, " ")}: {n}
                  </span>
                ))}
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

window.PipelineFunnel = PipelineFunnel;

// ============ ACCOUNT KANBAN (account lifecycle view) ============
// Shown in the Leads tab below the funnel when accounts exist.

const ACCOUNT_STAGES = [
  { id: "order_confirmed", label: "Order confirmed", col: "#2D6CDF" },
  { id: "in_progress",     label: "In progress",     col: "#E07B1A" },
  { id: "delivered",       label: "Delivered",       col: "#8B5CF6" },
  { id: "paid",            label: "Paid",            col: "#0B7A57" },
  { id: "active",          label: "Active",          col: "#065F46" },
];

function AccountKanban({ accounts, onPatch }) {
  if (!accounts || accounts.length === 0) return null;

  const byStage = {};
  for (const s of ACCOUNT_STAGES) byStage[s.id] = [];
  for (const a of accounts) {
    const col = byStage[a.stage];
    if (col) col.push(a);
  }

  return (
    <div style={{ background: "var(--paper)", border: "1px solid var(--border)", borderRadius: 12, padding: "18px 20px", marginTop: 18 }}>
      <div style={{ fontFamily: "var(--display-font)", fontWeight: 700, fontSize: 15, marginBottom: 14 }}>Account lifecycle</div>
      <div style={{ display: "grid", gridTemplateColumns: `repeat(${ACCOUNT_STAGES.length}, minmax(140px, 1fr))`, gap: 10, overflowX: "auto" }}>
        {ACCOUNT_STAGES.map((stage) => {
          const cards = byStage[stage.id] || [];
          return (
            <div key={stage.id}>
              <div style={{ fontSize: 11, fontWeight: 700, color: stage.col, textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: 8 }}>
                {stage.label} <span style={{ fontWeight: 400, color: "var(--ink-soft)" }}>({cards.length})</span>
              </div>
              <div style={{ display: "grid", gap: 8 }}>
                {cards.map((a) => {
                  const enteredAt = Array.isArray(a.stageHistory) && a.stageHistory.length
                    ? a.stageHistory[a.stageHistory.length - 1].enteredAt
                    : a.createdAt;
                  const daysInStage = enteredAt
                    ? Math.floor((Date.now() - new Date(enteredAt).getTime()) / 86400000)
                    : null;
                  return (
                    <div key={a.id} style={{ background: "var(--paper-2, #F9FAFB)", border: "1px solid var(--border)", borderRadius: 8, padding: "10px 12px" }}>
                      <div style={{ fontSize: 13, fontWeight: 600, color: "var(--ink)", marginBottom: 2 }}>
                        {a.customerName || a.customerCompany || a.phone || a.id.slice(0, 8)}
                      </div>
                      {(a.customerName && a.customerCompany) ? (
                        <div style={{ fontSize: 11, color: "var(--ink-2)", marginBottom: 2 }}>{a.customerCompany}</div>
                      ) : null}
                      {(!a.customerName && !a.customerCompany && a.phone) ? null : (
                        a.phone ? <div style={{ fontSize: 11, color: "var(--ink-soft)" }}>{a.phone}</div> : null
                      )}
                      {a.orderValue != null && (
                        <div style={{ fontSize: 12, color: "var(--ink-2)" }}>
                          {Number(a.orderValue).toLocaleString()} EGP
                        </div>
                      )}
                      {daysInStage !== null && (
                        <div style={{ fontSize: 11, color: "var(--ink-soft)", marginTop: 4 }}>
                          {daysInStage === 0 ? "Today" : `${daysInStage}d in stage`}
                        </div>
                      )}
                      <select
                        value={a.stage}
                        onChange={(e) => onPatch && onPatch(a.id, { stage: e.target.value })}
                        style={{ marginTop: 8, width: "100%", fontSize: 11, padding: "3px 6px", border: "1px solid var(--border)", borderRadius: 6, background: "var(--paper)", color: "var(--ink)" }}>
                        {ACCOUNT_STAGES.map((s) => <option key={s.id} value={s.id}>{s.label}</option>)}
                        <option value="lost">Lost</option>
                      </select>
                    </div>
                  );
                })}
                {cards.length === 0 && (
                  <div style={{ fontSize: 12, color: "var(--ink-soft)", padding: "8px 0", fontStyle: "italic" }}>—</div>
                )}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

window.AccountKanban = AccountKanban;
