/* Tract Console v2 — primitives, rich-text engine, and the shared state store
   (the 5-stage state machine lives here). */
const { useState, useEffect, useRef, useLayoutEffect, useSyncExternalStore } = React;
const D = window.V2DATA;

/* ---------------- icons ---------------- */
function Icon({ name, size = 18, color, strokeWidth = 1.9, style }) {
  const ref = useRef(null);
  useEffect(() => {
    if (!ref.current || !window.lucide) return;
    const node = window.lucide.createElement(window.lucide.icons[toPascal(name)] || window.lucide.icons.Circle);
    node.setAttribute("width", size); node.setAttribute("height", size);
    node.setAttribute("stroke-width", strokeWidth);
    ref.current.innerHTML = ""; ref.current.appendChild(node);
  }, [name, size, strokeWidth]);
  return <span ref={ref} style={{ display: "inline-flex", color: color || "currentColor", ...style }} />;
}
function toPascal(s) { return s.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(""); }

/* ---------------- avatars ---------------- */
const ROLE_COLOR = { advisor: "#2f6f4e", buyer: "#3b5b8c", attorney: "#7a4f86", admin: "#8a5a2b", tenant: "#6a6c66" };
function Avatar({ who, size = 30 }) {
  const p = typeof who === "string" ? (D.PEOPLE[who] || { name: who, role: "tenant" }) : who;
  const initials = (p.name || "?").split(" ").map((w) => w[0]).slice(0, 2).join("").toUpperCase();
  return <span className="ava" style={{ width: size, height: size, fontSize: size * 0.4, background: ROLE_COLOR[p.role] || "#6a6c66" }}>{initials}</span>;
}

/* ---------------- markdown → html ---------------- */
function esc(s) { return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); }
function inlineMd(s) {
  return esc(s)
    .replace(/`([^`]+)`/g, (_, c) => "<code>" + c + "</code>")
    .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
    .replace(/(^|[^*])\*([^*]+)\*/g, "$1<em>$2</em>")
    .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noreferrer">$1</a>');
}
function mdToHtml(src) {
  if (!src) return "";
  const lines = src.replace(/\r/g, "").split("\n");
  let html = "", i = 0, list = null;
  const closeList = () => { if (list) { html += "</" + list + ">"; list = null; } };
  while (i < lines.length) {
    let ln = lines[i];
    if (/^```/.test(ln)) {
      closeList(); i++; let code = [];
      while (i < lines.length && !/^```/.test(lines[i])) { code.push(esc(lines[i])); i++; }
      i++; html += "<pre><code>" + code.join("\n") + "</code></pre>"; continue;
    }
    if (/^\s*$/.test(ln)) { closeList(); i++; continue; }
    let m;
    if ((m = ln.match(/^(#{1,3})\s+(.*)$/))) { closeList(); html += "<h" + m[1].length + ">" + inlineMd(m[2]) + "</h" + m[1].length + ">"; i++; continue; }
    if (/^\s*([-*])\s+/.test(ln)) { if (list !== "ul") { closeList(); list = "ul"; html += "<ul>"; } html += "<li>" + inlineMd(ln.replace(/^\s*[-*]\s+/, "")) + "</li>"; i++; continue; }
    if (/^\s*\d+\.\s+/.test(ln)) { if (list !== "ol") { closeList(); list = "ol"; html += "<ol>"; } html += "<li>" + inlineMd(ln.replace(/^\s*\d+\.\s+/, "")) + "</li>"; i++; continue; }
    if (/^\s*>\s?/.test(ln)) { closeList(); html += "<blockquote>" + inlineMd(ln.replace(/^\s*>\s?/, "")) + "</blockquote>"; i++; continue; }
    if (/^\s*---\s*$/.test(ln)) { closeList(); html += "<hr/>"; i++; continue; }
    // paragraph (merge following non-empty, non-special lines)
    closeList(); let para = [ln]; i++;
    while (i < lines.length && !/^\s*$/.test(lines[i]) && !/^(#{1,3}\s|```|\s*([-*]|\d+\.)\s|\s*>|\s*---\s*$)/.test(lines[i])) { para.push(lines[i]); i++; }
    html += "<p>" + para.map(inlineMd).join("<br/>") + "</p>";
  }
  closeList();
  return html;
}

/* ---------------- sanitize pasted/typed html ---------------- */
const ALLOWED = new Set(["P", "BR", "B", "STRONG", "I", "EM", "U", "A", "UL", "OL", "LI", "H1", "H2", "H3", "CODE", "PRE", "BLOCKQUOTE", "SPAN", "DIV", "TABLE", "THEAD", "TBODY", "TR", "TH", "TD"]);
function sanitizeHtml(html) {
  const doc = new DOMParser().parseFromString("<div>" + html + "</div>", "text/html");
  const root = doc.body.firstChild;
  (function walk(node) {
    [...node.childNodes].forEach((c) => {
      if (c.nodeType === 1) {
        if (!ALLOWED.has(c.tagName)) { while (c.firstChild) node.insertBefore(c.firstChild, c); node.removeChild(c); return; }
        [...c.attributes].forEach((a) => { if (!(c.tagName === "A" && a.name === "href")) c.removeAttribute(a.name); });
        if (c.tagName === "A") { c.setAttribute("target", "_blank"); c.setAttribute("rel", "noreferrer"); }
        if (c.tagName === "DIV") { const p = doc.createElement("p"); while (c.firstChild) p.appendChild(c.firstChild); node.replaceChild(p, c); walk(p); return; }
        walk(c);
      }
    });
  })(root);
  return root.innerHTML.trim();
}
function RichText({ html }) { return <div className="rt" dangerouslySetInnerHTML={{ __html: html }} />; }

/* ---------------- popover ---------------- */
function Popover({ anchorRef, onClose, children, align = "left", width }) {
  const ref = useRef(null);
  const [pos, setPos] = useState(null);
  useLayoutEffect(() => {
    if (!anchorRef.current) return;
    const r = anchorRef.current.getBoundingClientRect();
    setPos({ top: r.bottom + 6, left: align === "right" ? r.right - (width || 200) : r.left, bottom: r.top });
  }, []);
  useEffect(() => {
    const h = (e) => { if (ref.current && !ref.current.contains(e.target) && !(anchorRef.current && anchorRef.current.contains(e.target))) onClose(); };
    const k = (e) => e.key === "Escape" && onClose();
    document.addEventListener("mousedown", h); document.addEventListener("keydown", k);
    return () => { document.removeEventListener("mousedown", h); document.removeEventListener("keydown", k); };
  }, []);
  if (!pos) return null;
  const w = width || 200;
  const left = Math.max(8, Math.min(pos.left, window.innerWidth - w - 8));
  const overflowsDown = pos.top + 260 > window.innerHeight;
  const top = overflowsDown ? Math.max(8, pos.bottom - 6 - 260) : pos.top;
  return <div ref={ref} className="pop" style={{ top, left, width }}>{children}</div>;
}

/* ---------------- modal / toast ---------------- */
function Modal({ icon, iconColor, title, onClose, footer, children, width }) {
  useEffect(() => { const k = (e) => e.key === "Escape" && onClose(); document.addEventListener("keydown", k); return () => document.removeEventListener("keydown", k); }, []);
  return (
    <div className="scrim" onMouseDown={(e) => e.target === e.currentTarget && onClose()}>
      <div className="modal" style={width ? { width } : null}>
        <div className="modal__h">{icon && <span style={{ width: 30, height: 30, borderRadius: 8, background: (iconColor || "var(--ink)") + "1a", color: iconColor || "var(--ink)", display: "inline-flex", alignItems: "center", justifyContent: "center" }}><Icon name={icon} size={17} /></span>}<span style={{ flex: 1 }}>{title}</span><button className="cbtn" onClick={onClose}><Icon name="x" size={18} /></button></div>
        <div className="modal__b">{children}</div>
        {footer && <div className="modal__f">{footer}</div>}
      </div>
    </div>
  );
}
function Toast({ msg, onDone }) { useEffect(() => { const t = setTimeout(onDone, 2600); return () => clearTimeout(t); }, []); return <div className="toast"><Icon name="check" size={16} color="var(--green)" />{msg}</div>; }

/* ---------------- rich composer ---------------- */
function Composer({ placeholder, onSend, sendLabel = "Send", compact }) {
  const ref = useRef(null);
  const [focus, setFocus] = useState(false);
  const [active, setActive] = useState({});
  const refreshActive = () => setActive({ bold: document.queryCommandState("bold"), italic: document.queryCommandState("italic"), ul: document.queryCommandState("insertUnorderedList") });
  const cmd = (c) => { document.execCommand(c, false, null); ref.current.focus(); refreshActive(); };
  const send = () => {
    const raw = ref.current.innerHTML;
    const clean = sanitizeHtml(raw);
    if (!clean || !ref.current.textContent.trim()) return;
    onSend(clean); ref.current.innerHTML = ""; setFocus(false);
  };
  return (
    <div className={"composer" + (focus ? " composer--focus" : "")}>
      <div ref={ref} className="composer__edit" contentEditable suppressContentEditableWarning data-ph={placeholder}
        onFocus={() => setFocus(true)} onBlur={() => setFocus(false)} onKeyUp={refreshActive} onMouseUp={refreshActive}
        onKeyDown={(e) => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); send(); } }} />
      <div className="composer__bar">
        <button className={"cbtn" + (active.bold ? " cbtn--on" : "")} title="Bold" onMouseDown={(e) => { e.preventDefault(); cmd("bold"); }}><Icon name="bold" size={15} /></button>
        <button className={"cbtn" + (active.italic ? " cbtn--on" : "")} title="Italic" onMouseDown={(e) => { e.preventDefault(); cmd("italic"); }}><Icon name="italic" size={15} /></button>
        <button className={"cbtn" + (active.ul ? " cbtn--on" : "")} title="Bulleted list" onMouseDown={(e) => { e.preventDefault(); cmd("insertUnorderedList"); }}><Icon name="list" size={15} /></button>
        <span style={{ width: 1, height: 18, background: "var(--line-2)", margin: "0 4px" }} />
        <span className="faint" style={{ fontSize: 11.5 }}>Paste keeps formatting · ⌘↵ to send</span>
        <span className="gap" />
        <button className={"btn btn--acc " + (compact ? "btn--sm" : "btn--sm")} onClick={send}><Icon name="send" size={14} />{sendLabel}</button>
      </div>
    </div>
  );
}

/* ================= STATE STORE — multi-deal ================= */
const Store = (function () {
  const LS = "tract-v2-state";
  function build() {
    const deals = JSON.parse(JSON.stringify(D.deals));
    Object.values(deals).forEach((dl) => Object.values(dl.threads).forEach((t) => t.messages.forEach((m) => { if (!m.html) m.html = mdToHtml(m.body); })));
    return { deals, order: [...D.dealOrder], activeId: null };
  }
  let state;
  try { const saved = JSON.parse(localStorage.getItem(LS)); state = saved && saved.deals ? saved : build(); }
  catch (e) { state = build(); }
  const listeners = new Set();
  const save = () => { try { localStorage.setItem(LS, JSON.stringify(state)); } catch (e) {} };
  const emit = () => { save(); listeners.forEach((l) => l()); };
  const active = () => state.deals[state.activeId];

  function prereqs(id) {
    if (id === "loi") return ["assess"];
    if (id === "psa") return ["assess", "loi"];
    if (id === "dd") return ["psa"];
    if (id === "close") return ["psa"];
    return [];
  }
  function canActivate(dl, id) { return prereqs(id).every((p) => dl.stageState[p] === "done"); }
  function closeEnabled(dl) { return dl.stageState.psa === "done"; }
  function canMarkDone(dl, id) { return id === "close" ? closeEnabled(dl) : true; }

  // update the active deal immutably
  function patch(fn) {
    const dl = active(); if (!dl) return;
    const next = fn({ ...dl });
    state = { ...state, deals: { ...state.deals, [state.activeId]: next } };
  }
  function patchById(id, fn) {
    const dl = state.deals[id]; if (!dl) return;
    const next = fn({ ...dl });
    state = { ...state, deals: { ...state.deals, [id]: next } };
  }

  function dispatch(a) {
    const dl = active();
    if (a.type === "SET_ACTIVE_DEAL") { state = { ...state, activeId: a.id }; emit(); return; }
    if (a.type === "RESET") { state = build(); localStorage.removeItem(LS); emit(); return; }
    if (a.type === "CREATE_DEAL") {
      const id = "TR-" + (1100 + state.order.length);
      const nd = {
        id, name: a.name, asset: a.asset || "Commercial property", address: a.address || "—", state: a.st || "FL",
        price: a.price || 0, cap: a.cap || "—", deposit: Math.round((a.price || 0) * 0.03), createdAt: "Just now", ddEnds: "—",
        parties: { advisor: "Marcus Lee", buyer: D.PEOPLE.buyer.name, attorney: "Dana Whitfield" },
        stageState: { assess: "active", loi: "preparing", psa: "preparing", dd: "preparing", close: "preparing" },
        frozen: false, postClosing: false,
        documents: [{ id: "n1", name: "Property profile", kind: "Intake", tags: ["assess"], note: a.name, date: "now" }],
        threads: {
          "advisor-buyer": { id: "advisor-buyer", kind: "dm", title: "Advisor & you", members: ["advisor", "buyer"], messages: [{ from: "advisor", at: "now", html: mdToHtml("Welcome aboard. I'm your advisor on **" + a.name + "**. I'll start the assessment & valuation — first I need a few documents from you.") }] },
          "group": { id: "group", kind: "group", title: "Deal group", members: ["advisor", "buyer", "attorney"], messages: [] },
        },
        ddItems: D.ddTemplate({}),
        activity: [{ ic: "user-plus", t: "You started this deal", at: "now" }],
        payouts: [], refunds: [],
      };
      state = { ...state, deals: { ...state.deals, [id]: nd }, order: [id, ...state.order], activeId: id };
      emit(); return;
    }
    // ----- funds (work on a.dealId, even outside the deal workspace) -----
    if (["REQUEST_PAYOUT", "RELEASE_PAYOUT", "REQUEST_REFUND", "RESOLVE_REFUND"].includes(a.type)) {
      const id = a.dealId || state.activeId; if (!state.deals[id]) return;
      if (a.type === "REQUEST_PAYOUT") patchById(id, (d) => { d.payouts = d.payouts || []; if (d.payouts.some((p) => p.role === a.role && p.status === "requested")) return d; d.payouts = [{ id: "po" + Date.now(), who: a.who, role: a.role, amount: a.amount, status: "requested", at: "now" }, ...d.payouts]; d.activity = [{ ic: "hand-coins", t: "**" + a.who + "** requested a payout of $" + a.amount.toLocaleString(), at: "now" }, ...d.activity]; return d; });
      if (a.type === "RELEASE_PAYOUT") patchById(id, (d) => { d.payouts = (d.payouts || []).map((p) => p.id === a.id ? { ...p, status: "released", at: "now" } : p); d.activity = [{ ic: "banknote", t: "Payout released", at: "now" }, ...d.activity]; return d; });
      if (a.type === "REQUEST_REFUND") patchById(id, (d) => { d.refunds = d.refunds || []; d.refunds = [{ id: "rf" + Date.now(), who: a.who || D.PEOPLE.buyer.name, amount: a.amount, reason: a.reason, status: "requested", at: "now" }, ...d.refunds]; d.activity = [{ ic: "rotate-ccw", t: "Refund requested — $" + a.amount.toLocaleString(), at: "now" }, ...d.activity]; return d; });
      if (a.type === "RESOLVE_REFUND") patchById(id, (d) => { d.refunds = (d.refunds || []).map((r) => r.id === a.id ? { ...r, status: a.status, at: "now" } : r); d.activity = [{ ic: a.status === "approved" ? "check" : "x", t: "Refund " + a.status, at: "now" }, ...d.activity]; return d; });
      emit(); return;
    }
    if (!dl) return;
    if (dl.frozen) return;
    switch (a.type) {
      case "STAGE_ACTIVE": {
        if (!canActivate(dl, a.id)) return;
        patch((d) => { const ss = { ...d.stageState }; Object.keys(ss).forEach((k) => { if (ss[k] === "active") ss[k] = "preparing"; }); ss[a.id] = "active"; d.stageState = ss; d.activity = [{ ic: "circle-dot", t: "Marked **" + label(a.id) + "** active", at: "now" }, ...d.activity]; return d; }); break;
      }
      case "STAGE_DONE": {
        if (!canMarkDone(dl, a.id)) return;
        patch((d) => { const ss = { ...d.stageState }; ss[a.id] = "done"; d.stageState = ss; if (a.id === "close") { d.frozen = true; d.postClosing = true; d.activity = [{ ic: "lock", t: "Deal **closed & frozen**", at: "now" }, ...d.activity]; } else { d.activity = [{ ic: "check", t: "Marked **" + label(a.id) + "** done", at: "now" }, ...d.activity]; } return d; }); break;
      }
      case "STAGE_PREPARING": { patch((d) => { const ss = { ...d.stageState }; ss[a.id] = "preparing"; d.stageState = ss; return d; }); break; }
      case "DOC_NOTE": { patch((d) => { d.documents = d.documents.map((x) => x.id === a.id ? { ...x, note: a.note } : x); return d; }); break; }
      case "DOC_TAGS": { patch((d) => { d.documents = d.documents.map((x) => x.id === a.id ? { ...x, tags: a.tags } : x); return d; }); break; }
      case "DOC_ADD": { patch((d) => { d.documents = [{ id: "u" + Date.now(), name: a.name, kind: a.kind || "Upload", tags: a.tags || [], note: a.note || "", date: "now" }, ...d.documents]; d.activity = [{ ic: "upload", t: "Added **" + a.name + "**", at: "now" }, ...d.activity]; return d; }); break; }
      case "CHAT_ADD": { patch((d) => { const threads = { ...d.threads }; const t = { ...threads[a.thread] }; t.messages = [...t.messages, { from: a.from, at: "now", html: a.html }]; threads[a.thread] = t; d.threads = threads; return d; }); break; }
      case "DD_SET": { patch((d) => { d.ddItems = d.ddItems.map((it) => it.id === a.id ? { ...it, state: a.state } : it); return d; }); break; }
      default: return;
    }
    emit();
  }
  function label(id) { const s = D.STAGES.find((x) => x.id === id); return s ? s.short : id; }
  return {
    subscribe(l) { listeners.add(l); return () => listeners.delete(l); },
    getState() { return state; },
    dispatch, canActivate, canMarkDone, closeEnabled, prereqs,
  };
})();
function useStore() { return useSyncExternalStore(Store.subscribe, Store.getState); }
function useDeal() { const s = useStore(); return s.deals[s.activeId]; }

Object.assign(window, { Icon, Avatar, ROLE_COLOR, mdToHtml, sanitizeHtml, RichText, Popover, Modal, Toast, Composer, Store, useStore, useDeal, useState, useEffect, useRef });
