/* ============================================================
   WAREHOUSE 2.0 · core — API / 圖標 / Swiss 組件庫 / 秘書塢
   ============================================================ */
(() => {
const W2 = {};
window.W2 = W2;

/* ── API 層 ── */
W2.API_BASE = (location.hostname === "127.0.0.1" || location.hostname === "localhost") ? "http://127.0.0.1:8090" : "";
// 經典版(1.0)所在域名:V2 走獨立域名 bonfirework.org 後,回 1.0 的鏈接必須絕對
W2.CLASSIC_URL = (location.hostname === "127.0.0.1" || location.hostname === "localhost") ? "../" : "https://ncsyaikg.com/";
W2.TOKEN_KEY = "warehouse_auth_token";
W2.TENANT_KEY = "warehouse_current_tenant";
W2.token = () => localStorage.getItem(W2.TOKEN_KEY) || "";
W2.setToken = (t) => t ? localStorage.setItem(W2.TOKEN_KEY, t) : localStorage.removeItem(W2.TOKEN_KEY);
W2.tenant = () => localStorage.getItem(W2.TENANT_KEY) || "";
W2.setTenant = (s) => s ? localStorage.setItem(W2.TENANT_KEY, s) : localStorage.removeItem(W2.TENANT_KEY);

W2.fetch = async (path, options = {}) => {
  const headers = new Headers(options.headers || {});
  const t = W2.token();
  if (t) headers.set("Authorization", "Bearer " + t);
  const slug = W2.tenant();
  if (slug) headers.set("X-Tenant-Slug", slug);
  const res = await fetch(/^https?:/.test(path) ? path : W2.API_BASE + path, { ...options, headers });
  if (res.status === 401) { W2.setToken(""); window.dispatchEvent(new Event("w2-auth-expired")); }
  return res;
};
W2.json = async (path, options) => {
  const res = await W2.fetch(path, options);
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(data.error || data.message || res.statusText);
  return data;
};
W2.post = (path, body) => W2.json(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body || {}) });

W2.agentStream = async (body, onEvent) => {
  const res = await W2.fetch("/api/agent/run/stream", {
    method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
  });
  if (!res.ok || !res.body) {
    let msg = res.statusText;
    try { msg = (await res.json()).error || msg; } catch (e) {}
    throw new Error(msg);
  }
  const reader = res.body.getReader();
  const dec = new TextDecoder();
  let buf = "";
  for (;;) {
    const { done, value } = await reader.read();
    if (done) break;
    buf += dec.decode(value, { stream: true });
    let i;
    while ((i = buf.indexOf("\n")) >= 0) {
      const line = buf.slice(0, i).trim();
      buf = buf.slice(i + 1);
      if (!line) continue;
      try { onEvent(JSON.parse(line)); } catch (e) {}
    }
  }
};

/* ── 圖標(1.6 描邊,幾何化) ── */
const P = {
  home: <><path d="M4 11.5 12 4l8 7.5"/><path d="M6 10v9.5h12V10"/></>,
  box: <><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z"/><path d="m3 8 9 5 9-5"/><path d="M12 13v8"/></>,
  inbound: <><path d="M12 3v10"/><path d="m8 9 4 4 4-4"/><path d="M4 17v3h16v-3"/></>,
  outbound: <><path d="M12 13V3"/><path d="m8 7 4-4 4 4"/><path d="M4 17v3h16v-3"/></>,
  alert: <><path d="m10.3 3.6-8 14A2 2 0 0 0 4 20.5h16a2 2 0 0 0 1.7-3l-8-14a2 2 0 0 0-3.4 0Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></>,
  bell: <><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></>,
  search: <><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></>,
  sparkle: <><path d="M12 3v4M12 17v4M3 12h4M17 12h4"/><path d="M12 8a4 4 0 0 0 4 4 4 4 0 0 0-4 4 4 4 0 0 0-4-4 4 4 0 0 0 4-4Z"/></>,
  chart: <><path d="M3 3v18h18"/><path d="M7 15v-4M12 15V7M17 15v-6"/></>,
  layers: <><path d="m12 2 9 5-9 5-9-5 9-5Z"/><path d="m3 12 9 5 9-5"/><path d="m3 17 9 5 9-5"/></>,
  user: <><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></>,
  gear: <><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M2 12h3M19 12h3M4.9 19.1 7 17M17 7l2.1-2.1"/></>,
  x: <><path d="M18 6 6 18M6 6l12 12"/></>,
  check: <path d="m5 12 5 5L20 7"/>,
  checkCircle: <><circle cx="12" cy="12" r="9"/><path d="m9 12 2 2 4-4"/></>,
  chevron: <path d="m9 18 6-6-6-6"/>,
  chevronDown: <path d="m6 9 6 6 6-6"/>,
  plus: <><path d="M12 5v14M5 12h14"/></>,
  flame: <><path d="M12 2c1 4 5 5 5 9a5 5 0 0 1-10 0c0-1.5.5-2.5 1-3 0 1.5 1 2 2 2-1-2 0-5 2-8Z"/></>,
  swap: <><path d="m17 2 4 4-4 4"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></>,
  clock: <><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></>,
  map: <><circle cx="12" cy="10" r="3"/><path d="M12 21s7-6.5 7-11a7 7 0 1 0-14 0c0 4.5 7 11 7 11Z"/></>,
  shield: <><path d="M12 2 4 5v6c0 5 3.4 8.5 8 10 4.6-1.5 8-5 8-10V5l-8-3Z"/></>,
  clipboard: <><rect x="8" y="2" width="8" height="4"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="m9 14 2 2 4-4"/></>,
  refresh: <><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/><path d="M3 21v-5h5"/></>,
  eye: <><path d="M2 12s3.6-6 10-6 10 6 10 6-3.6 6-10 6S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/></>,
  eyeOff: <><path d="M3 3l18 18"/><path d="M10.6 10.6a3 3 0 0 0 4.2 4.2"/><path d="M9.9 5.2A10.8 10.8 0 0 1 12 5c6.4 0 10 7 10 7a16.5 16.5 0 0 1-3.1 3.8"/><path d="M6.6 6.6A16 16 0 0 0 2 12s3.6 7 10 7a10.8 10.8 0 0 0 4.2-.8"/></>,
  arrow: <><path d="M5 12h14"/><path d="m13 6 6 6-6 6"/></>,
  logo: <path d="M13 2 4 14h6l-1 8 9-12h-6l1-8Z"/>,
  pkg: <><path d="M21 16V8a2 2 0 0 0-1-1.7l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.7l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5M12 22V12"/></>,
  scan: <><path d="M3 7V5a2 2 0 0 1 2-2h2M17 3h2a2 2 0 0 1 2 2v2M21 17v2a2 2 0 0 1-2 2h-2M7 21H5a2 2 0 0 1-2-2v-2"/><path d="M3 12h18"/></>,
  cpu: <><rect x="6" y="6" width="12" height="12"/><path d="M9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3"/></>,
  doc: <><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6Z"/><path d="M14 2v6h6"/></>,
  wallet: <><path d="M20 7H5a2 2 0 0 1 0-4h13v4"/><path d="M4 5v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2"/><circle cx="16.5" cy="14" r="1.4"/></>,
  building: <><rect x="4" y="3" width="16" height="18"/><path d="M9 8h1M14 8h1M9 12h1M14 12h1M9 16h1M14 16h1"/></>,
  trend: <><path d="m22 7-8.5 8.5-5-5L2 17"/><path d="M16 7h6v6"/></>,
  gavel: <><path d="m14 13 6 6-1.5 1.5-6-6"/><path d="m9 8 5 5"/><path d="m7 6 4-4 4 4-4 4-4-4Z"/><path d="M3 21h9"/></>,
};
const Icon2 = ({ name, size = 17, sw = 1.6, color = "currentColor", style }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color}
    strokeWidth={sw} strokeLinecap="square" strokeLinejoin="miter" style={style}>{P[name] || null}</svg>
);
W2.Icon = Icon2;

/* ── UI 原子 ── */
const Btn = ({ kind = "", size = "", icon, children, ...rest }) => (
  <button className={`btn ${kind} ${size}`.trim()} {...rest}>{icon && <Icon2 name={icon} size={size === "sm" ? 12 : 14}/>}{children}</button>
);
const Tag = ({ tone = "plain", dot, children, style }) => (
  <span className={`tag ${tone}`} style={style}>{dot && <span className="dot"/>}{children}</span>
);
const Label = ({ red, dim, children, style }) => (
  <div className={"label" + (red ? " red" : "") + (dim ? " dim" : "")} style={style}>{children}</div>
);
const Empty = ({ icon = "box", title, sub, action }) => (
  <div className="col" style={{ alignItems: "center", gap: 12, padding: "48px 20px" }}>
    <Icon2 name={icon} size={26} color="var(--ink-4)"/>
    <div style={{ fontWeight: 700, fontSize: 14 }}>{title}</div>
    {sub && <div className="muted" style={{ fontSize: 12.5, maxWidth: 340, textAlign: "center", lineHeight: 1.6 }}>{sub}</div>}
    {action}
  </div>
);

/* ── KPI 格:巨型數字 ── */
const Kpi = ({ label, value, unit, red, foot, delay = 0 }) => (
  <div className="kpi rise" style={{ animationDelay: delay + "s" }}>
    <div className="k-label"><Label>{label}</Label>{red && <span className="blink-dot"/>}</div>
    <div className={"k-value" + (red ? " red" : "")}>{value}{unit && <span className="k-unit">{unit}</span>}</div>
    <div className="k-foot">{foot || <span/>}</div>
  </div>
);

/* ── 量表 ── */
const Meter = ({ label, count, total, color = "var(--ink)" }) => (
  <div className="meter">
    <div className="row spread" style={{ fontSize: 12.5 }}>
      <span className="ink2">{label}</span>
      <span className="num" style={{ fontWeight: 700 }}>{count}<span className="muted" style={{ fontWeight: 400 }}> / {total}</span></span>
    </div>
    <div className="m-track"><i className="m-fill" style={{ width: (total ? count / total * 100 : 0) + "%", background: color }}/></div>
  </div>
);
const StackBar = ({ data = [] }) => {
  const total = data.reduce((s, d) => s + (Number(d.value) || 0), 0) || 1;
  return (
    <div className="stackbar">
      {data.map((d, i) => <i key={i} style={{ width: (d.value / total * 100) + "%", background: d.color }} title={d.label + " " + d.value}/>)}
    </div>
  );
};
W2.CHART_COLORS = ["#141414", "#E0261C", "#85806F", "#B4AE9C", "#4A4740", "#D8D2C2"];

const Spark2 = ({ points = [], w = 100, h = 30, color = "var(--ink)", fill = false }) => {
  if (!points || points.length < 2) return <svg width={w} height={h}/>;
  const max = Math.max(...points), min = Math.min(...points), rng = max - min || 1;
  const step = w / (points.length - 1);
  const pts = points.map((p, i) => [i * step, h - ((p - min) / rng) * (h - 6) - 3]);
  const d = pts.map((p, i) => (i ? "L" : "M") + p[0].toFixed(1) + " " + p[1].toFixed(1)).join(" ");
  const last = pts[pts.length - 1];
  return (
    <svg width={w} height={h} style={{ display: "block" }}>
      <path d={d} fill="none" stroke={color} strokeWidth="1.5"/>
      <circle cx={last[0]} cy={last[1]} r="2.4" fill={color}/>
    </svg>
  );
};

/* ── Markdown 渲染(秘書回覆:加粗/表格/標題/代碼/鏈接/公式)── */
let _purifyHooked = false;
W2.mdToHtml = (text) => {
  const raw = String(text == null ? "" : text);
  if (!window.marked || !window.DOMPurify) return null;   // CDN 未就緒 → 調用方回退純文本
  // 先把數學段落藏起來,防止 marked 把公式裡的 _ * 當強調解析
  const stash = [];
  const s = raw.replace(/\$\$([\s\S]+?)\$\$|\\\[([\s\S]+?)\\\]|\\\((.+?)\\\)|\$([^\n$]+?)\$/g, (m) => {
    stash.push(m);
    return "§§M" + (stash.length - 1) + "§§";
  });
  let html = window.marked.parse(s, { gfm: true, breaks: true });
  html = html.replace(/§§M(\d+)§§/g, (_, i) =>
    stash[+i].replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"));
  if (!_purifyHooked) {
    _purifyHooked = true;
    window.DOMPurify.addHook("afterSanitizeAttributes", (node) => {
      if (node.tagName === "A") { node.setAttribute("target", "_blank"); node.setAttribute("rel", "noopener"); }
    });
  }
  return window.DOMPurify.sanitize(html);
};

const MdBubble = ({ text }) => {
  const ref = React.useRef(null);
  const html = W2.mdToHtml(text);
  React.useEffect(() => {
    if (html != null && ref.current && window.renderMathInElement) {
      try {
        window.renderMathInElement(ref.current, {
          delimiters: [
            { left: "$$", right: "$$", display: true },
            { left: "\\[", right: "\\]", display: true },
            { left: "\\(", right: "\\)", display: false },
            { left: "$", right: "$", display: false },
          ],
          throwOnError: false,
        });
      } catch (e) {}
    }
  }, [text, html]);
  if (html == null) return <div className="bubble-a" style={{ whiteSpace: "pre-wrap" }}>{text}</div>;
  return <div className="bubble-a md" ref={ref} dangerouslySetInnerHTML={{ __html: html }}/>;
};

/* ── 秘書塢 ── */
const SecretaryDock = () => {
  const t = window.W2_LANG.t;
  const { useState, useRef, useEffect, useCallback } = React;
  const [open, setOpen] = useState(false);
  const [big, setBig] = useState(false);
  const [items, setItems] = useState([]);
  const [input, setInput] = useState("");
  const [busy, setBusy] = useState(false);
  const convRef = useRef(null);
  const scrollRef = useRef(null);
  const inputRef = useRef(null);

  useEffect(() => {
    const h = (e) => {
      setOpen(true);
      const p = e.detail && e.detail.prompt;
      if (p) send(p);
      else setTimeout(() => inputRef.current && inputRef.current.focus(), 60);
    };
    window.addEventListener("w2-secretary-open", h);
    return () => window.removeEventListener("w2-secretary-open", h);
  }, []);
  useEffect(() => { scrollRef.current && (scrollRef.current.scrollTop = scrollRef.current.scrollHeight); }, [items, open]);

  const send = useCallback(async (text) => {
    const msg = (text || "").trim();
    if (!msg) return;
    setBusy(true);
    setItems((prev) => [...prev, { role: "u", text: msg }]);
    try {
      let finalText = "";
      await W2.agentStream({ text: msg, conversation_id: convRef.current }, (ev) => {
        if (ev.event === "run_start") convRef.current = ev.conversation_id || convRef.current;
        else if (ev.event === "step_start") setItems((p) => [...p, { role: "step", text: ev.title || ev.command || t("執行中…"), running: true }]);
        else if (ev.event === "step") setItems((p) => {
          const n = [...p];
          for (let i = n.length - 1; i >= 0; i--) if (n[i].role === "step" && n[i].running) { n[i] = { ...n[i], running: false, text: ev.title || ev.command || n[i].text }; break; }
          return n;
        });
        else if (ev.event === "final") { finalText = ev.message || (ev.payload && ev.payload.message) || ""; }
      });
      setItems((p) => [...p, { role: "a", text: finalText || t("(完成,但沒有返回文字)") }]);
    } catch (e) {
      setItems((p) => [...p, { role: "a", text: "⚠ " + (e.message || String(e)) }]);
    } finally { setBusy(false); }
  }, []);
  const submit = () => { const t = input; setInput(""); send(t); };

  if (!open) return (
    <button className="dock-fab" onClick={() => { setOpen(true); setTimeout(() => inputRef.current && inputRef.current.focus(), 60); }}>
      <Icon2 name="sparkle" size={15}/>{t("秘書")}<span className="mono" style={{ fontSize: 9, letterSpacing: ".14em", opacity: .6 }}>AI</span>
    </button>
  );
  return (
    <div className={"dock" + (big ? " big" : "")}>
      <div className="row spread" style={{ padding: "12px 16px", borderBottom: "2px solid var(--rule)" }}>
        <div className="row g10">
          <span className="label">SECRETARY</span>
          <span className="blink-dot" style={{ background: "var(--ok)" }}/>
          <span style={{ fontSize: 12.5, fontWeight: 700 }}>{t("公司秘書")}</span>
        </div>
        <div className="row g4">
          <button className="btn ghost sm" style={{ padding: "0 7px", fontFamily: "var(--f-mono)", fontSize: 12 }} title={big ? "縮小" : "放大"} onClick={() => setBig(v => !v)}>{big ? "⤡" : "⤢"}</button>
          <button className="btn ghost sm" style={{ padding: "0 7px" }} onClick={() => setOpen(false)}><Icon2 name="x" size={13}/></button>
        </div>
      </div>
      <div ref={scrollRef} className="col g10 dock-scroll" style={{ padding: 16, overflowY: "auto" }}>
        {!items.length && (
          <div className="col g10" style={{ margin: "auto 0", alignItems: "center" }}>
            <Label dim>SAY IT · I RUN IT · AUDITED</Label>
            <div style={{ fontSize: 13, fontWeight: 650 }}>{t("吩咐一句,我來執行")}</div>
            <div className="muted" style={{ fontSize: 11.5, textAlign: "center", lineHeight: 1.8 }}>{t("「出庫 2 雙絕緣手套給檢修一班」")}<br/>{t("「今天有什麼要處理的?」·「幫低庫存物資補貨」")}</div>
          </div>
        )}
        {items.map((m, i) =>
          m.role === "u" ? <div key={i} className="bubble-u">{m.text}</div>
          : m.role === "step" ? <div key={i} className="step-line"><Icon2 name={m.running ? "refresh" : "check"} size={10} color={m.running ? "var(--ink-4)" : "var(--ok)"}/>{m.text}</div>
          : <MdBubble key={i} text={m.text}/>
        )}
        {busy && <div className="step-line"><Icon2 name="refresh" size={10}/>{t("秘書工作中…")}</div>}
      </div>
      <div style={{ padding: "12px 14px 14px", borderTop: "1px solid var(--hair)" }}>
        <div className="row g8">
          <input ref={inputRef} className="field" style={{ flex: 1, height: 38, fontSize: 13.5 }} value={input} disabled={busy}
            onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && !busy && submit()}
            placeholder={t("吩咐秘書…")}/>
          <button className="btn primary" style={{ width: 42, height: 38, padding: 0 }} disabled={busy || !input.trim()} onClick={submit}>
            <Icon2 name="arrow" size={15}/>
          </button>
        </div>
      </div>
    </div>
  );
};
W2.openSecretary = (prompt) => window.dispatchEvent(new CustomEvent("w2-secretary-open", { detail: { prompt } }));

Object.assign(W2, { Btn, Tag, Label, Empty, Kpi, Meter, StackBar, Spark2, SecretaryDock });
})();
