/* AP Agent Control Room — live data from R2 + cosmetic pipeline animation */
const { useState, useEffect, useRef, useMemo } = React;

const STATUS_URL = "https://pub-dd9ecf0a9e664eb7948a0ced6b389dc5.r2.dev/status.json";

// ---------- Static reference data ----------
const STAGES = [
  { key: "receive", name: "Receive", detail: "Gmail API · poll", note: "every 30 min · 7am–5pm" },
  { key: "extract", name: "Extract", detail: "Gemini 2.5 Flash", note: "vendor · invoice # · amount" },
  { key: "classify", name: "Classify", detail: "Job code → Zone", note: "33 codes · N / S / E" },
  { key: "route", name: "Route", detail: "Forward to Dext", note: "@dext.cc · @multiple.dext.cc" },
  { key: "log", name: "Log", detail: "SQLite · state.db", note: "label · dedup · report" },
];

const STAGE_ICONS = {
  receive: (
    <svg viewBox="0 0 24 24"><path d="M3 7l9 6 9-6" /><rect x="3" y="5" width="18" height="14" rx="2" /></svg>
  ),
  extract: (
    <svg viewBox="0 0 24 24">
      <path d="M7 3h7l5 5v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z" />
      <path d="M14 3v5h5" />
      <path d="M9 13h6M9 17h4" />
    </svg>
  ),
  classify: (
    <svg viewBox="0 0 24 24">
      <circle cx="12" cy="12" r="9" />
      <path d="M12 3v18M3 12h18" />
    </svg>
  ),
  route: (
    <svg viewBox="0 0 24 24">
      <path d="M4 17l6-6 4 4 6-6" />
      <path d="M14 5h6v6" />
    </svg>
  ),
  log: (
    <svg viewBox="0 0 24 24">
      <ellipse cx="12" cy="6" rx="8" ry="3" />
      <path d="M4 6v6c0 1.7 3.6 3 8 3s8-1.3 8-3V6" />
      <path d="M4 12v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6" />
    </svg>
  ),
};

const VENDORS = [
  "Bunnings Trade", "Reece Plumbing", "Tradelink", "Boral Concrete",
  "James Hardie", "Stratco", "CSR Gyprock", "Beacon Lighting",
  "Dulux Trade", "BlueScope Steel", "Laminex", "Caroma",
  "Stegbar Windows", "ITI Timber", "Holcim Aggregates", "Hume Doors",
  "Kennards Hire", "Bowens Timber"
];

const ZONES = [
  { key: "N", name: "North", route: "north@zanetto.dext.cc" },
  { key: "S", name: "South", route: "south@zanetto.dext.cc" },
  { key: "E", name: "East",  route: "east@zanetto.dext.cc" },
];

const HOLD_REASONS = [
  "Unknown job code · ZB-?-???",
  "Low confidence · 0.42",
  "Job code missing on PDF",
  "Vendor not in master list",
  "Multi-job split needed",
  "Confidence under 0.60",
];

const THOR_LINES = {
  running: [
    "Sniffing inbox… something just came in.",
    "Got one! Tail wagging at 4 Hz.",
    "Heel, Bunnings. Sit, Reece. Good invoice.",
    "Forwarding to Dext. Treat please.",
    "Two-paw classification accuracy.",
    "AI fetched, I deliver. Teamwork.",
  ],
  idle: [
    "All quiet. Resting one ear up.",
    "Inbox empty. Time for a nap.",
    "Standing by. Waiting for the postie.",
    "Zzz… on standby. Wake me at 4pm.",
  ],
  hold: [
    "Bork! That one needs Nick.",
    "Smells like an unknown job code.",
    "Holding this bone for a human.",
  ],
  error: [
    "Grrrrr. Something's not right.",
    "PDF won't open. Spat it out.",
  ],
};

const fmtMoney = (n) =>
  "$" + Math.round(n).toLocaleString("en-AU");

const pad2 = (n) => String(n).padStart(2, "0");
const fmtTime = (d) =>
  `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;

const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];

// Pre-seed today's totals so dashboard doesn't look empty on load
const SEED = {
  processed: 38,
  total: 64210,
  perZone: { N: 14, S: 16, E: 8 },
  perZoneVal: { N: 22340, S: 28560, E: 13310 },
  conf: { high: 31, med: 6, low: 1 },
};

const SEED_HOLDS = [
  { id: "h1", vendor: "Stratco", reason: "Unknown job code · ZB-?-014b", amt: 1284.50 },
  { id: "h2", vendor: "Hume Doors", reason: "Low confidence · 0.48", amt: 612.00 },
  { id: "h3", vendor: "Caroma", reason: "Job code missing on PDF", amt: 2960.30 },
];

// ---------- Token (invoice on rail) ----------
function Token({ inv }) {
  // Position by stage (0..4)
  const pct = 6 + (inv.stage * 22); // 6%, 28%, 50%, 72%, 94%
  const flag = inv.outcome ? `flag-${inv.outcome}` : "";
  const opacity = inv.dying ? 0 : 1;
  return (
    <div
      className={`token ${flag}`}
      style={{ left: `${pct}%`, opacity, top: inv.dying ? 40 : 70 }}
    >
      <div className="token-row">
        <div className="token-pdf">PDF</div>
        <div style={{ minWidth: 0, flex: 1 }}>
          <div className="token-vendor">{inv.vendor}</div>
        </div>
      </div>
      <div className="token-meta">
        <span>{inv.jobCode}</span>
        <span className="amt">{fmtMoney(inv.amount)}</span>
      </div>
    </div>
  );
}

// ---------- Pipeline ----------
function Pipeline({ active, invoices, counters }) {
  return (
    <div className="pipeline-card">
      <div className="pipeline-head">
        <div>
          <div className="section-sub">Live pipeline · realtime</div>
          <div className="section-title">Receive → Extract → Classify → Route → Log</div>
        </div>
        <div className="section-sub">{counters.total} processed today</div>
      </div>
      <div className="pipeline">
        <div className="rail" />
        <div className="rail-glow" />
        <div className="stages">
          {STAGES.map((s, i) => {
            const isActive = active.has(i);
            return (
              <div key={s.key} className={`stage ${isActive ? "active" : ""}`}>
                <div className="stage-icon">{STAGE_ICONS[s.key]}</div>
                <div className="stage-name">{s.name}</div>
                <div className="stage-detail">{s.detail}</div>
                <div className="stage-count">
                  <strong>{counters.stage[i] || 0}</strong> {s.note}
                </div>
              </div>
            );
          })}
        </div>
        {invoices.map((inv) => <Token key={inv.id} inv={inv} />)}
      </div>
    </div>
  );
}

// ---------- KPIs ----------
function KPI({ label, value, unit, foot, urgent }) {
  return (
    <div className={`kpi ${urgent ? "urgent" : ""}`}>
      <div className="kpi-label">{label}</div>
      <div className="kpi-value">
        {value}{unit && <span className="unit">{unit}</span>}
      </div>
      <div className="kpi-foot">{foot}</div>
    </div>
  );
}

// ---------- Zones ----------
function Zones({ perZone, perZoneVal }) {
  return (
    <div className="card">
      <div className="card-head">
        <div className="card-title">Zone routing · today</div>
        <div className="card-eyebrow">N / S / E</div>
      </div>
      <div className="zones">
        {ZONES.map((z) => (
          <div key={z.key} className="zone" data-key={z.key}>
            <div className="zone-bar" />
            <div className="zone-letter">{z.key}</div>
            <div>
              <div className="zone-name">{z.name} Zone</div>
              <div className="zone-route">{z.route}</div>
            </div>
            <div className="zone-num">
              <div className="n">{perZone[z.key] || 0}</div>
              <div className="v">{fmtMoney(perZoneVal[z.key] || 0)}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ---------- Hold queue ----------
function HoldQueue({ holds, onResolve }) {
  return (
    <div className="card hold-card">
      <div className="card-head">
        <div>
          <div className="card-title">On hold · needs Nick</div>
          <div className="card-eyebrow" style={{ marginTop: 4 }}>
            {holds.length} item{holds.length === 1 ? "" : "s"} awaiting review
          </div>
        </div>
        <div className="card-eyebrow">⚠ amber</div>
      </div>
      <div className="hold-list">
        {holds.length === 0 && (
          <div className="hold-empty">— queue clear · nothing to review —</div>
        )}
        {holds.map((h) => (
          <div key={h.id} className="hold-item">
            <div className="hold-icon">!</div>
            <div>
              <div className="hold-vendor">{h.vendor}</div>
              <div className="hold-reason">{h.reason}</div>
            </div>
            <div className="hold-amt">{fmtMoney(h.amt)}</div>
          </div>
        ))}
      </div>
      <button
        className="review-btn"
        disabled={holds.length === 0}
        onClick={onResolve}
      >
        {holds.length === 0 ? "All caught up" : `Review oldest (${holds.length})`}
      </button>
    </div>
  );
}

// ---------- AI confidence chart ----------
function Confidence({ conf }) {
  const total = conf.high + conf.med + conf.low || 1;
  const rows = [
    { k: "high", label: "High", n: conf.high },
    { k: "med",  label: "Med",  n: conf.med  },
    { k: "low",  label: "Low",  n: conf.low  },
  ];
  return (
    <div className="card">
      <div className="card-head">
        <div className="card-title">AI confidence</div>
        <div className="card-eyebrow">Gemini 2.5</div>
      </div>
      <div className="conf-rows">
        {rows.map((r) => {
          const pct = (r.n / total) * 100;
          return (
            <div key={r.k} className="conf-row">
              <div className="conf-label">{r.label}</div>
              <div className="conf-bar">
                <div className={`conf-fill ${r.k}`} style={{ width: `${pct}%` }} />
              </div>
              <div className="conf-num">{r.n} <span style={{ color: "var(--ink-faint)" }}>· {pct.toFixed(0)}%</span></div>
            </div>
          );
        })}
      </div>
      <div style={{ marginTop: 14, fontFamily: "var(--mono)", fontSize: 11, color: "var(--ink-faint)", letterSpacing: "0.06em" }}>
        Threshold for auto-route: ≥ 0.75
      </div>
    </div>
  );
}

// ---------- Activity log ----------
function ActivityLog({ entries }) {
  const ref = useRef(null);
  useEffect(() => {
    if (ref.current) ref.current.scrollTop = 0;
  }, [entries]);
  const iconFor = (k) => {
    if (k === "ok") return "✓";
    if (k === "hold") return "⚠";
    if (k === "err") return "✗";
    return "›";
  };
  return (
    <div className="card">
      <div className="card-head">
        <div className="card-title">Activity log</div>
        <div className="card-eyebrow">tail · state.db</div>
      </div>
      <div className="log" ref={ref}>
        {entries.map((e, i) => (
          <div key={i} className="log-line">
            <span className="log-time">{e.t}</span>
            <span className={`log-icon ${e.k}`}>{iconFor(e.k)}</span>
            <span className="log-msg" dangerouslySetInnerHTML={{ __html: e.m }} />
          </div>
        ))}
      </div>
    </div>
  );
}

// ---------- Thor watchdog ----------
function ThorCard({ mode, lastSeen }) {
  const [line, setLine] = useState(THOR_LINES.idle[0]);
  useEffect(() => {
    const pool = THOR_LINES[mode] || THOR_LINES.idle;
    setLine(pick(pool));
    const id = setInterval(() => setLine(pick(pool)), 5200);
    return () => clearInterval(id);
  }, [mode]);

  const collarLabel =
    mode === "running" ? "On duty" :
    mode === "hold"    ? "Bork mode" :
    mode === "error"   ? "Grrrrr" :
                         "Napping";
  const collarClass = mode === "idle" ? "napping" : "";

  return (
    <div className={`card thor-card ${mode === "running" ? "alert" : ""}`}>
      <div className="card-head">
        <div>
          <div className="card-title">Thor · Watchdog</div>
          <div className="card-eyebrow" style={{ marginTop: 4 }}>kyle's QA officer</div>
        </div>
        <div className="card-eyebrow">🦴 v1.0</div>
      </div>
      <div className="thor-frame">
        <div className={`thor-collar ${collarClass}`}>{collarLabel}</div>
        <div className="thor-bone">treats: 12 / 12</div>
        <img className="thor-img" src="assets/thor.png" alt="Thor the dog, on duty" />
      </div>
      <div className="thor-bubble">{line}</div>
      <div className="thor-meta">
        <div className="thor-stat">
          <div className="l">Last bark</div>
          <div className="v">{lastSeen || "—"}</div>
        </div>
        <div className="thor-stat">
          <div className="l">False positives</div>
          <div className="v">0 (good boy)</div>
        </div>
      </div>
    </div>
  );
}

// ---------- Main App ----------
function App() {
  const [now, setNow] = useState(new Date());
  const [agentMode, setAgentMode] = useState("running"); // running | idle | error
  const [invoices, setInvoices] = useState([]);
  const [holds, setHolds] = useState(SEED_HOLDS);
  const [log, setLog] = useState([
    { t: fmtTime(new Date(Date.now() - 1000*60*4)), k: "info", m: "Agent started · poll cycle <span class='code'>14:00 AEST</span>" },
    { t: fmtTime(new Date(Date.now() - 1000*60*3)), k: "ok",   m: "Forwarded <span class='vendor'>Reece Plumbing</span> → North · <span class='code'>$842.30</span>" },
    { t: fmtTime(new Date(Date.now() - 1000*60*3)), k: "ok",   m: "Forwarded <span class='vendor'>Bunnings Trade</span> → East · <span class='code'>$214.05</span>" },
    { t: fmtTime(new Date(Date.now() - 1000*60*2)), k: "hold", m: "Held <span class='vendor'>Stratco</span> · job code unmatched" },
    { t: fmtTime(new Date(Date.now() - 1000*60*1)), k: "ok",   m: "Forwarded <span class='vendor'>Boral Concrete</span> → South · <span class='code'>$3,210.00</span>" },
  ]);
  const [counters, setCounters] = useState({
    total: SEED.processed,
    holdCount: SEED_HOLDS.length,
    valueTotal: SEED.total,
    inboxUnread: 4,
    perZone: { ...SEED.perZone },
    perZoneVal: { ...SEED.perZoneVal },
    stage: [SEED.processed, SEED.processed, SEED.processed, SEED.processed, SEED.processed],
    conf: { ...SEED.conf },
  });
  const [lastRun, setLastRun] = useState(new Date(Date.now() - 1000*60*4));
  const [thorLastSeen, setThorLastSeen] = useState("4 min ago");

  // Live data from R2 (when STATUS_URL is configured)
  useEffect(() => {
    if (!STATUS_URL) return;

    const load = () => {
      fetch(`${STATUS_URL}?t=${Date.now()}`)
        .then((r) => r.json())
        .then((data) => {
          const pz = data.today?.per_zone || {};

          setCounters((c) => ({
            ...c,
            total:      data.today?.processed   || 0,
            valueTotal: data.today?.value_ex_gst || 0,
            holdCount:  data.today?.on_hold      || 0,
            inboxUnread: data.today?.errors      || 0,
            perZone: {
              N: pz.North?.count || 0,
              S: pz.South?.count || 0,
              E: pz.East?.count  || 0,
            },
            perZoneVal: {
              N: pz.North?.value || 0,
              S: pz.South?.value || 0,
              E: pz.East?.value  || 0,
            },
          }));

          if (data.hold_queue) {
            setHolds(
              data.hold_queue.map((h, i) => ({
                id: `live-${i}`,
                vendor: h.vendor   || "Unknown",
                reason: h.reason   || "Review required",
                amt:    h.amount   || 0,
              }))
            );
          }

          if (data.recent_log?.length) {
            setLog(
              data.recent_log.map((e) => {
                const k = e.status === "processed" ? "ok"
                        : e.status === "hold"      ? "hold"
                        : "err";
                let m = "";
                if (e.status === "processed") {
                  m = `Forwarded <span class='vendor'>${e.vendor}</span> → ${e.zone} · <span class='code'>${fmtMoney(e.amount)}</span>`;
                } else if (e.status === "hold") {
                  m = `Held <span class='vendor'>${e.vendor}</span> · ${e.note || "manual review"}`;
                } else {
                  m = `Error · <span class='vendor'>${e.vendor || "unknown"}</span> · ${e.note || "check log"}`;
                }
                return { t: e.time, k, m };
              })
            );
          }

          if (data.last_run) {
            setLastRun(new Date(data.last_run));
            setAgentMode("running");
          }
        })
        .catch(() => {}); // silently fail — keep showing last known state
    };

    load();
    const id = setInterval(load, 30_000);
    return () => clearInterval(id);
  }, []);

  // Clock
  useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(id);
  }, []);

  // Spawn invoices
  useEffect(() => {
    if (agentMode !== "running") return;
    const spawn = () => {
      const vendor = pick(VENDORS);
      const zone = pick(ZONES).key;
      const codeNum = String(Math.floor(Math.random() * 33) + 1).padStart(3, "0");
      const jobCode = `ZB-${zone}-${codeNum}`;
      const amount = Math.round(50 + Math.random() * 4500);
      const r = Math.random();
      const outcome = r < 0.78 ? "ok" : r < 0.94 ? "hold" : "err";
      const conf = outcome === "ok" ? 0.78 + Math.random() * 0.22
                : outcome === "hold" ? 0.45 + Math.random() * 0.25
                : 0.30 + Math.random() * 0.40;
      const inv = {
        id: `${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
        vendor, jobCode, amount, zone, outcome, conf,
        stage: 0, dying: false,
      };
      setInvoices((arr) => [...arr, inv]);

      // Animate stages
      const stepDelay = 1500;
      [1, 2, 3, 4].forEach((s, idx) => {
        setTimeout(() => {
          setInvoices((arr) => arr.map((i) => i.id === inv.id ? { ...i, stage: s } : i));
        }, stepDelay * (idx + 1));
      });
      // Resolve — only update counters when not in live-data mode
      setTimeout(() => {
        if (STATUS_URL) {
          // Live mode: counters come from R2 fetch, not simulation
          setInvoices((arr) => arr.map((i) => i.id === inv.id ? { ...i, dying: true } : i));
          setTimeout(() => setInvoices((arr) => arr.filter((i) => i.id !== inv.id)), 600);
          return;
        }
        setCounters((c) => {
          const next = {
            ...c,
            stage: c.stage.map((v, i) => v + 1),
          };
          if (outcome === "ok") {
            next.total = c.total + 1;
            next.valueTotal = c.valueTotal + amount;
            next.perZone = { ...c.perZone, [zone]: (c.perZone[zone] || 0) + 1 };
            next.perZoneVal = { ...c.perZoneVal, [zone]: (c.perZoneVal[zone] || 0) + amount };
          } else if (outcome === "hold") {
            next.holdCount = c.holdCount + 1;
          }
          // Confidence buckets
          const cb = conf >= 0.85 ? "high" : conf >= 0.65 ? "med" : "low";
          next.conf = { ...c.conf, [cb]: c.conf[cb] + 1 };
          return next;
        });
        if (outcome === "hold") {
          setHolds((hs) => [
            { id: inv.id, vendor, reason: pick(HOLD_REASONS), amt: amount },
            ...hs,
          ].slice(0, 8));
        }
        setLog((ls) => {
          let entry;
          const tt = fmtTime(new Date());
          if (outcome === "ok") {
            const zname = ZONES.find(z => z.key === zone).name;
            entry = { t: tt, k: "ok", m: `Forwarded <span class='vendor'>${vendor}</span> → ${zname} · <span class='code'>${fmtMoney(amount)}</span>` };
          } else if (outcome === "hold") {
            entry = { t: tt, k: "hold", m: `Held <span class='vendor'>${vendor}</span> · confidence ${conf.toFixed(2)}` };
          } else {
            entry = { t: tt, k: "err", m: `Error reading <span class='vendor'>${vendor}</span> PDF · retry queued` };
          }
          return [entry, ...ls].slice(0, 60);
        });
        setThorLastSeen("just now");
        setLastRun(new Date());

        // Fade token
        setInvoices((arr) => arr.map((i) => i.id === inv.id ? { ...i, dying: true } : i));
        setTimeout(() => {
          setInvoices((arr) => arr.filter((i) => i.id !== inv.id));
        }, 600);
      }, stepDelay * 5);
    };

    // Initial quick spawn so user sees movement immediately
    const initial = setTimeout(spawn, 700);
    const id = setInterval(spawn, 4500);
    return () => { clearInterval(id); clearTimeout(initial); };
  }, [agentMode]);

  // Active stages = where any token currently sits
  const activeStages = useMemo(() => {
    const s = new Set();
    invoices.forEach((i) => { if (!i.dying) s.add(i.stage); });
    return s;
  }, [invoices]);

  // Last run relative time
  const lastRunStr = useMemo(() => {
    const diff = Math.floor((now - lastRun) / 1000);
    if (diff < 5) return "just now";
    if (diff < 60) return `${diff}s ago`;
    const m = Math.floor(diff / 60);
    return `${m} min ago`;
  }, [now, lastRun]);

  // Thor mode reflects current activity
  const thorMode = useMemo(() => {
    if (agentMode === "error") return "error";
    if (agentMode === "idle") return "idle";
    if (invoices.some(i => i.outcome === "hold" && i.stage >= 2)) return "hold";
    if (invoices.some(i => !i.dying)) return "running";
    return "idle";
  }, [agentMode, invoices]);

  const resolveOldestHold = () => {
    setHolds((hs) => hs.slice(0, -1));
    setCounters((c) => ({ ...c, holdCount: Math.max(0, c.holdCount - 1) }));
    setLog((ls) => [
      { t: fmtTime(new Date()), k: "info", m: "Manual review · 1 hold cleared by Nick" },
      ...ls,
    ].slice(0, 60));
  };

  const cycleAgent = () => {
    setAgentMode((m) => m === "running" ? "idle" : m === "idle" ? "error" : "running");
  };

  return (
    <div className="app">
      {/* Header */}
      <div className="topbar">
        <div className="brand">
          <img src="assets/zb-logo-circle.jpg" alt="Zanetto Builders logo" />
          <div className="brand-text">
            <div className="eyebrow">Zanetto Builders · Forever Home Builders</div>
            <h1>AP Agent · <em>Control Room</em></h1>
          </div>
        </div>
        <div />
        <div className="topbar-meta">
          <div className="meta-block">
            <div className="meta-label">Last run</div>
            <div className="meta-value">{lastRunStr}</div>
          </div>
          <div className="meta-block">
            <div className="meta-label">Local · AEST</div>
            <div className="meta-value">{fmtTime(now)}</div>
          </div>
          <div
            className={`status-pill ${agentMode}`}
            onClick={cycleAgent}
            title="click to cycle status"
            style={{ cursor: "pointer" }}
          >
            <span className="dot" />
            <span>
              {agentMode === "running" ? "Agent running" :
               agentMode === "idle" ? "Idle · standby" :
               "API error"}
            </span>
          </div>
        </div>
      </div>

      {/* Pipeline */}
      <Pipeline active={activeStages} invoices={invoices} counters={counters} />

      {/* KPI strip */}
      <div className="row kpis">
        <KPI
          label="Inbox · unprocessed"
          value={counters.inboxUnread}
          foot={<><span className="delta-flat">●</span> Gmail API · live</>}
        />
        <KPI
          label="Today processed"
          value={counters.total}
          foot={<><span className="delta-up">▲ +{counters.total - SEED.processed}</span> since last run</>}
        />
        <KPI
          label="Forwarded · today"
          value={fmtMoney(counters.valueTotal)}
          foot={<><span className="delta-up">▲ ex GST</span> · sum from state.db</>}
        />
        <KPI
          label="On hold · needs Nick"
          value={holds.length}
          foot={<><span style={{ color: "var(--hold)" }}>● amber</span> manual review queue</>}
          urgent={holds.length > 0}
        />
      </div>

      {/* Mid row: zones · hold queue · confidence */}
      <div className="row mid">
        <Zones perZone={counters.perZone} perZoneVal={counters.perZoneVal} />
        <HoldQueue holds={holds} onResolve={resolveOldestHold} />
        <Confidence conf={counters.conf} />
      </div>

      {/* Bottom row: log · thor */}
      <div className="row bottom">
        <ActivityLog entries={log} />
        <ThorCard mode={thorMode} lastSeen={thorLastSeen} />
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
