const { useState, useEffect } = React;

/* CRM · Deals — kanban pipeline, REAL deals only (no representative cards).
 * Columns come from the seeded pipeline templates (crm_pipelines); cards are the
 * user's own deals from crm_deal_board, grouped by stage key. Drag a card to
 * another column → crm_move_deal persists the new stage. Empty columns when the
 * user has no deals yet (honest — not fabricated). */

const AV = { JM: {}, RK: { bg: "#7c3aed18", fg: "#7c3aed", bd: "#7c3aed30" }, AL: { bg: "#15803d18", fg: "#15803d", bd: "#15803d30" }, TV: { bg: "#b4530918", fg: "#b45309", bd: "#b4530930" } };
function Avatar({ who, size = 22 }) {
  const a = AV[who] || {};
  const st = { width: size, height: size, fontSize: 9 };
  if (a.bg) { st.background = a.bg; st.color = a.fg; st.borderColor = a.bd; }
  return <span className="avatar" style={st}>{who}</span>;
}

const RAILS = ["var(--fg-3)", "var(--src-sba)", "var(--accent)", "var(--src-hedge)", "var(--amber)", "var(--green)", "var(--src-government)", "var(--red)"];
// Stage keys/labels mirror the seeded crm_pipelines templates so live deals
// (stage stored as the template key) land in the right column.
const STAGES = {
  leasing:  [["prospect", "Prospect"], ["tour", "Tour"], ["proposal", "Proposal"], ["loi", "LOI"], ["negotiation", "Lease Negotiation"], ["won", "Closed Won"], ["lost", "Closed Lost"]],
  invsales: [["sourcing", "Sourcing"], ["underwriting", "Underwriting"], ["loi", "LOI"], ["psa", "PSA"], ["dd", "Due Diligence"], ["closing", "Closing"], ["closed", "Closed"]],
  sublease: [["prospect", "Prospect"], ["tour", "Tour"], ["proposal", "Proposal"], ["loi", "LOI"], ["won", "Closed Won"], ["lost", "Closed Lost"]],
  lending:  [["lead", "Lead"], ["application", "Application"], ["underwriting", "Underwriting"], ["term_sheet", "Term Sheet"], ["credit_approval", "Credit Approval"], ["closing", "Closing"], ["funded", "Funded"], ["declined", "Declined"]],
};
const DEAL_TYPE_BOARD = { lease: "leasing", sale: "invsales", sublease: "sublease", loan: "lending" };
const BOARD_DEAL_TYPE = { leasing: "lease", invsales: "sale", sublease: "sublease", lending: "loan" };
const PERSONA_DEFAULT = { broker: "leasing", lender: "lending" };
const TABS = [
  { id: "leasing",  label: "CRE Leasing",        persona: "broker" },
  { id: "invsales", label: "Investment Sales",   persona: "broker" },
  { id: "sublease", label: "Sublease",           persona: "broker" },
  { id: "lending",  label: "Commercial Lending", persona: "lender" },
];
const fmtMoney = (n) => { n = Number(n) || 0; return n >= 1e6 ? "$" + (n / 1e6).toFixed(1) + "M" : n >= 1e3 ? "$" + Math.round(n / 1e3) + "K" : n ? "$" + n : "—"; };

// ── Tour schedule + itinerary helpers ───────────────────────────────────────
const GMAPS_KEY = "AIzaSyAQXSa9nMi6h1TGQvkQA3zpmqYiRWP6yr8";
const DWELL_MIN = 20; // minutes per showing
async function routeLegSeconds(stops) {
  const seq = (stops || []).filter((s) => s.lat != null && s.lng != null).slice(0, 25);
  if (seq.length < 2) return [];
  try {
    const body = {
      origin: { location: { latLng: { latitude: +seq[0].lat, longitude: +seq[0].lng } } },
      destination: { location: { latLng: { latitude: +seq[seq.length - 1].lat, longitude: +seq[seq.length - 1].lng } } },
      intermediates: seq.slice(1, -1).map((s) => ({ location: { latLng: { latitude: +s.lat, longitude: +s.lng } } })),
      travelMode: "DRIVE",
    };
    const res = await fetch("https://routes.googleapis.com/directions/v2:computeRoutes", {
      method: "POST", headers: { "Content-Type": "application/json", "X-Goog-Api-Key": GMAPS_KEY, "X-Goog-FieldMask": "routes.legs.duration" }, body: JSON.stringify(body),
    });
    if (!res.ok) return null;
    const j = await res.json();
    const legs = j.routes && j.routes[0] && j.routes[0].legs;
    return legs ? legs.map((l) => parseInt(String(l.duration || "0"), 10) || 0) : null;
  } catch (e) { return null; }
}
function fmtClock(min) {
  min = ((Math.round(min) % 1440) + 1440) % 1440;
  let h = Math.floor(min / 60); const m = min % 60; const ap = h < 12 ? "AM" : "PM"; h = h % 12 || 12;
  return h + ":" + String(m).padStart(2, "0") + " " + ap;
}
function buildSchedule(stops, legs, startMin) {
  const out = []; let t = startMin;
  for (let i = 0; i < stops.length; i++) {
    const arrive = t, depart = t + DWELL_MIN;
    const legMin = (legs && legs[i] != null) ? Math.round(legs[i] / 60) : null;
    out.push({ arrive, depart, legMin });
    t = depart + (legMin != null ? legMin : 0);
  }
  return out;
}
function itineraryText(deal, stops, sched) {
  const lines = [(deal.title || "Tour") + " — Showing tour", ""];
  stops.forEach((s, i) => {
    const w = (sched && sched[i]) ? " (" + fmtClock(sched[i].arrive) + "–" + fmtClock(sched[i].depart) + ")" : "";
    lines.push((i + 1) + ". " + (s.label || s.address || "") + w);
    if (s.parcel_ref) lines.push("   " + s.parcel_ref.replace("BBL:", "BBL "));
    (s.talking_points || []).forEach((p) => lines.push("   • " + p));
    if (sched && sched[i] && sched[i].legMin != null && i < stops.length - 1) lines.push("   → " + sched[i].legMin + " min drive to next stop");
    lines.push("");
  });
  return lines.join("\n");
}
const _p2 = (n) => String(n).padStart(2, "0");
function _icsLocal(min) { const d = new Date(); d.setHours(0, Math.round(min), 0, 0); return d.getFullYear() + _p2(d.getMonth() + 1) + _p2(d.getDate()) + "T" + _p2(d.getHours()) + _p2(d.getMinutes()) + "00"; }
function downloadICS(deal, stops, sched) {
  const esc = (s) => String(s || "").replace(/([,;\\])/g, "\\$1").replace(/\n/g, "\\n");
  const v = ["BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//AIContactIQ//Tour//EN", "CALSCALE:GREGORIAN"];
  stops.forEach((s, i) => {
    const sc = (sched && sched[i]) || { arrive: 540 + i * 40, depart: 560 + i * 40 };
    const desc = (s.talking_points || []).map((p) => "• " + p).join("\n");
    v.push("BEGIN:VEVENT", "UID:tour-" + deal.id + "-" + (s.id || i) + "@aicontactiq",
      "DTSTART:" + _icsLocal(sc.arrive), "DTEND:" + _icsLocal(sc.depart),
      "SUMMARY:" + esc("Showing " + (i + 1) + ": " + (s.label || s.address || "")),
      "LOCATION:" + esc(s.address || s.label || ""), "DESCRIPTION:" + esc(desc), "END:VEVENT");
  });
  v.push("END:VCALENDAR");
  const blob = new Blob([v.join("\r\n")], { type: "text/calendar" });
  const a = document.createElement("a"); a.href = URL.createObjectURL(blob);
  a.download = String(deal.title || "tour").replace(/[^a-z0-9]+/gi, "_").replace(/^_|_$/g, "") + "_tour.ics";
  document.body.appendChild(a); a.click(); a.remove();
  setTimeout(() => URL.revokeObjectURL(a.href), 2000);
}

// build empty boards keyed pipeline → [{key,label,rail,cards:[]}]
function emptyBoards() {
  const b = {};
  Object.keys(STAGES).forEach((p) => { b[p] = STAGES[p].map(([key, label], i) => ({ key, label, rail: RAILS[i % RAILS.length], cards: [] })); });
  return b;
}

function DealCard({ c, board, colIdx, stageKey, onDragStart, onOpen, onTour }) {
  return (
    <article className="deal-card" draggable="true" onClick={onOpen} style={{ cursor: "pointer" }}
      onDragStart={(e) => onDragStart(e, board, colIdx, c.id)}>
      <div className="dc-title">{c.title}</div>
      {(c.addr || c.bbl) && <div className="dc-addr">{c.addr} {c.bbl && <span className="bbl">{c.bbl}</span>}</div>}
      {c.org && <div className="dc-org">{c.org}</div>}
      <div className="dc-foot">
        <span className="dc-val">{c.val}{c.unit && <span className="unit"> {c.unit}</span>}</span>
        <span className="dc-spacer"></span>
        {c.scope && <span className={"scope-flag" + (c.scope === "shared" ? " shared" : "")}>{c.scope === "shared" ? <window.CrmIco.Team/> : <window.CrmIco.Lock/>}</span>}
        <Avatar who={c.who || "JM"}/>
      </div>
      {stageKey === "tour" && (
        <button className="btn btn-sm" style={{ marginTop: "8px", width: "100%", justifyContent: "center" }}
          onClick={(e) => { e.stopPropagation(); onTour && onTour(); }}>
          <window.CrmIco.Pin/> Showing list{c.tour_count ? " · " + c.tour_count : ""}
        </button>
      )}
    </article>
  );
}

/* Tour "showing list" — the properties a broker is showing the company. Backed by
 * crm_tour_stops; "Plan route" opens the same Google-Maps multi-stop route the map
 * uses (openRouteBuilder). */
function TourDrawer({ deal, onClose }) {
  const { toast } = React.useContext(window.CrmContext);
  const Ico = window.CrmIco;
  const [stops, setStops] = useState([]);
  const [loaded, setLoaded] = useState(false);
  const [addr, setAddr] = useState("");
  const [sug, setSug] = useState([]);
  const [sugOpen, setSugOpen] = useState(false);
  const [busy, setBusy] = useState(false);
  const dragIdx = React.useRef(null);
  const [overIdx, setOverIdx] = useState(null);
  const sugTimer = React.useRef(null);
  const [startMin, setStartMin] = useState(540); // 9:00 AM
  const [legs, setLegs] = useState(null);
  const client = window.ContactIQ && window.ContactIQ.client;

  const load = () => {
    if (!client) { setLoaded(true); return; }
    client.rpc("crm_tour_list", { p_deal_id: deal.id })
      .then(({ data, error }) => { if (!error) setStops(data || []); })
      .catch(() => {}).then(() => setLoaded(true));
  };
  useEffect(() => { load(); }, [deal.id]);

  // Pull real drive-leg times so the drawer shows the same timed plan as the map.
  useEffect(() => {
    let alive = true;
    routeLegSeconds(stops).then((l) => { if (alive) setLegs(l); });
    return () => { alive = false; };
  }, [stops]);
  const sched = buildSchedule(stops, legs, startMin);
  const hhmm = String(Math.floor(startMin / 60)).padStart(2, "0") + ":" + String(startMin % 60).padStart(2, "0");

  // Address autocomplete straight from our DB (address_suggest → parcel_ref + coords).
  useEffect(() => {
    if (!client) return;
    const q = addr.trim();
    clearTimeout(sugTimer.current);
    if (q.length < 3) { setSug([]); setSugOpen(false); return; }
    sugTimer.current = setTimeout(() => {
      client.rpc("address_suggest", { q, lim: 8 }).then(({ data, error }) => {
        if (!error) { setSug(data || []); setSugOpen((data || []).length > 0); }
      }).catch(() => {});
    }, 180);
    return () => clearTimeout(sugTimer.current);
  }, [addr]);

  // Add a picked suggestion (already resolved — no re-geocode).
  const addResolved = (s) => {
    if (!client) return;
    setBusy(true); setSugOpen(false);
    client.rpc("crm_tour_add_stop", {
      p_deal_id: deal.id, p_parcel_ref: s.parcel_ref || null,
      p_label: s.street || s.label,
      p_address: s.label || [s.street, s.city, s.state].filter(Boolean).join(", "),
      p_lat: s.latitude, p_lng: s.longitude,
    }).then(({ error }) => { if (error) toast("Couldn't add"); else { setAddr(""); setSug([]); load(); } })
      .catch(() => {}).then(() => setBusy(false));
  };
  // Enter / Add with free text → pick the top suggestion if any, else resolve by text.
  const addFreeText = () => {
    const a = addr.trim(); if (!a || !client) return;
    if (sug.length) { addResolved(sug[0]); return; }
    setBusy(true); setSugOpen(false);
    client.rpc("crm_tour_add", { p_deal_id: deal.id, p_address: a, p_zip: null })
      .then(({ error }) => { if (error) toast("Couldn't add"); else { setAddr(""); load(); } })
      .catch(() => {}).then(() => setBusy(false));
  };
  const remove = (id) => { if (client) client.rpc("crm_tour_remove", { p_stop_id: id }).then(() => load()).catch(() => {}); };

  // Drag to reorder stops; persist the new order.
  const onRowDrop = (toIdx) => {
    const from = dragIdx.current; dragIdx.current = null; setOverIdx(null);
    if (from == null || from === toIdx) return;
    const next = stops.slice();
    const [m] = next.splice(from, 1);
    next.splice(toIdx, 0, m);
    setStops(next);
    if (client) client.rpc("crm_tour_reorder", { p_deal_id: deal.id, p_ids: next.map((s) => s.id) }).catch(() => {});
  };

  // Plan the route on OUR own dedicated route map (separate clean page; it fetches
  // the stops and draws the driving route) — not the main prospecting map, not Google.
  const planRoute = () => {
    const usable = stops.filter((s) => (s.lat != null && s.lng != null) || s.address || s.label);
    if (usable.length < 2) { toast("Add at least 2 properties to plan a route"); return; }
    const mapDir = location.pathname.includes("/public-site/") ? "../" : "/map/";
    window.open(mapDir + "tour.html?deal=" + deal.id + "&title=" + encodeURIComponent(deal.title || ""), "_blank");
  };

  const inp = { width: "100%", height: "34px", border: "1px solid var(--border-2)", borderRadius: "var(--r-md)", padding: "0 10px", font: "500 13px var(--font-sans)", outline: "none" };
  return (
    <>
      <div className="scrim open" onClick={onClose}></div>
      <div style={{ position: "fixed", top: "10vh", left: "50%", transform: "translateX(-50%)", width: "520px", maxWidth: "94vw", maxHeight: "80vh", display: "flex", flexDirection: "column", background: "var(--bg)", border: "1px solid var(--border-1)", borderRadius: "var(--r-lg)", boxShadow: "var(--shadow-4)", zIndex: 60, overflow: "hidden" }}>
        <div className="card-head">
          <span className="ch-title">Showing list · {deal.title}</span><span className="ch-spacer"></span>
          <button className="dr-close" onClick={onClose}><Ico.X/></button>
        </div>
        <div style={{ padding: "12px 16px", display: "flex", gap: "8px", borderBottom: "1px solid var(--border-1)", position: "relative" }}>
          <div style={{ flex: 1, position: "relative" }}>
            <input autoFocus value={addr} autoComplete="off"
              onChange={(e) => setAddr(e.target.value)}
              onKeyDown={(e) => { if (e.key === "Enter") addFreeText(); if (e.key === "Escape") setSugOpen(false); }}
              onFocus={() => { if (sug.length) setSugOpen(true); }}
              onBlur={() => setTimeout(() => setSugOpen(false), 160)}
              placeholder="Search an address…" style={{ ...inp, width: "100%" }}/>
            {sugOpen && (
              <div style={{ position: "absolute", top: "38px", left: 0, right: 0, zIndex: 70, background: "var(--bg)", border: "1px solid var(--border-1)", borderRadius: "var(--r-md)", boxShadow: "var(--shadow-3)", maxHeight: "260px", overflowY: "auto" }}>
                {sug.map((s, i) => (
                  <div key={i} onMouseDown={(e) => { e.preventDefault(); addResolved(s); }}
                    style={{ padding: "8px 10px", cursor: "pointer", borderBottom: i < sug.length - 1 ? "1px solid var(--border-1)" : "none" }}
                    onMouseEnter={(e) => e.currentTarget.style.background = "var(--bg-2)"}
                    onMouseLeave={(e) => e.currentTarget.style.background = "transparent"}>
                    <div style={{ font: "600 13px var(--font-sans)" }}>{s.street || s.label}</div>
                    <div style={{ font: "500 11px var(--font-mono)", color: "var(--fg-3)" }}>
                      {[s.city, s.state, s.zip].filter(Boolean).join(", ")}{s.match_count > 1 ? "  · " + s.match_count + " here" : ""}
                    </div>
                  </div>
                ))}
              </div>
            )}
          </div>
          <button className="btn btn-primary btn-sm" disabled={busy} onClick={addFreeText}><Ico.Plus s={12}/>{busy ? "…" : "Add"}</button>
        </div>
        {stops.length > 0 && (
          <div style={{ padding: "8px 16px", display: "flex", alignItems: "center", gap: "8px", borderBottom: "1px solid var(--border-1)", fontSize: "var(--fs-sm)", color: "var(--fg-2)" }}>
            <span>Start</span>
            <input type="time" value={hhmm} onChange={(e) => { const p = (e.target.value || "09:00").split(":"); setStartMin((+p[0] || 0) * 60 + (+p[1] || 0)); }}
              style={{ height: "30px", border: "1px solid var(--border-2)", borderRadius: "var(--r-md)", padding: "0 8px", font: "600 12px var(--font-sans)" }}/>
            <span className="muted">· {DWELL_MIN} min/stop{legs && legs.length ? "" : " · drive times loading…"}</span>
            {sched.length > 0 && <span style={{ marginLeft: "auto", font: "700 12px var(--font-sans)", color: "var(--accent)" }}>{fmtClock(sched[0].arrive)}–{fmtClock(sched[sched.length - 1].depart)}</span>}
          </div>
        )}
        <div style={{ flex: 1, overflowY: "auto", padding: "8px 16px" }}>
          {!loaded ? <div className="muted" style={{ padding: "16px", textAlign: "center" }}>Loading…</div>
            : !stops.length ? <div className="muted" style={{ padding: "20px", textAlign: "center", fontSize: "var(--fs-base)" }}>No properties yet. Add the addresses you're showing this company, then Plan route.</div>
            : stops.map((s, i) => (
              <div key={s.id} draggable="true"
                onDragStart={() => { dragIdx.current = i; }}
                onDragOver={(e) => { e.preventDefault(); setOverIdx(i); }}
                onDragLeave={() => setOverIdx((o) => (o === i ? null : o))}
                onDrop={() => onRowDrop(i)}
                style={{ display: "flex", alignItems: "flex-start", gap: "10px", padding: "10px 4px", borderBottom: "1px solid var(--border-1)", borderTop: overIdx === i ? "2px solid var(--accent)" : "2px solid transparent", cursor: "grab" }}>
                <span title="Drag to reorder" style={{ color: "var(--fg-3)", cursor: "grab", fontSize: "14px", lineHeight: 1.4 }}>⠿</span>
                <span style={{ font: "700 12px var(--font-mono)", color: "var(--fg-3)", width: "16px", lineHeight: 1.4 }}>{i + 1}</span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ font: "600 13px var(--font-sans)" }}>{s.label || s.address}</div>
                  <div style={{ font: "500 11px var(--font-mono)", color: "var(--fg-3)" }}>
                    {s.parcel_ref ? s.parcel_ref.replace("BBL:", "BBL ") : (s.address || "")}{(s.lat != null) ? "" : "  · not geocoded"}
                  </div>
                  {sched[i] && (
                    <div style={{ font: "700 11px var(--font-sans)", color: "var(--accent)", marginTop: "2px" }}>
                      {fmtClock(sched[i].arrive)} – {fmtClock(sched[i].depart)}
                      {sched[i].legMin != null && i < stops.length - 1 ? "  ·  " + sched[i].legMin + " min drive →" : ""}
                    </div>
                  )}
                  {Array.isArray(s.talking_points) && s.talking_points.length > 0 && (
                    <ul style={{ margin: "5px 0 0", paddingLeft: "15px" }}>
                      {s.talking_points.map((p, k) => <li key={k} style={{ font: "500 11.5px/1.45 var(--font-sans)", color: "var(--fg-2)", marginBottom: "1px" }}>{p}</li>)}
                    </ul>
                  )}
                </div>
                <button className="row-action" style={{ background: "none", border: 0, color: "var(--fg-3)", cursor: "pointer", alignSelf: "flex-start" }} onClick={() => remove(s.id)}>Remove</button>
              </div>
            ))}
        </div>
        <div style={{ padding: "12px 16px", display: "flex", justifyContent: "flex-end", alignItems: "center", gap: "8px", flexWrap: "wrap", borderTop: "1px solid var(--border-1)" }}>
          <span className="muted" style={{ fontSize: "var(--fs-sm)", marginRight: "auto" }}>{stops.length} {stops.length === 1 ? "property" : "properties"}</span>
          <button className="btn btn-sm" disabled={!stops.length}
            onClick={() => { try { navigator.clipboard.writeText(itineraryText(deal, stops, sched)); toast("Itinerary copied"); } catch (e) { toast("Copy failed"); } }}>Copy itinerary</button>
          <button className="btn btn-sm" disabled={!stops.length} onClick={() => downloadICS(deal, stops, sched)}>Calendar (.ics)</button>
          <button className="btn btn-primary btn-sm" onClick={planRoute}><Ico.Pin/> Plan route (map)</button>
        </div>
      </div>
    </>
  );
}

function DealsBoard() {
  const { persona, toast, go } = React.useContext(window.CrmContext);
  const Ico = window.CrmIco;
  const [pipe, setPipe] = useState(PERSONA_DEFAULT[persona]);
  const [boards, setBoards] = useState(emptyBoards);
  const [loaded, setLoaded] = useState(false);
  const dragRef = React.useRef(null);
  const [overCol, setOverCol] = useState(null);
  const [creating, setCreating] = useState(false);
  const [form, setForm] = useState({ title: "", value: "" });
  const [tourDeal, setTourDeal] = useState(null);

  const submitDeal = () => {
    const title = form.title.trim(); if (!title) { setCreating(false); return; }
    const c = window.ContactIQ && window.ContactIQ.client;
    if (c) c.rpc("crm_create_deal", { p_title: title, p_deal_type: BOARD_DEAL_TYPE[pipe], p_value_usd: form.value ? Number(String(form.value).replace(/[^0-9.]/g, "")) || null : null, p_stage: STAGES[pipe][0][0] }).then(() => loadDeals()).catch(() => {});
    toast("Deal created in " + (TABS.find((t) => t.id === pipe) || {}).label);
    setForm({ title: "", value: "" }); setCreating(false);
  };

  useEffect(() => { setPipe(PERSONA_DEFAULT[persona]); }, [persona]);

  // Load the user's real deals into the matching board/stage.
  const loadDeals = () => {
    const c = window.ContactIQ && window.ContactIQ.client;
    if (!c) { setLoaded(true); return; }
    c.rpc("crm_deal_board", {}).then(({ data, error }) => {
      const next = emptyBoards();
      if (!error && data) data.forEach((d) => {
        const cols = next[DEAL_TYPE_BOARD[d.deal_type]]; if (!cols) return;
        let idx = cols.findIndex((col) => col.key === String(d.stage || "").toLowerCase());
        if (idx < 0) idx = 0;
        cols[idx].cards.push({ id: String(d.id), title: d.title, addr: (d.parcel_ref || "").replace("BBL:", "BBL ") || "", bbl: "", org: "", val: fmtMoney(d.value_usd), amt: Number(d.value_usd) || 0, who: "JM", scope: "shared", parcel_ref: d.parcel_ref, tour_count: Number(d.tour_count) || 0 });
      });
      setBoards(next);
    }).catch(() => {}).then(() => setLoaded(true));
  };
  useEffect(() => { loadDeals(); }, []);

  const onDragStart = (e, board, colIdx, id) => { dragRef.current = { board, colIdx, id }; try { e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", id); } catch (_) {} };
  const onDrop = (board, toIdx) => {
    const drag = dragRef.current;
    dragRef.current = null; setOverCol(null);
    if (!drag || drag.board !== board || drag.colIdx === toIdx) return;
    // Read the move from CURRENT state (synchronously) — NOT from inside the
    // setBoards updater, which runs async (that made the save get skipped, so
    // drags didn't persist). Compute id/stage here, then optimistic UI + save.
    const cols = boards[board]; if (!cols) return;
    const fromCol = cols[drag.colIdx];
    const card = fromCol && fromCol.cards.find((c) => c.id === drag.id);
    if (!card) return;
    const toKey = cols[toIdx].key, toLabel = cols[toIdx].label;
    setBoards((prev) => {
      const next = JSON.parse(JSON.stringify(prev));
      const f = next[board][drag.colIdx];
      const fi = f.cards.findIndex((c) => c.id === drag.id);
      if (fi < 0) return prev;
      next[board][toIdx].cards.push(f.cards.splice(fi, 1)[0]);
      return next;
    });
    if (/^\d+$/.test(String(card.id))) {
      const c = window.ContactIQ && window.ContactIQ.client;
      if (c) c.rpc("crm_move_deal", { p_deal_id: Number(card.id), p_stage: toKey })
        .then(({ error }) => { toast(error ? ("Move didn't save: " + (error.message || "error")) : ("Moved “" + card.title + "” → " + toLabel)); if (error) loadDeals(); })
        .catch(() => toast("Move didn't save"));
    } else {
      toast("Moved “" + card.title + "” → " + toLabel);
    }
  };

  const cols = boards[pipe] || [];
  const totalDeals = cols.reduce((n, c) => n + c.cards.length, 0);

  return (
    <div className="page wide" style={{ paddingBottom: "24px" }}>
      <div className="page-head" style={{ marginBottom: "14px" }}>
        <div className="ph-text"><div className="eyebrow">Pipeline</div><h1 className="page-title">Deals</h1></div>
      </div>

      <div className="deals-toolbar">
        <div className="pipeline-tabs">
          {TABS.map((t) => (
            <button key={t.id} className={(t.persona === "broker" ? "broker-only" : "lender-only") + (pipe === t.id ? " on" : "")} onClick={() => setPipe(t.id)}>
              {t.label} <span className="cnt">{(boards[t.id] || []).reduce((n, c) => n + c.cards.length, 0)}</span>
            </button>
          ))}
        </div>
        <div className="top-spacer"></div>
        <button className="btn btn-sm" onClick={() => toast("Filters — assignee, metro, value, scope")}>
          <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"><path d="M2 3h12l-4.5 5.5V13l-3-1.5V8.5z"/></svg>Filter
        </button>
        <button className="btn btn-primary btn-sm" onClick={() => setCreating(true)}><Ico.Plus s={13}/>New deal</button>
      </div>

      {loaded && totalDeals === 0 && (
        <div className="card" style={{ padding: "26px", textAlign: "center", color: "var(--fg-2)", marginBottom: "16px" }}>
          No deals in this pipeline yet. Convert a <a className="row-action" onClick={() => go("crm_signals")} style={{ display: "inline-flex" }}>Signal</a> or start one from a building's tenant roster — they'll appear here.
        </div>
      )}

      <div className="kanban">
        {cols.map((col, ci) => (
          <section className="kanban-col" key={col.key}>
            <div className="col-head">
              <div className="ch-row">
                <span className="col-stage">{col.label}</span>
                <span className="col-count">{col.cards.length}</span>
                <span className="col-sum">{(() => { const s = col.cards.reduce((t, c) => t + (c.amt || 0), 0); return s ? fmtMoney(s) : ""; })()}</span>
              </div>
              <div className="col-rail" style={{ background: col.rail }}></div>
            </div>
            <div className={"col-body" + (overCol === ci ? " dragover" : "")}
              onDragOver={(e) => { if (dragRef.current && dragRef.current.board === pipe) { e.preventDefault(); setOverCol(ci); } }}
              onDragLeave={() => setOverCol((o) => (o === ci ? null : o))}
              onDrop={() => onDrop(pipe, ci)}>
              {col.cards.map((c) => (
                <DealCard key={c.id} c={c} board={pipe} colIdx={ci} stageKey={col.key} onDragStart={onDragStart}
                  onOpen={() => { if (c.parcel_ref) go("crm_building"); else toast(c.title); }}
                  onTour={() => setTourDeal({ id: Number(c.id), title: c.title })}/>
              ))}
            </div>
          </section>
        ))}
      </div>

      {tourDeal && <TourDrawer deal={tourDeal} onClose={() => setTourDeal(null)}/>}

      {creating && (
        <>
          <div className="scrim open" onClick={() => setCreating(false)}></div>
          <div style={{ position: "fixed", top: "16vh", left: "50%", transform: "translateX(-50%)", width: "460px", maxWidth: "94vw", background: "var(--bg)", border: "1px solid var(--border-1)", borderRadius: "var(--r-lg)", boxShadow: "var(--shadow-4)", zIndex: 60, overflow: "hidden" }}>
            <div className="card-head"><span className="ch-title">New deal · {(TABS.find((t) => t.id === pipe) || {}).label}</span><span className="ch-spacer"></span><button className="dr-close" onClick={() => setCreating(false)}><Ico.X/></button></div>
            <div style={{ padding: "14px 16px", display: "flex", flexDirection: "column", gap: "10px" }}>
              <label style={{ fontSize: "var(--fs-sm)", color: "var(--fg-2)" }}>Title
                <input autoFocus value={form.title} onChange={(e) => setForm((s) => ({ ...s, title: e.target.value }))} onKeyDown={(e) => { if (e.key === "Enter") submitDeal(); }}
                  placeholder="e.g. Acme — HQ relocation" style={{ width: "100%", marginTop: "4px", height: "36px", border: "1px solid var(--border-2)", borderRadius: "var(--r-md)", padding: "0 12px", font: "500 13px var(--font-sans)", outline: "none" }}/>
              </label>
              <label style={{ fontSize: "var(--fs-sm)", color: "var(--fg-2)" }}>Value {pipe === "lending" ? "(loan amount)" : pipe === "leasing" || pipe === "sublease" ? "(annual / total)" : "(price)"}
                <input value={form.value} onChange={(e) => setForm((s) => ({ ...s, value: e.target.value }))} onKeyDown={(e) => { if (e.key === "Enter") submitDeal(); }}
                  placeholder="$ optional" style={{ width: "100%", marginTop: "4px", height: "36px", border: "1px solid var(--border-2)", borderRadius: "var(--r-md)", padding: "0 12px", font: "500 13px var(--font-mono)", outline: "none" }}/>
              </label>
              <div style={{ display: "flex", justifyContent: "flex-end", gap: "8px", marginTop: "2px" }}>
                <button className="btn btn-sm" onClick={() => setCreating(false)}>Cancel</button>
                <button className="btn btn-primary btn-sm" onClick={submitDeal}><Ico.Plus s={12}/>Create deal</button>
              </div>
            </div>
          </div>
        </>
      )}
    </div>
  );
}

function CrmDealsPage({ go, user }) {
  return <window.CrmShell go={go} user={user} current="deals"><DealsBoard/></window.CrmShell>;
}
window.CrmDealsPage = CrmDealsPage;
