/* ZY console — 采购 COO 原生只读核对入口(procurement_native_readiness.v1 · 只读);老板在采购建议页点「核对数据」。
   诚实口径:① 首次只 GET 入口状态,未启用 / 未配置只说明现状,不发起核对、不调用模型;② 每次点击只 POST 一次,进行中不可重复发起;
   ③ 进行中每 1.5 秒串行 GET,卸载或离开进行中即停(停轮询不等于取消);④ 取消跟踪到终态,确认前不显示「已取消」;
   ⑤ 网络失败只说未确认并保留本次编号,「刷新结果」只 GET,绝不自动重新 POST;已知 HTTP 拒绝(409/403/503)沿用上一次结果,不丢可取消的旧核对;
   ⑥ provenance 只认契约两类;facts 的 null 一律「未知」,ISO 原值留 title;迟到结果不许盖住新编号;本文件只导出 window.KitNativeReadiness。 */
const DSNR = window.ZYDesignSystem_d746df;
const NR_POLL_MS = 1500;
const NR_ACTIVE = ["queued", "running", "cancelling"];
const NR_UNKNOWN = "未知";
const NR_STATUS = {
  queued: { tone: "warn", label: "已排队", note: "已排队,等待开始;这一步没有结论。" }, running: { tone: "accent", label: "核对中", note: "只读核对进行中,可以取消。" },
  cancelling: { tone: "warn", label: "正在取消", note: "取消已发出,等待确认终态。" }, completed: { tone: "good", label: "核对完成", note: "本轮只读核对已完成;下方数字取自固定快照。" },
  cancelled: { tone: "neutral", label: "已取消", note: "取消已确认,本轮没有结论。" }, failed: { tone: "danger", label: "核对失败", note: "本轮没有形成可用结论。" },
  unknown: { tone: "neutral", label: "结果未确认", note: "本轮状态或依据尚未核实。请先刷新结果；确认仍在运行时才可取消，不会自动重发。" },
};
const NR_JST = [["ok", "正常"], ["incomplete", "不完整"], ["stale", "已过期"], ["never", "从未成功"], ["unmonitored", "未监控"]];
// provenance 只接受契约声明的两类;未声明的值不猜(不别名、不正则),显示「未知」。
const NR_KIND = { production_read_only_capture: "生产捕获快照", synthetic_fixture: "模拟数据" };
const NR_SUB = { fontSize: 11, color: "var(--fg-3)", lineHeight: 1.7, overflowWrap: "anywhere" };
const NR_GRID = { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(150px, 1fr))", gap: 12, marginTop: 10 };
const NR_ALERT = { marginTop: 10, padding: "10px 12px", border: "1px solid var(--negative)", borderRadius: 11, background: "color-mix(in srgb, var(--negative) 10%, var(--surface))", color: "var(--negative)", display: "flex", gap: 8 };
function nrPresent(v) { return v !== null && v !== undefined && v !== ""; }
function nrText(v) { return nrPresent(v) ? String(v) : ""; }
// null / 空 / 不可解析一律「未知」;真值 0 是事实,照原样显示。未监控 / 已排除项取 {unit} 或字符串。
function nrCount(v) { return typeof v === "number" && Number.isFinite(v) ? String(v) : NR_UNKNOWN; }
function nrUnit(v) { if (v === null || v === undefined) return NR_UNKNOWN; return typeof v === "object" ? (nrPresent(v.unit) ? String(v.unit) : NR_UNKNOWN) : String(v); }
// 时间:ISO 原值只进 title,台面显示易读时间;取不到 / 不可解析显示「未知」。
function nrTime(v) {
  if (!nrPresent(v)) return NR_UNKNOWN;
  const date = new Date(v);
  if (Number.isNaN(date.getTime())) return NR_UNKNOWN;
  const options = { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" };
  try { return typeof window.ZY_formatDateTime === "function" ? window.ZY_formatDateTime(v, options) : date.toLocaleString("zh-CN", Object.assign({ hour12: false }, options)); } catch (_e) { return NR_UNKNOWN; }
}
// 本次核对编号:crypto.randomUUID;拿不到就不发起(不编随机数冒充 UUID)。合并视图时不抹掉已有 request_id。
function nrId() { const box = typeof window !== "undefined" && window.crypto ? window.crypto : (typeof crypto !== "undefined" ? crypto : null); if (!box || typeof box.randomUUID !== "function") return null; try { const id = box.randomUUID(); return typeof id === "string" && id ? id : null; } catch (_e) { return null; } }
function nrMerge(prev, next) { const base = prev && typeof prev === "object" ? prev : {}; const merged = Object.assign({}, base, next); if (!nrPresent(merged.request_id) && nrPresent(base.request_id)) merged.request_id = base.request_id; return merged; }
// 契约两类之外的状态码在这里消化:已知 HTTP 拒绝沿用旧结果,未知网络失败才算未确认。
function nrHttpStatus(error) { const raw = error && error.status; return nrPresent(raw) && Number.isFinite(Number(raw)) ? Number(raw) : null; }
function nrRejectNote(s) { if (s === 409) return "已有核对正在进行,本次新请求未受理。"; if (s === 401) return "会话已失效,请重新登录。"; if (s === 403) return "只有老板本人可以发起核对。"; if (s === 503) return "核对服务暂时不可用。"; return "本次新请求未受理,请稍后重试。"; }
function nrNote(error, lead) { const s = nrHttpStatus(error); if (s === 404) return "本轮核对编号在服务端已不存在;可「再核对一次」重新发起。"; return s ? lead + ":服务端拒绝了本次请求。" : lead + ":网络失败,未拿到服务端确认。"; }
function NRFact({ label, value, title }) {
  return (<div style={{ minWidth: 0 }}><div style={NR_SUB}>{label}</div>
    <div className="num" style={{ fontSize: 14, color: "var(--fg)", overflowWrap: "anywhere" }} title={title || undefined}>{value}</div></div>);
}
function KitNativeReadiness() {
  const { Card, Button, Pill, Icon } = DSNR || {};
  const [gate, setGate] = React.useState(null), [gateErr, setGateErr] = React.useState(false), [gateTick, setGateTick] = React.useState(0);
  const [run, setRun] = React.useState(null), [notice, setNotice] = React.useState(""), [busy, setBusy] = React.useState(false);
  const actionLock = React.useRef(false);   // 发起 / 取消 / 刷新单飞
  const pollLock = React.useRef(false);     // 轮询单飞:上一轮 GET 未回不发下一轮
  const gen = React.useRef(0);              // 结果代次:迟到结果不得盖住新编号
  React.useEffect(() => () => { gen.current += 1; }, []);
  // ① 只读入口状态:挂载只 GET status,不调用模型、不发起核对。
  React.useEffect(() => {
    let live = true;
    setGateErr(false);
    Promise.resolve()
      .then(() => (typeof window.ZY_nativeReadinessStatus === "function" ? window.ZY_nativeReadinessStatus() : Promise.reject(new Error("not-wired"))))
      .then((data) => { if (live) setGate(data && typeof data === "object" ? data : null); })
      .catch(() => { if (live) { setGate(null); setGateErr(true); } });
    return () => { live = false; };
  }, [gateTick]);
  const requestId = run && nrPresent(run.request_id) ? String(run.request_id) : "", runStatus = run ? (nrPresent(run.status) ? String(run.status) : "unknown") : "";
  const active = NR_ACTIVE.indexOf(runStatus) >= 0, meta = NR_STATUS[runStatus];
  const gateEnabled = gate ? gate.enabled === true : null, gateConfigured = gate ? gate.configured === true : null, gateReady = gateEnabled === true && gateConfigured === true;
  const gatePill = gateErr ? { tone: "danger", label: "入口状态读取失败" } : gate === null ? { tone: "neutral", label: "读取入口状态中" }
    : gateReady ? { tone: "good", label: "已启用 · 可核对" } : { tone: "warn", label: gateEnabled === true ? "已启用 · 配置未齐备" : "未启用" };
  const facts = run && run.facts && typeof run.facts === "object" ? run.facts : null;
  const prov = facts && facts.provenance && typeof facts.provenance === "object" ? facts.provenance : null;
  const kind = prov && nrPresent(prov.kind) ? String(prov.kind) : "";
  const jst = facts && facts.jst && typeof facts.jst === "object" ? facts.jst : null, jstCounts = jst && jst.counts && typeof jst.counts === "object" ? jst.counts : null;
  const coverage = facts && facts.coverage && typeof facts.coverage === "object" ? facts.coverage : null;
  const unmonitored = coverage && Array.isArray(coverage.unmonitored) ? coverage.unmonitored : null, excluded = coverage && Array.isArray(coverage.excluded) ? coverage.excluded : null;
  const models = facts && Array.isArray(facts.models) ? facts.models : null, answer = run && typeof run.answer === "string" ? run.answer : "";
  const canCancel = !!requestId && (runStatus === "queued" || runStatus === "running"), canRefresh = !!requestId && runStatus === "unknown";
  const simulated = kind === "synthetic_fixture", kindLabel = NR_KIND[kind] || "", snapshotOnly = facts ? facts.snapshot_only === true : null;
  // ③ 进行中轮询:每 1.5 秒 GET 一次,单飞串行;卸载或离开进行中即停(停轮询不等于取消)。
  React.useEffect(() => {
    if (!requestId || !active) return undefined;
    let live = true;
    const timer = setInterval(() => {
      if (pollLock.current) return;
      pollLock.current = true;
      const mark = gen.current;
      Promise.resolve()
        .then(() => (typeof window.ZY_getNativeReadiness === "function" ? window.ZY_getNativeReadiness(requestId) : Promise.reject(new Error("not-wired"))))
        .then((data) => { if (live && mark === gen.current && data && typeof data === "object") { setRun((prev) => nrMerge(prev, data)); setNotice(""); } })
        .catch((error) => { if (live && mark === gen.current) { setNotice(nrNote(error, "同步结果未确认")); setRun((prev) => ({ ...prev, status: "unknown" })); } })
        .then(() => { pollLock.current = false; });
    }, NR_POLL_MS);
    return () => { live = false; clearInterval(timer); };
  }, [requestId, active, runStatus]);
  // ② 显式发起:生成新编号,每次点击只 POST 一次;已知 HTTP 拒绝保留旧结果。
  const start = () => {
    if (actionLock.current || busy || active) return;
    if (typeof window.ZY_startNativeReadiness !== "function") { setNotice("核对发起通道尚未接线。"); return; }
    const id = nrId();
    if (!id) { setNotice("本浏览器无法生成核对编号,未发起核对。"); return; }
    const mark = ++gen.current;
    actionLock.current = true; setBusy(true); setNotice("");
    Promise.resolve().then(() => window.ZY_startNativeReadiness(id))
      .then((data) => { if (mark === gen.current) setRun(nrMerge({ request_id: id }, data)); })
      .catch((error) => {
        if (mark !== gen.current) return;
        const status = nrHttpStatus(error);
        if ([400, 401, 403, 404, 409, 503].includes(status)) { setNotice(nrRejectNote(status)); return; }
        setRun({ request_id: id, status: "unknown" });
      })
      .then(() => { actionLock.current = false; if (mark === gen.current) setBusy(false); });
  };
  // ④ 取消:调 cancel 接口,再由轮询给出终态;未知状态先刷新，只有恢复为进行中才允许请求取消。
  const cancel = () => {
    if (actionLock.current || busy || !requestId) return;
    if (runStatus !== "queued" && runStatus !== "running") return;
    if (typeof window.ZY_cancelNativeReadiness !== "function") { setNotice("取消通道尚未接线。"); return; }
    const mark = ++gen.current;
    actionLock.current = true; setBusy(true); setNotice("");
    Promise.resolve().then(() => window.ZY_cancelNativeReadiness(requestId))
      .then((data) => { if (mark === gen.current && data && typeof data === "object") setRun((prev) => nrMerge(prev, data)); })
      .catch((error) => { if (mark === gen.current) setNotice(nrNote(error, "取消请求未确认")); })
      .then(() => { actionLock.current = false; if (mark === gen.current) setBusy(false); });
  };
  // ⑤ 网络失败后的恢复:只 GET 当前编号一次,不重复 POST。
  const refresh = () => {
    if (actionLock.current || busy || !requestId) return;
    if (typeof window.ZY_getNativeReadiness !== "function") { setNotice("核对查询通道尚未接线。"); return; }
    const mark = ++gen.current;
    actionLock.current = true; setBusy(true); setNotice("");
    Promise.resolve().then(() => window.ZY_getNativeReadiness(requestId))
      .then((data) => { if (mark === gen.current && data && typeof data === "object") setRun((prev) => nrMerge(prev, data)); })
      .catch((error) => { if (mark === gen.current) setNotice(nrNote(error, "刷新结果未确认")); })
      .then(() => { actionLock.current = false; if (mark === gen.current) setBusy(false); });
  };
  return (
    <div className="view viewfade">
      <Card>
        <div style={{ display: "flex", gap: 10, alignItems: "flex-start", flexWrap: "wrap" }}>
          <span style={{ color: "var(--accent)", lineHeight: 1 }}><Icon name="ClipboardList" size={20} /></span>
          <div style={{ flex: "1 1 260px", minWidth: 0 }}>
            <div style={{ fontFamily: "var(--font-display)", fontSize: 16, fontWeight: 600 }}>数据就绪核对</div>
            <div style={{ ...NR_SUB, marginTop: 4 }}>核对已采集快照中的 JST 同步状态和采购、仓储资料截止时间。</div>
          </div>
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}><Pill tone="neutral" soft>只读</Pill><Pill tone={gatePill.tone} soft>{gatePill.label}</Pill></div>
        </div>
        <div style={{ marginTop: 12, display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
          {gateErr ? (<React.Fragment>
            <span style={NR_SUB}>入口状态读取失败,未确认是否可用(不显示「已启用」)。</span>
            <Button variant="ghost" icon="RefreshCw" onClick={() => setGateTick((tick) => tick + 1)}>重新读取入口状态</Button>
          </React.Fragment>) : gate === null ? (<span style={NR_SUB}>正在读取入口状态…</span>) : (<React.Fragment>
            <span style={NR_SUB}>{gateReady ? "可发起只读核对，结果将注明采集时间。" : gateEnabled === true ? "核对服务配置尚未齐备。" : "核对服务尚未启用。"}</span>
            <Button variant="solid" icon="ShieldCheck" disabled={busy || active || !gateReady} onClick={start}>{busy ? "正在发起…" : "核对数据(只读)"}</Button>
          </React.Fragment>)}
        </div>
        {run && (<div style={{ marginTop: 14, borderTop: "1px solid var(--border-soft)", paddingTop: 12 }}>
          <div aria-live="polite" style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
            <Pill tone={meta ? meta.tone : "neutral"} soft>{meta ? meta.label : "状态未确认"}</Pill>
            {simulated && <Pill tone="danger" soft>模拟数据</Pill>}
            {requestId && <span style={NR_SUB} title={requestId}>核对编号 {requestId}</span>}
          </div>
          {meta && <div style={{ ...NR_SUB, marginTop: 6 }}>{meta.note}</div>}
          <div style={{ marginTop: 10, display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
            {canCancel && <Button variant="ghost" icon="Clock" disabled={busy || runStatus === "cancelling"} onClick={cancel}>{runStatus === "cancelling" ? "正在取消…" : "取消本次核对"}</Button>}
            {canRefresh && <Button variant="ghost" icon="RefreshCw" disabled={busy} onClick={refresh}>刷新结果</Button>}
            {!active && <Button variant="ghost" icon="RefreshCw" disabled={busy || !gateReady} onClick={start}>再核对一次(新编号)</Button>}
            {active && <span style={NR_SUB}>每 1.5 秒自动同步一次;关闭页面只停止同步,不等于取消。</span>}
          </div>
          {(runStatus === "failed" || runStatus === "cancelled") && <div style={{ ...NR_SUB, marginTop: 10 }}>本轮没有形成可用结论;点「再核对一次」会生成新编号重新发起,不会自动重跑,也不复用上次结果。</div>}
          {runStatus === "completed" && (<div style={{ marginTop: 12 }}>
            <details className="audittech"><summary>核对说明</summary>
            {answer ? <div style={{ whiteSpace: "pre-wrap", marginTop: 6, overflowWrap: "anywhere", fontSize: 13, lineHeight: 1.7 }}>{answer}</div>
              : <div style={{ ...NR_SUB, marginTop: 6 }}>本轮没有可显示的结论文字(答复为空),请以此处事实卡为准。</div>}
            </details>
            <div style={{ marginTop: 12, borderTop: "1px solid var(--border-soft)", paddingTop: 12 }}>
              <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
                <span style={{ fontSize: 14, fontWeight: 600 }}>事实卡 · 固定快照</span>
                <Pill tone={snapshotOnly === true ? "neutral" : "warn"} soft>{snapshotOnly === true ? "固定快照(非实时查询)" : "快照边界未声明"}</Pill>
              </div>
              {simulated && <div role="alert" style={NR_ALERT}><Icon name="OctagonAlert" size={15} /><span>本轮是模拟数据,不代表真实生产数据,不能据此判断经营状态。</span></div>}
              {!facts ? <div style={{ ...NR_SUB, marginTop: 10 }}>本轮没有返回事实卡(未知),不显示替代数字。</div> : (<React.Fragment>
                <div style={NR_GRID}>
                  <NRFact label="来源类型" value={kindLabel || NR_UNKNOWN} />
                  <NRFact label="来源捕获时间" value={nrTime(prov && prov.captured_at)} title={nrText(prov && prov.captured_at)} />
                  <NRFact label="健康汇总生成时间" value={nrTime(facts.health_generated_at)} title={nrText(facts.health_generated_at)} />
                </div>
                <div style={{ fontSize: 13, fontWeight: 600, marginTop: 14 }}>JST 完整性</div>
                {jst ? (<React.Fragment>
                  <div style={NR_GRID}>
                    <NRFact label="监控同步项数" value={nrCount(jst.leg_count)} title={nrText(jst.leg_count)} />
                    <NRFact label="缺成功时间的同步项数" value={nrCount(jst.missing_success_time_count)} title={nrText(jst.missing_success_time_count)} />
                    <NRFact label="最早成功" value={nrTime(jst.earliest_success_at)} title={nrText(jst.earliest_success_at)} />
                    <NRFact label="最近成功" value={nrTime(jst.latest_success_at)} title={nrText(jst.latest_success_at)} />
                  </div>
                  <div style={{ ...NR_SUB, marginTop: 10 }}>各同步项状态计数</div>
                  <div style={NR_GRID}>{NR_JST.map((item) => <NRFact key={item[0]} label={item[1]} value={jstCounts ? nrCount(jstCounts[item[0]]) : NR_UNKNOWN} title={jstCounts ? nrText(jstCounts[item[0]]) : ""} />)}</div>
                </React.Fragment>) : <div style={{ ...NR_SUB, marginTop: 6 }}>本轮未返回 JST 汇总(未知)。</div>}
                <details className="audittech" style={{ marginTop: 14 }}><summary>覆盖说明</summary>
                {coverage ? (<React.Fragment>
                  <div style={NR_GRID}>
                    <NRFact label="覆盖总数" value={nrCount(coverage.total)} title={nrText(coverage.total)} />
                    <NRFact label="已监控" value={nrCount(coverage.monitored)} title={nrText(coverage.monitored)} />
                    <NRFact label="未监控同步项数" value={unmonitored ? String(unmonitored.length) : NR_UNKNOWN} />
                  </div>
                  <div style={{ ...NR_SUB, marginTop: 8 }}>未监控:{unmonitored ? (unmonitored.length ? unmonitored.map((unit, index) => <span key={index} style={{ marginRight: 8 }}>{nrUnit(unit)}</span>) : "无") : NR_UNKNOWN}</div>
                  <div style={{ ...NR_SUB, marginTop: 6 }}>已排除:{excluded ? (excluded.length ? excluded.map((item, index) => <span key={index} style={{ marginRight: 8 }}>{nrUnit(item && item.unit)}({item && nrPresent(item.why) ? String(item.why) : NR_UNKNOWN})</span>) : "无") : NR_UNKNOWN}</div>
                </React.Fragment>) : <div style={{ ...NR_SUB, marginTop: 6 }}>本轮未返回覆盖范围(未知)。</div>}
                </details>
                <div style={{ fontSize: 13, fontWeight: 600, marginTop: 14 }}>采购 / 仓储事实截止</div>
                {models && models.length > 0 ? models.map((row, index) => (<div key={index} style={{ borderTop: index === 0 ? "none" : "1px solid var(--border-soft)", padding: "10px 0" }}>
                  <div style={{ fontSize: 13, fontWeight: 600 }}>{row && nrPresent(row.label) ? String(row.label) : NR_UNKNOWN}</div>
                  <div style={NR_GRID}>
                    <NRFact label="资料截至" value={nrTime(row && row.captured_at)} title={nrText(row && row.captured_at)} />
                    <NRFact label="来源同步时间" value={nrTime(row && row.source_synced_at)} title={nrText(row && row.source_synced_at)} />
                    <NRFact label="归档时间" value={nrTime(row && row.archived_at)} title={nrText(row && row.archived_at)} />
                  </div>
                </div>)) : <div style={{ ...NR_SUB, marginTop: 6 }}>本轮未返回采购 / 仓储读模型时间(未知)。</div>}
              </React.Fragment>)}
            </div>
          </div>)}
        </div>)}
        {notice && (<div role="alert" style={{ ...NR_SUB, marginTop: 12, display: "flex", gap: 8, alignItems: "flex-start", color: "var(--warning)" }}><Icon name="OctagonAlert" size={14} /><span>未确认:{notice}</span></div>)}
        <details className="audittech" style={{ margin: "14px 0 0", paddingTop: 12 }}>
          <summary>来源说明与边界</summary>
          <div style={{ ...NR_SUB, marginTop: 8, display: "flex", flexDirection: "column", gap: 4 }}>
            <span>· 只读核对,不建采购单、不改库存、不写经营记忆、不发消息。</span>
            <span>· 结论与数字来自服务端固定快照(捕获时刻见「来源捕获时间」),不是实时查询;原值缺失处显示「未知」,不会补成 0。</span>
            <span>· 每轮核对有独立编号,进行中每 1.5 秒同步一次;关闭页面只是停止同步,不等于取消。</span>
            <span>· 来源类型只按契约两类显示:生产捕获快照 / 模拟数据;模拟结果不代表真实生产数据。</span>
          </div>
        </details>
      </Card>
    </div>
  );
}

Object.assign(window, { KitNativeReadiness });
