/* ============================================================
   Fynch AI — motion.jsx
   Animation system tied to slide activation + signature custom
   graphics. Loaded after ui.jsx, before slide files.
   Exposes window.* (SlideActiveCtx, Reveal, CountUp, Stat,
   EvidenceProvenance, OperatingStack, QRJoin, ScreenSlot, useActive).
   ============================================================ */
(function () {
  const { useState, useEffect, useRef, useContext } = React;
  const SlideActiveCtx = React.createContext(false);
  function useActive() { return useContext(SlideActiveCtx); }

  /* ---- Reveal: staggered entrance gated on slide-active ---- */
  function Reveal({ variant = "up", delay = 0, dur = 620, className = "", style, as = "div", children, ...rest }) {
    const active = useActive();
    const Tag = as;
    return (
      <Tag
        className={"rv " + className}
        data-variant={variant}
        data-show={active ? "1" : "0"}
        style={{ "--rd": delay + "ms", "--rdur": dur + "ms", ...style }}
        {...rest}
      >{children}</Tag>
    );
  }

  /* ---- CountUp: animate a number when slide activates ---- */
  function CountUp({ to, dur = 1100, decimals = 0, prefix = "", suffix = "", className, style }) {
    const active = useActive();
    const [val, setVal] = useState(0);
    const raf = useRef(0);
    useEffect(() => {
      cancelAnimationFrame(raf.current);
      if (!active) { setVal(0); return; }
      const start = performance.now();
      const from = 0;
      const tick = (t) => {
        const p = Math.min(1, (t - start) / dur);
        const e = 1 - Math.pow(1 - p, 3); // easeOutCubic
        setVal(from + (to - from) * e);
        if (p < 1) raf.current = requestAnimationFrame(tick);
      };
      raf.current = requestAnimationFrame(tick);
      const safety = setTimeout(() => setVal(to), dur + 200); // guarantee final value even if RAF throttled
      return () => { cancelAnimationFrame(raf.current); clearTimeout(safety); };
    }, [active, to, dur]);
    const shown = decimals ? val.toFixed(decimals) : Math.round(val).toLocaleString();
    return <span className={className} style={{ fontVariantNumeric: "tabular-nums", ...style }}>{prefix}{shown}{suffix}</span>;
  }

  /* ---- Stat: big count-up metric ---- */
  function Stat({ to, label, decimals, prefix, suffix, accent, delay = 0 }) {
    return (
      <Reveal variant="up" delay={delay} style={{ display: "flex", flexDirection: "column", gap: 6 }}>
        <span className="bignum" style={{ color: accent || "var(--ink-900)", fontSize: 60 }}>
          <CountUp to={to} decimals={decimals} prefix={prefix} suffix={suffix} />
        </span>
        <span style={{ fontSize: 18, color: "var(--ink-500)", fontWeight: 500, lineHeight: 1.3 }}>{label}</span>
      </Reveal>
    );
  }

  /* ============================================================
     EvidenceProvenance — THE signature graphic.
     A source document line is cited into a structured fact:
     doc → highlight sweep → connector draws → fact assembles →
     confidence fills → lineage chips pop. Tells the whole product
     story in one animated piece. Fully prop-driven & reusable.
     ============================================================ */
  function EvidenceProvenance({ field = "T12 Net Operating Income", fieldKey = "fin.noi", value = "$1,284,000", source = "Operating_Statement_2025.pdf", page = "p.12", confidence = 0.94, reviewer = "A. Chen", deps = ["DSCR", "Debt yield", "Credit memo §3"], lines, highlightIdx = 4 }) {
    const active = useActive();
    const L = lines || [
      "Gross potential rent .............. 2,646,000",
      "Vacancy & credit loss ............ (171,990)",
      "Effective gross income ........... 2,474,010",
      "Total operating expenses ......... (1,190,010)",
      "Net operating income ............. 1,284,000",
      "Capital reserves ................. (46,000)",
    ];
    return (
      <div className="provenance">
        {/* source doc */}
        <Reveal variant="left" className="prov-doc" delay={60}>
          <div className="prov-doc-bar"><span className="d r"></span><span className="d y"></span><span className="d g"></span><span className="fn">{source}</span><span className="pg">{page}</span></div>
          <div className="prov-doc-body">
            <div className="prov-doc-title">STATEMENT OF OPERATIONS — TRAILING TWELVE</div>
            {L.map((ln, i) => (
              <div key={i} className={"prov-line" + (i === highlightIdx ? " hot" : "")} style={{ "--ld": 350 + i * 70 + "ms" }}>
                {ln}
                {i === highlightIdx && <span className="prov-hl" aria-hidden></span>}
              </div>
            ))}
          </div>
        </Reveal>

        {/* connector */}
        <div className="prov-link" aria-hidden>
          <svg viewBox="0 0 160 320" preserveAspectRatio="none">
            <path className={"prov-path" + (active ? " draw" : "")} d="M0 150 C 70 150, 90 110, 160 110" />
            <circle className={"prov-pulse" + (active ? " run" : "")} r="5" />
          </svg>
        </div>

        {/* fact card */}
        <Reveal variant="right" className="prov-fact" delay={120}>
          <div className="prov-fact-top">
            <span className="prov-key">{fieldKey}</span>
            <span className="prov-cite"><span className="dot"></span>cited · match {confidence.toFixed(2)}</span>
          </div>
          <div className="prov-field">{field}</div>
          <div className="prov-value"><CountUp to={parseFloat(String(value).replace(/[^0-9.]/g, "")) || 0} prefix={String(value).trim().startsWith("$") ? "$" : ""} dur={1200} /></div>
          <div className="prov-conf">
            <div className="prov-conf-track"><div className="prov-conf-fill" style={{ width: active ? confidence * 100 + "%" : 0 }}></div></div>
            <span>confidence</span>
          </div>
          <div className="prov-meta">
            <div className="prov-meta-row" style={{ "--ld": "900ms" }}><span>Reviewer</span><b>{reviewer}</b></div>
            <div className="prov-meta-row" style={{ "--ld": "990ms" }}><span>Conflicts</span><b style={{ color: "var(--approved-fg)" }}>0</b></div>
          </div>
          <div className="prov-deps">
            <span className="prov-deps-lab">Feeds</span>
            {deps.map((d, i) => <span key={d} className="prov-dep" style={{ "--ld": 1100 + i * 110 + "ms" }}>{d}</span>)}
          </div>
        </Reveal>
      </div>
    );
  }

  /* ============================================================
     OperatingStack — dimensional 7-layer stack with a deal token
     descending a spine. Layers slide in staggered.
     ============================================================ */
  const STACK = [
    { t: "Intake", d: "Email · documents · borrower files · outside data", chips: ["broker_email", "RentRoll.xlsx", "T12.pdf"] },
    { t: "Organize", d: "Deal workspace · checklist · source coverage", chips: ["package", "coverage"] },
    { t: "Extract", d: "PDF & Excel parsing · CRE fact extraction", chips: ["150 fields"] },
    { t: "Verify", d: "Evidence · confidence · conflicts · review", chips: ["citations", "review queue"] },
    { t: "Decide", d: "DSCR · LTV · debt yield · policy · quoting", chips: ["policy", "metrics"] },
    { t: "Generate", d: "Screening & credit memos · templates · Q&A", chips: ["memo", "Q&A"] },
    { t: "Govern", d: "Audit · lineage · retention · admin controls", chips: ["audit", "lineage"] },
  ];
  function OperatingStack() {
    const active = useActive();
    return (
      <div className={"opstack" + (active ? " on" : "")}>
        <div className="opstack-spine"><span className="opstack-token"></span></div>
        <div className="opstack-layers">
          {STACK.map((l, i) => (
            <div key={l.t} className="opstack-layer" style={{ "--ld": 120 + i * 95 + "ms", "--z": STACK.length - i }}>
              <div className="opstack-node"></div>
              <div className="opstack-name"><span className="opstack-n">{String(i + 1).padStart(2, "0")}</span>{l.t}</div>
              <div className="opstack-desc">{l.d}</div>
              <div className="opstack-chips">{l.chips.map((c) => <span key={c} className="opstack-chip">{c}</span>)}</div>
            </div>
          ))}
        </div>
      </div>
    );
  }

  /* ============================================================
     QRJoin — scan-to-participate panel for room-input workshops.
     Uses a QR image service (phones need internet to reach the
     participant page anyway); falls back to big code + URL.
     ============================================================ */
  function participantURL() {
    if (window.FYNCH_PARTICIPANT_URL) return window.FYNCH_PARTICIPANT_URL.replace("{code}", FynchStore.code());
    try { return new URL("participant.html?code=" + FynchStore.code(), location.href).href; } catch (e) { return "participant.html"; }
  }
  function QRJoin({ compact }) {
    const url = participantURL();
    const [err, setErr] = useState(false);
    const count = FynchStore.participantCount();
    const qsrc = "https://api.qrserver.com/v1/create-qr-code/?size=320x320&margin=0&qzone=1&color=0f172a&data=" + encodeURIComponent(url);
    return (
      <Reveal variant="scale" className={"qrjoin" + (compact ? " compact" : "")}>
        <div className="qr-frame">
          {!err ? <img src={qsrc} alt="Scan to join" onError={() => setErr(true)} /> : <div className="qr-fallback"><Icon name="search" size={30} /></div>}
        </div>
        <div className="qr-info">
          <div className="qr-kicker"><span className="live"></span>Scan to join &amp; vote</div>
          <div className="qr-code">{FynchStore.code()}</div>
          <div className="qr-url">{url.replace(/^https?:\/\//, "")}</div>
          <div className="qr-count"><Icon name="people" size={16} />{count} in the room</div>
        </div>
      </Reveal>
    );
  }

  /* ============================================================
     ScreenSlot — labeled product-screenshot placeholder.
     The user drops a real screenshot here later (or sends the
     file and it's wired as <img>). Tagged with exactly what goes
     in each slot so nothing is ambiguous.
     ============================================================ */
  function ScreenSlot({ label, note, tab, src }) {
    return (
      <Reveal variant="up" className="screenslot">
        {src ? <img className="screenslot-img" src={src} alt={label} /> : (
          <div className="screenslot-inner">
            <div className="screenslot-chrome"><span className="d r"></span><span className="d y"></span><span className="d g"></span><span className="screenslot-url">app.fynch.ai</span></div>
            <div className="screenslot-body">
              <div className="screenslot-mark"><img src="assets/fynch-mark.png" alt="" /></div>
              <div className="screenslot-tag">PRODUCT SCREENSHOT</div>
              <div className="screenslot-label">{label}</div>
              {tab && <div className="screenslot-tab">{tab}</div>}
              {note && <div className="screenslot-note">{note}</div>}
            </div>
          </div>
        )}
      </Reveal>
    );
  }

  /* ============================================================
     DealJourney — self-playing, clickable deal-flow simulator.
     A deal advances through 9 stages; metrics tween as it
     transforms; click any stage to focus it. (Slide 20)
     ============================================================ */
  function useTween(value, dur = 700) {
    const [v, setV] = useState(value);
    const from = useRef(value), raf = useRef(0);
    useEffect(() => {
      cancelAnimationFrame(raf.current);
      const start = performance.now(), a = from.current, b = value;
      const tick = (t) => { const p = Math.min(1, (t - start) / dur); const e = 1 - Math.pow(1 - p, 3); setV(a + (b - a) * e); if (p < 1) raf.current = requestAnimationFrame(tick); else from.current = b; };
      raf.current = requestAnimationFrame(tick);
      const safety = setTimeout(() => { from.current = b; setV(b); }, dur + 150);
      return () => { cancelAnimationFrame(raf.current); clearTimeout(safety); };
    }, [value, dur]);
    return v;
  }
  function TweenNum({ to }) { const v = useTween(to); return <span>{Math.round(v).toLocaleString()}</span>; }

  const DJ_STAGES = [
    { name: "Intake", icon: "mail", screen: 23, blurb: "Broker email, rent roll, T12 & sponsor docs land in one controlled pipeline — not a personal inbox.", m: [16, 0, 0, 0], chips: ["broker_email", "RentRoll_v4.xlsx", "T12.pdf"] },
    { name: "Package", icon: "grid", screen: 22, blurb: "The checklist scores whether the package is strong enough for this stage of the deal.", m: [16, 0, 0, 2], chips: ["8 sufficient", "2 weak", "1 blocking"] },
    { name: "Extraction", icon: "table", screen: 24, blurb: "PDFs & spreadsheets are parsed into ~150 structured CRE facts — no manual spreading.", m: [16, 150, 0, 2], chips: ["units 184", "occ 93.5%", "NOI $1.284M"] },
    { name: "Evidence", icon: "eye", screen: 26, blurb: "Each fact is linked back to a page, cell, table, or computed metric.", m: [16, 150, 142, 2], chips: ["match 0.94", "p.12", "fin.noi"] },
    { name: "Review", icon: "flag", screen: 27, blurb: "Conflicts & low-confidence values are routed to underwriters as exceptions.", m: [16, 150, 142, 3], chips: ["2 blocking", "3 warning"] },
    { name: "Policy", icon: "shield", screen: 28, blurb: "DSCR, LTV, debt yield & credit policy run from sourced facts.", m: [16, 150, 150, 1], chips: ["DSCR 1.38×", "LTV 65.0%", "DY 8.4%"] },
    { name: "Quote", icon: "spark", screen: 28, blurb: "Sizing & indicative terms are checked against eligibility.", m: [16, 150, 150, 1], chips: ["$15.275M", "within policy"] },
    { name: "Memo", icon: "doc", screen: 28, blurb: "Screening & credit memos are generated from approved facts and templates.", m: [16, 150, 150, 0], chips: ["screening memo", "credit memo"] },
    { name: "Audit", icon: "layers", screen: 28, blurb: "Full lineage — who changed what, where it came from, and what depends on it.", m: [16, 150, 150, 0], chips: ["lineage", "retention", "reconstructable"] },
  ];
  const DJ_METRICS = [["Documents", "var(--ink-700)"], ["Facts extracted", "var(--brand-600)"], ["Evidence-linked", "var(--verified-fg)"], ["Open exceptions", "var(--conflict-fg)"]];
  function DealJourney() {
    const active = useActive();
    const [i, setI] = useState(0);
    const [playing, setPlaying] = useState(true);
    useEffect(() => {
      if (!active) { setI(0); setPlaying(true); return; }
      if (!playing) return;
      const t = setInterval(() => setI((p) => (p + 1) % DJ_STAGES.length), 2700);
      return () => clearInterval(t);
    }, [active, playing]);
    const s = DJ_STAGES[i];
    const pct = (i / (DJ_STAGES.length - 1)) * 100;
    return (
      <div className="dj">
        <div className="dj-metrics">
          {DJ_METRICS.map((m, k) => (
            <div key={m[0]} className="dj-metric"><span className="v" style={{ color: m[1] }}><TweenNum to={s.m[k]} /></span><span className="l">{m[0]}</span></div>
          ))}
        </div>
        <div className="dj-track">
          <div className="dj-line"><div className="dj-line-fill" style={{ width: pct + "%" }}></div></div>
          {DJ_STAGES.map((st, idx) => (
            <button key={st.name} className={"dj-node" + (idx === i ? " active" : "") + (idx < i ? " done" : "")} onClick={() => { setI(idx); setPlaying(false); }}>
              <span className="dj-dot"><Icon name={st.icon} size={24} /></span>
              <span className="dj-name">{st.name}</span>
            </button>
          ))}
        </div>
        <div className="dj-detail" key={i}>
          <div className="dj-detail-head"><span className="dj-ix">{String(i + 1).padStart(2, "0")}</span><h3>{s.name}</h3><span className="dj-screen">↳ slide {s.screen}</span></div>
          <p>{s.blurb}</p>
          <div className="dj-chips">{s.chips.map((c, k) => <span key={c} className="dj-chip" style={{ "--cd": k * 90 + "ms" }}>{c}</span>)}</div>
        </div>
        <button className="dj-play" onClick={() => setPlaying((p) => !p)}>
          <Icon d={playing ? "M6 5h4v14H6zM14 5h4v14h-4z" : "M7 5l12 7-12 7z"} size={15} />{playing ? "Pause" : "Play"} auto-advance · click any stage
        </button>
      </div>
    );
  }

  Object.assign(window, { SlideActiveCtx, useActive, Reveal, CountUp, Stat, EvidenceProvenance, OperatingStack, QRJoin, ScreenSlot, participantURL, DealJourney });
})();
