const $ = (selector, scope = document) => scope.querySelector(selector);
const $$ = (selector, scope = document) => [...scope.querySelectorAll(selector)];

const portal = document.body.dataset.portal || "entry";
const viewNames = portal === "admin"
  ? { dashboard: "管理总览", pair: "往来查询", audit: "审核中心", flows: "流水管理", companies: "公司与账号", settings: "结账与期初", "period-audit": "审计记录", reminders: "提醒管理" }
  : { workspace: "工作台", upload: "流水导入", manual: "手工记录", flows: "流水管理", transfers: "转账往来", reconcile: "往来确认", accounts: "银行账户", notifications: "通知" };

const SUBJECT_CODE_LABEL = {
  receivable: "应收",
  other_receivable: "其他应收",
  payable: "应付",
  other_payable: "其他应付",
};
const SUBJECT_LABEL_CODE = {
  应收: "receivable",
  其他应收: "other_receivable",
  应付: "payable",
  其他应付: "other_payable",
};

const accountStatusLabels = {
  pending: "待复核",
  active: "已启用",
  returned: "已退回",
  disabled: "已停用",
};

const state = {
  coverageGaps: [],
  companyCoverageGaps: [],
  companies: [],
  currentView: portal === "admin" ? "dashboard" : "workspace",
  selectedFile: null,
  parseResult: null,
};

const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");

function animateView(view, { initial = false } = {}) {
  if (!view || motionQuery.matches || typeof view.animate !== "function") return;
  if (!initial) {
    view.getAnimations().forEach((animation) => animation.cancel());
    view.animate(
      [{ opacity: 0.84, transform: "translateY(5px)" }, { opacity: 1, transform: "translateY(0)" }],
      { duration: 180, easing: "cubic-bezier(.22,1,.36,1)" },
    );
    return;
  }
  const selectors = [
    ".page-head",
    ".stat-card",
    ".card",
    ".notice",
    ".list-row",
    ".filters",
  ];
  const elements = [...new Set(selectors.flatMap((selector) => [...view.querySelectorAll(selector)]))];

  elements.forEach((element, index) => {
    element.getAnimations().forEach((animation) => animation.cancel());
    const keyframes = [
      { opacity: 0, transform: `translateY(${initial ? 16 : 10}px)` },
      { opacity: 1, transform: "translateY(0)" },
    ];
    element.animate(keyframes, {
      duration: 440,
      delay: Math.min(index * 38, 260),
      easing: "cubic-bezier(.22,1,.36,1)",
      fill: "both",
    });
  });
}

function initMotion() {
  if (motionQuery.matches) return;
  animateView($(".app-view.is-active"), { initial: true });

  $$(".side-nav a", $("#sidebar") || document).forEach((item, index) => {
    item.animate(
      [{ opacity: 0, transform: "translateX(-8px)" }, { opacity: 1, transform: "translateX(0)" }],
      { duration: 360, delay: 90 + index * 28, easing: "cubic-bezier(.22,1,.36,1)", fill: "both" },
    );
  });
}

function recordStatus(status) {
  if (["已启用", "已确认"].includes(status)) return { className: "success", label: status };
  if (status === "已退回") return { className: "danger", label: status };
  if (["异常待处理", "已停用"].includes(status)) return { className: "neutral", label: status };
  return { className: "warning", label: status || "待复核" };
}

function accountStatusLabel(status) {
  return accountStatusLabels[status] || "待复核";
}

function pillClass(statusClass) {
  return { success: "pill-success", danger: "pill-danger", warning: "pill-warn", neutral: "pill-muted", info: "pill-info" }[statusClass] || "pill-muted";
}

function accountTail(masked) {
  return String(masked || "").replace(/^\*+/, "");
}

function formatCurrency(value) {
  return Number(value).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

function showTableLoading(tbody, cols = 6) {
  if (!tbody) return;
  tbody.innerHTML = `<tr class="loading-row"><td colspan="${cols}">加载中…</td></tr>`;
}

function showTableError(tbody, cols = 6) {
  if (!tbody) return;
  tbody.innerHTML = `<tr class="loading-row"><td colspan="${cols}" style="color: var(--danger);">加载失败，请稍后重试</td></tr>`;
}

function showToast(title, detail = "", kind = "info") {
  const region = $("#toastRegion");
  if (!region) return;
  const toast = document.createElement("div");
  toast.className = `toast ${["success", "warn", "danger", "info"].includes(kind) ? kind : "info"}`;
  toast.setAttribute("role", "status");
  const dot = document.createElement("span");
  dot.className = "t-dot";
  const body = document.createElement("div");
  body.className = "t-body";
  const heading = document.createElement("div");
  heading.className = "t-title";
  heading.textContent = title;
  body.append(heading);
  if (detail) {
    const description = document.createElement("div");
    description.className = "t-detail";
    description.textContent = detail;
    body.append(description);
  }
  toast.append(dot, body);
  region.append(toast);
  window.setTimeout(() => {
    toast.style.opacity = "0";
    toast.style.transition = "opacity 0.2s ease";
    window.setTimeout(() => toast.remove(), 200);
  }, 4200);
}

function toastIfLocked(result) {
  const msg = String(result?.message || "");
  if (!(result?.year_month || /已结账锁定/.test(msg))) return false;
  const parts = msg.split(" / ");
  showToast(parts[0] || "账期已锁定", parts.slice(1).join(" / "), "warn");
  return true;
}

function closeNavigation({ restoreFocus = false } = {}) {
  const sidebar = $("#sidebar");
  if (!sidebar) return;
  const wasOpen = sidebar.classList.contains("is-open");
  sidebar.classList.remove("is-open");
  $$(".menu-button").forEach((button) => {
    button.setAttribute("aria-expanded", "false");
    button.setAttribute("aria-label", "打开导航");
  });
  if (restoreFocus && wasOpen) $(".menu-button")?.focus();
}

function showView(view) {
  if (!viewNames[view]) return;
  const navigationWasOpen = $("#sidebar")?.classList.contains("is-open");
  state.currentView = view;
  $$(".app-view").forEach((page) => page.classList.toggle("is-active", page.dataset.page === view));
  $$(".side-nav a[data-view]").forEach((item) => {
    const active = item.dataset.view === view;
    item.classList.toggle("active", active);
    if (active) item.setAttribute("aria-current", "page");
    else item.removeAttribute("aria-current");
  });
  const title = $("#currentViewName");
  if (title) {
    if (view === "transfers" && state.transfersDetail?.company_name) {
      title.textContent = `转账往来 / ${state.transfersDetail.company_name}`;
    } else {
      title.textContent = viewNames[view];
    }
  }
  closeNavigation({ restoreFocus: navigationWasOpen });
  const activeView = $(`.app-view[data-page="${view}"]`);
  requestAnimationFrame(() => animateView(activeView));
  window.scrollTo({ top: 0, behavior: motionQuery.matches ? "auto" : "smooth" });
  if (portal === "company" && view === "transfers") {
    if (state.transfersKeepDetail && state.transfersDetail?.company_id) {
      showTransfersDetailLayer();
    } else {
      state.transfersDetail = null;
      showTransfersOverviewLayer();
      loadTransfersSummary();
    }
  }
  state.transfersKeepDetail = false;
  if (portal === "admin" && view === "settings") loadPeriodClose();
  if (portal === "admin" && view === "period-audit") loadPeriodAudit();
  if (portal === "admin" && view === "audit") loadReopenQueue();
  if (view === "flows") loadFlows();
  if (portal === "company" && view === "manual") loadCompanyManualRecords();
}

// 原型占位，非本司真待办/真断档数据：detailContent 仅用于演示总览/工作台事项
// 点开抽屉的静态文案，真实待办与断档来自审核中心 / 往来确认等接口数据。
const detailContent = {
  "admin-gap": { tag: ["danger", "高风险"], title: "金牛置业 · 中行账户断档", desc: "中国银行尾号 8821 缺少 07-06 至 07-16 流水，已影响 7 月结账。", fields: [["公司", "金牛置业"], ["账户", "中国银行 · 尾号 8821"], ["缺口期间", "2026-07-06 — 07-16 · 11 天"], ["影响", "7 月结账 · 与金牛煤业 320 万往来无法归集"], ["当前状态", "阻断结账"]], tip: "先向金牛置业出纳发送补传提醒，补齐流水后到审核中心复核覆盖区间。", action: ["去审核中心处理", "audit"] },
  "admin-unsubmitted": { tag: ["danger", "高风险"], title: "金牛农业 7 月未提交流水", desc: "全部账户 7 月流水均未提交，已发 2 次系统提醒。", fields: [["公司", "金牛农业"], ["账户", "全部账户"], ["已提醒", "2 次 · 最近一次 08-18"], ["当前状态", "未响应"]], tip: "可再次发送提醒，或在提醒管理里查看已发出的提醒记录。", action: ["去提醒管理", "reminders"] },
  "admin-unilateral": { tag: ["warning", "中风险"], title: "金牛新能源 ↔ 金牛贸易 单边流水", desc: "3 笔单边流水合计 486 万元，待对方选择银行流水佐证。", fields: [["本方", "金牛新能源"], ["对方", "金牛贸易"], ["笔数 / 金额", "3 笔 · 486 万元"], ["当前状态", "待对方证据"]], tip: "提醒新能源侧在往来确认中选择对方银行流水佐证后提交。", action: ["去审核中心匹配", "audit"] },
  "admin-subject": { tag: ["warning", "中风险"], title: "手工记录待确认往来科目", desc: "5 笔手工记录应收 / 其他应收待判定，涉及煤业、物流、贸易。", fields: [["待确认", "应收 / 其他应收"], ["涉及公司", "煤业、物流、贸易"], ["笔数", "5 笔"]], tip: "科目只按确定性规则建议，拿不准时选「其他应收」并注明依据。", action: ["去审核中心复核", "audit"] },
  "task-match": { tag: ["danger", "阻断"], title: "确认 3 笔单边流水", desc: "选择对方银行流水作为证据后提交确认。", fields: [["笔数", "3 笔"], ["处理方式", "选择对方银行流水佐证"], ["关联", "7 月结账阻断项"]], tip: "系统推荐账号一致的候选，请核对回单后再确认。", action: ["去往来确认", "reconcile"] },
  "task-subject": { tag: ["danger", "阻断"], title: "确认 2 笔其他应收科目", desc: "核对后改判或维持原科目。", fields: [["笔数", "2 笔"], ["处理方式", "改判或维持原科目"], ["关联", "7 月结账阻断项"]], tip: "科目只按确定性规则建议，拿不准时选「其他应收」并注明依据。", action: ["去手工记录", "manual"] },
  "task-upload": { tag: ["danger", "阻断"], title: "补传交行 7710 账户流水", desc: "账户已登记，待总行审核通过后即可导入。", fields: [["账户", "交通银行 · 尾号 7710"], ["状态", "待审核"], ["处理", "审核通过后上传 7 月流水"]], tip: "账户审核通过后，从交行网银导出 7 月流水直接上传。", action: ["去查看账户", "accounts"] },
  "task-reconcile": { tag: ["muted", "一般"], title: "核对与金牛置业 320 万往来", desc: "置业 8821 账户 7 月流水断档，需人工核对。", fields: [["对方", "金牛置业"], ["金额", "320 万元"], ["原因", "置业 8821 账户流水断档"]], tip: "先核对双方流水日期与金额是否一致，再决定是否提交确认。", action: ["去流水管理", "flows"] },
};

function initDetailDrawer() {
  const triggers = $$("[data-detail]");
  if (!triggers.length) return;
  const drawer = document.createElement("aside");
  drawer.className = "drawer";
  drawer.id = "detailDrawer";
  drawer.setAttribute("aria-label", "事项详情");
  drawer.innerHTML = `<div class="drawer-head"><div><span class="pill" id="detailTag"></span><h2 class="d-title" id="detailTitle"></h2><p class="d-desc" id="detailDesc"></p></div><button type="button" class="icon-button" data-close-detail aria-label="关闭详情" title="关闭详情"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"><path d="M6 6l12 12M18 6L6 18"/></svg></button></div><div class="drawer-body"><dl class="kv" id="detailFields"></dl></div><div class="drawer-tip" id="detailTip"></div><div class="drawer-foot"><button type="button" class="btn" data-close-detail>关闭</button><button type="button" class="btn btn-primary" id="detailAction"></button></div>`;
  document.body.append(drawer);
  let lastTrigger = null;

  function closeDrawer({ restoreFocus = true } = {}) {
    drawer.classList.remove("is-open");
    if (restoreFocus && lastTrigger) lastTrigger.focus();
  }

  function openDetail(id, trigger) {
    const item = detailContent[id];
    if (!item) return;
    lastTrigger = trigger;
    const tag = $("#detailTag", drawer);
    tag.className = `pill ${pillClass(item.tag[0])}`;
    tag.textContent = item.tag[1];
    $("#detailTitle", drawer).textContent = item.title;
    $("#detailDesc", drawer).textContent = item.desc;
    const fields = $("#detailFields", drawer);
    fields.replaceChildren(...item.fields.flatMap(([label, value]) => {
      const dt = document.createElement("dt"); dt.textContent = label;
      const dd = document.createElement("dd"); dd.textContent = value;
      return [dt, dd];
    }));
    const tip = $("#detailTip", drawer);
    tip.replaceChildren();
    const tipHeading = document.createElement("strong"); tipHeading.textContent = "处理建议";
    tip.append(tipHeading, document.createTextNode(item.tip));
    const action = $("#detailAction", drawer);
    action.textContent = item.action[0];
    action.onclick = () => {
      closeDrawer({ restoreFocus: false });
      if (item.action[1] === "upload") $("[data-open-upload]")?.click();
      else showView(item.action[1]);
    };
    drawer.classList.add("is-open");
    $("[data-close-detail]", drawer).focus();
  }

  $$("[data-close-detail]", drawer).forEach((button) => button.addEventListener("click", () => closeDrawer()));
  drawer.addEventListener("keydown", (event) => {
    if (event.key === "Escape") {
      event.stopPropagation();
      closeDrawer();
    }
  });

  triggers.forEach((element) => {
    element.addEventListener("click", (event) => {
      const innerButton = event.target.closest("button");
      if (innerButton && innerButton !== element) return;
      openDetail(element.dataset.detail, element);
    });
    if (element.tagName !== "BUTTON") {
      element.addEventListener("keydown", (event) => {
        if (event.key === "Enter" || event.key === " ") {
          event.preventDefault();
          openDetail(element.dataset.detail, element);
        }
      });
    }
  });
}

function initEntry() {
  const form = $("#loginForm");
  if (!form) return;
  if (!motionQuery.matches) {
    [
      [$(".entry-form-wrap"), 0],
      [$(".entry-statement h1"), 60],
      [$(".entry-statement p"), 120],
      [$(".entry-figure"), 120],
    ].forEach(([element, delay]) => {
      if (!element) return;
      element.animate(
        [{ opacity: 0, transform: "translateY(12px)" }, { opacity: 1, transform: "translateY(0)" }],
        { duration: 300, delay, easing: "cubic-bezier(.16,1,.3,1)", fill: "both" },
      );
    });
  }
  const roleInputs = $$('input[name="role"]', form);
  const username = $('input[name="username"]', form);
  const password = $('input[name="password"]', form);
  const action = $("#loginAction");
  const errorBox = $("#loginError");
  const changeSection = $("#changePassword");
  let pendingRole = null;
  const submitButton = $('button[type="submit"]', form);

  function setLoading(loading) {
    submitButton.disabled = loading;
    submitButton.classList.toggle("is-loading", loading);
    $$("input, button", form).forEach((el) => {
      if (el !== submitButton) el.disabled = loading;
    });
    if (loading) {
      action.textContent = "登录中…";
    } else if (pendingRole) {
      action.textContent = "设置新密码并进入";
    } else {
      updateRole();
    }
  }

  function showError(message) {
    errorBox.textContent = message;
    errorBox.hidden = false;
  }

  function updateRole() {
    const role = $('input[name="role"]:checked', form).value;
    action.textContent = role === "admin" ? "进入管理端" : "进入公司业务端";
  }

  roleInputs.forEach((input) => input.addEventListener("change", updateRole));
  $("#togglePassword").addEventListener("click", (event) => {
    const visible = password.type === "text";
    password.type = visible ? "password" : "text";
    event.currentTarget.setAttribute("aria-label", visible ? "显示密码" : "隐藏密码");
    event.currentTarget.title = visible ? "显示密码" : "隐藏密码";
  });
  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    errorBox.hidden = true;
    setLoading(true);
    const role = pendingRole || $('input[name="role"]:checked', form).value;

    if (pendingRole) {
      const newPassword = $('input[name="new_password"]', form).value;
      const confirmPassword = $('input[name="confirm_password"]', form).value;
      if (newPassword !== confirmPassword) {
        showError("两次输入的新密码不一致。");
        setLoading(false);
        return;
      }
      const changeResponse = await fetch("/api/password/change", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ old_password: password.value, new_password: newPassword }),
      }).catch(() => null);
      const changeResult = await changeResponse?.json().catch(() => ({}));
      if (!changeResponse || !changeResponse.ok) {
        showError(changeResult?.message || "修改密码失败，请稍后重试。");
        setLoading(false);
        return;
      }
      window.location.href = role === "admin" ? "admin.html" : "company.html";
      return;
    }

    const response = await fetch("/api/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ username: username.value.trim(), password: password.value, portal: role }),
    }).catch(() => null);
    const result = await response?.json().catch(() => ({}));
    if (!response || !response.ok) {
      showError(result?.message || "登录服务暂时不可用，请稍后重试。");
      setLoading(false);
      return;
    }
    if (result.must_change_password) {
      pendingRole = role;
      changeSection.hidden = false;
      setLoading(false);
      $('input[name="new_password"]', form).focus();
      return;
    }
    window.location.href = role === "admin" ? "admin.html" : "company.html";
  });
}

async function initAuthGuard() {
  if (portal === "entry") return true;
  try {
    const response = await fetch("/api/me");
    if (response.status === 401) {
      window.location.href = "index.html";
      return false;
    }
    const me = await response.json();
    if (!response.ok || me.role !== portal) {
      window.location.href = "index.html";
      return false;
    }
    state.me = me;
    applyCompanyIdentity(me);
    return true;
  } catch {
    return true;
  }
}

function applyCompanyIdentity(me) {
  if (!me) return;
  const row = $(".side-foot .user-row");
  if (row) {
    const name = me.username || (portal === "company" ? (me.company_name || "公司用户") : "系统管理员");
    const avatar = $(".avatar", row);
    if (avatar) avatar.textContent = name.slice(0, 1);
    const nameEl = $(".user-name", row);
    if (nameEl) nameEl.textContent = name;
    const metaEl = $(".user-meta", row);
    if (metaEl) metaEl.textContent = portal === "company" ? (me.company_name || "公司业务端") : "管理员";
  }
  // The company portal always shows the session-bound company in page copy.
  if (portal === "company" && me.company_name) {
    $$(".company-identity").forEach((el) => { el.textContent = me.company_name; });
  }
}

function initShell() {
  $$(".topbar").forEach((bar) => {
    if (bar.querySelector(".menu-button")) return;
    const button = document.createElement("button");
    button.type = "button";
    button.className = "icon-button menu-button";
    button.setAttribute("aria-label", "打开导航");
    button.setAttribute("aria-expanded", "false");
    button.setAttribute("aria-controls", "sidebar");
    button.title = "打开导航";
    button.innerHTML = '<svg><use href="icons.svg#menu"></use></svg>';
    bar.prepend(button);
  });

  $$(".menu-button").forEach((menuButton) => {
    menuButton.addEventListener("click", () => {
      const open = $("#sidebar").classList.toggle("is-open");
      menuButton.setAttribute("aria-expanded", String(open));
      menuButton.setAttribute("aria-label", open ? "关闭导航" : "打开导航");
    });
  });

  $$(".side-nav a").forEach((item) => {
    const label = $("span", item)?.textContent.trim();
    if (label) {
      item.setAttribute("aria-label", label);
      item.title = label;
    }
  });
  $$("[data-view]").forEach((button) => button.addEventListener("click", (event) => { event.preventDefault(); showView(button.dataset.view); }));
  document.addEventListener("click", (event) => {
    const button = event.target.closest("[data-view-link]");
    if (!button) return;
    event.preventDefault();
    showView(button.dataset.viewLink);
  });
  $$(".logout").forEach((control) => control.addEventListener("click", async (event) => {
    event.preventDefault();
    try {
      await fetch("/api/logout", { method: "POST" });
    } catch { /* 网络异常时仍然回到登录页 */ }
    window.location.href = "index.html";
  }));
  $$("[data-metric-link]").forEach((card) => {
    const activate = () => showView(card.dataset.metricLink);
    card.addEventListener("click", activate);
    card.addEventListener("keydown", (event) => {
      if (event.key === "Enter" || event.key === " ") {
        event.preventDefault();
        activate();
      }
    });
  });
  $$("[data-metric-action=\"upload\"]").forEach((card) => {
    const activate = () => $("[data-open-upload]")?.click();
    card.addEventListener("click", activate);
    card.addEventListener("keydown", (event) => {
      if (event.key === "Enter" || event.key === " ") {
        event.preventDefault();
        activate();
      }
    });
  });
  // Delegated: company table rows are rendered from the API after init.
  document.addEventListener("click", (event) => {
    const toastButton = event.target.closest("[data-toast]");
    if (toastButton) showToast(toastButton.dataset.toast);
  });
  $(".side-nav a[data-view].active")?.setAttribute("aria-current", "page");

  document.addEventListener("click", (event) => {
    if ($("#sidebar")?.classList.contains("is-open") && !event.target.closest("#sidebar") && !event.target.closest(".menu-button")) closeNavigation({ restoreFocus: true });
  });
  document.addEventListener("keydown", (event) => {
    if (event.key === "Escape") closeNavigation({ restoreFocus: true });
  });

  initDetailDrawer();
  initMotion();
}

function companyByName(name) {
  return (state.companies || []).find((item) => item.name === name);
}

const pairSubjectOrder = ["应收", "其他应收", "应付", "其他应付"];

async function setPair(from, to, endDate = "2026-07-31") {
  $$('[data-pair-from]').forEach((item) => { item.textContent = from; });
  $$('[data-pair-to]').forEach((item) => { item.textContent = to; });
  $$("[data-pair-form]").forEach((form) => {
    const fromSelect = $('[name="from"]', form);
    const toSelect = $('[name="to"]', form);
    if (fromSelect && [...fromSelect.options].some((option) => option.value === from)) fromSelect.value = from;
    if (toSelect && [...toSelect.options].some((option) => option.value === to)) toSelect.value = to;
    const endInput = $('[name="end"]', form);
    if (endInput) endInput.value = endDate;
  });
  if (!$("#pairReport")) return;
  const fromCo = companyByName(from);
  const toCo = companyByName(to);
  const notice = $("#pairNotice");
  if (!fromCo || !toCo) {
    state.pairRows = [];
    renderPairRows();
    if (notice) notice.style.display = "";
    return;
  }
  const start = state.calculationStart || "2026-01-01";
  const pairResp = await fetch(`/api/admin/intercompany/pairs/${fromCo.id}/${toCo.id}?from=${encodeURIComponent(start)}&cutoff=${encodeURIComponent(endDate)}`).catch(() => null);
  const eventsResp = await fetch(`/api/admin/intercompany/events?company_a=${fromCo.id}&company_b=${toCo.id}&from=${encodeURIComponent(start)}&cutoff=${encodeURIComponent(endDate)}&limit=200`).catch(() => null);
  const pairResult = await pairResp?.json().catch(() => null);
  const eventsResult = await eventsResp?.json().catch(() => null);
  if (!pairResp?.ok || pairResult?.status !== "ok") {
    showToast("往来查询失败", pairResult?.message || "请稍后重试", "danger");
    return;
  }
  const item = (pairResult.items || [])[0] || {};
  const a = item.a || { period: {}, result: {} };
  const debit = Number(a.period?.debit || 0);
  const credit = Number(a.period?.credit || 0);
  const opening = Number(item.opening?.amount || 0);
  const signed = Number(a.result?.signed_amount || (debit - credit));
  $("#pairPeriod").textContent = `统计口径 ${start}—${endDate}`;
  $("#pairOpening").innerHTML = formatWanHtml(opening);
  $("#pairDebit").innerHTML = formatWanHtml(debit);
  $("#pairCredit").innerHTML = formatWanHtml(credit);
  const dirLabel = signed > 0 ? "应收" : signed < 0 ? "应付" : "持平";
  $("#pairFinal").innerHTML = `${dirLabel} ${formatWanHtml(Math.abs(signed))}`;
  const unresolved = Number(item.unresolved?.count || 0);
  $("#pairReviewStatus").textContent = unresolved ? `含 ${unresolved} 笔待审核` : "无待审核";
  const subjects = item.subjects || {};
  const totals = { 应收: 0, 其他应收: 0, 应付: 0, 其他应付: 0 };
  Object.values(subjects).forEach((bucket) => {
    const label = bucket.label || SUBJECT_CODE_LABEL[bucket.subject_code];
    if (label && totals[label] != null) {
      totals[label] = Number(bucket.a_debit || 0) + Number(bucket.a_credit || 0);
    }
  });
  const events = eventsResult?.items || [];
  $('[data-subject-total="all"]').textContent = `${events.length} 笔`;
  Object.entries(totals).forEach(([subject, value]) => {
    $(`[data-subject-total="${subject}"]`)?.replaceChildren(document.createTextNode(formatCurrency(yuanToWan(value) || 0)));
  });
  if (notice) notice.style.display = events.length ? "none" : "";
  state.pairContext = { from, to, endDate, fromId: fromCo.id, toId: toCo.id };
  state.pairRows = events.map((event) => {
    const outgoing = Number(event.payer_company_id) === Number(fromCo.id);
    return {
      date: String(event.effective_at || "").slice(0, 10),
      direction: outgoing ? "转出" : "转入",
      subject: event.subject_label || SUBJECT_CODE_LABEL[event.subject_code] || "—",
      ownAccount: event.own_account_label || event.own_account || "—",
      counterparty: event.counterparty_company_name || (outgoing ? event.payee_company_name : event.payer_company_name) || to,
      summary: event.summary || event.purpose || "—",
      match: event.state === "confirmed" ? "双边匹配" : "待确认",
      amount: Number(event.amount || 0),
      serial: event.reference || event.source_id || "—",
      batch: event.import_batch_id ? `IMP-${String(event.import_batch_id).padStart(6, "0")}` : "—",
      peerAccount: event.counterparty_account || "—",
      ledgerEventId: event.ledger_event_id,
    };
  });
  renderPairRows();
}

function renderPairRows() {
  const tbody = $("#pairTransactions");
  if (!tbody) return;
  tbody.replaceChildren();
  (state.pairRows || []).forEach((row, index) => {
    const tr = document.createElement("tr");
    tr.dataset.subject = row.subject;
    tr.dataset.pairIdx = String(index);
    [["td", "num", row.date], ["td", "", row.direction], ["td", "", row.subject], ["td", "", row.ownAccount], ["td", "", row.counterparty], ["td", "wrap", row.summary]].forEach(([tag, cls, value]) => {
      const td = document.createElement(tag);
      if (cls) td.className = cls;
      td.textContent = value;
      tr.append(td);
    });
    const matchTd = document.createElement("td");
    const pill = document.createElement("span");
    pill.className = `pill ${row.match === "双边匹配" ? "pill-success" : "pill-warn"}`;
    pill.textContent = row.match;
    matchTd.append(pill);
    const amt = document.createElement("td");
    amt.className = `num-col ${row.direction === "转出" ? "amt-out" : "amt-in"}`;
    amt.textContent = formatCurrency(row.amount);
    const act = document.createElement("td");
    const btn = document.createElement("button");
    btn.type = "button";
    btn.className = "btn btn-sm";
    btn.dataset.trace = String(index);
    btn.textContent = "穿透";
    act.append(btn);
    tr.append(matchTd, amt, act);
    tbody.append(tr);
  });
}

function openTrace(index) {
  const modal = $("#traceModal");
  if (!modal) return;
  const row = state.pairRows?.[index];
  if (!row) return;
  const ctx = state.pairContext || { from: "—", to: "—" };
  $("#traceSub").textContent = `${ctx.from} ↔ ${ctx.to} · ${row.subject} · ${row.date} · ${row.direction} ${formatCurrency(row.amount)} 元`;
  $("#kvOwnTx").textContent = row.serial || "—";
  $("#kvOwnAcct").textContent = row.ownAccount;
  $("#kvPeerCo").textContent = ctx.to;
  $("#kvPeerAcct").textContent = row.peerAccount || "—";
  $("#kvTime").textContent = row.date;
  $("#kvAmt").textContent = `${formatCurrency(row.amount)} 元`;
  $("#kvBatch").textContent = row.batch || "—";
  const evidence = $("#kvEvidence");
  if (row.match === "双边匹配") { evidence.textContent = "已匹配"; evidence.style.color = ""; }
  else { evidence.textContent = "待对方提供"; evidence.style.color = "var(--warn)"; }
  modal.classList.add("open");
}

function initPairQueries() {
  $$("[data-pair-form]").forEach((form) => {
    $("[data-swap]", form)?.addEventListener("click", () => {
      const from = $('[name="from"]', form);
      const to = $('[name="to"]', form);
      const previous = from.value;
      from.value = to.value;
      to.value = previous;
    });
    form.addEventListener("submit", (event) => {
      event.preventDefault();
      const from = $('[name="from"]', form).value;
      const to = $('[name="to"]', form).value;
      const endDate = $('[name="end"]', form)?.value || "2026-07-31";
      const error = $("#pairError");
      if (from === to) {
        if (error) { error.style.display = ""; }
        showToast("请选择两个不同的公司", "同公司账户调拨不进入公司间往来查询", "warn");
        return;
      }
      if (error) error.style.display = "none";
      const notice = $("#pairNotice");
      if (notice) notice.style.display = "none";
      setPair(from, to, endDate);
      showView("pair");
    });
  });

  $$("[data-pair-link]").forEach((button) => button.addEventListener("click", () => {
    const [from, to] = button.dataset.pairLink.split("|");
    setPair(from, to);
    showView("pair");
  }));

  $$("[data-subject-filter]").forEach((button) => button.addEventListener("click", () => {
    const subject = button.dataset.subjectFilter;
    $$("[data-subject-filter]").forEach((item) => {
      const active = item === button;
      item.classList.toggle("active", active);
      item.setAttribute("aria-pressed", String(active));
    });
    $$("#pairTransactions tr").forEach((row) => { row.hidden = subject !== "all" && row.dataset.subject !== subject; });
  }));

  $("#pairTransactions")?.addEventListener("click", (event) => {
    const button = event.target.closest("[data-trace]");
    if (button) openTrace(Number(button.dataset.trace));
  });
  $("#traceClose")?.addEventListener("click", () => $("#traceModal")?.classList.remove("open"));
  $("#traceOk")?.addEventListener("click", () => $("#traceModal")?.classList.remove("open"));
  $("#traceModal")?.addEventListener("click", (event) => { if (event.target === event.currentTarget) event.currentTarget.classList.remove("open"); });
}

function fillAccountSelects(accounts) {
  const usable = accounts.filter((account) => account.usable);
  [$("#accountSelect"), $('#manualEntryForm [name="sourceAccount"]')].forEach((select) => {
    if (!select) return;
    const kept = [...select.options].filter((option) => option.value === "" || option.textContent === "个人过账");
    select.replaceChildren(...kept);
    usable.forEach((account) => {
      const value = `${account.bank_name} · ${accountTail(account.account_number_masked)}`;
      const option = new Option(value, value);
      option.dataset.accountId = account.id;
      select.add(option);
    });
  });
}

function companyAccountMeta(status) {
  if (status === "active") return { status: { cls: "pill-success", label: "启用" }, audit: { cls: "pill-success", label: "已审核" } };
  if (status === "returned") return { status: { cls: "pill-danger", label: "已退回" }, audit: { cls: "pill-danger", label: "已退回" } };
  if (status === "disabled") return { status: { cls: "pill-muted", label: "停用" }, audit: { cls: "pill-success", label: "已审核" } };
  return { status: { cls: "pill-info", label: "待启用" }, audit: { cls: "pill-info", label: "待审核" } };
}

function renderCompanyAccounts(accounts) {
  state.accounts = accounts;
  const tbody = $("#account-tbody");
  if (tbody) {
    tbody.replaceChildren(...accounts.map((account) => {
      const row = document.createElement("tr");
      row.dataset.accountId = account.id;

      const identity = document.createElement("td");
      const name = document.createElement("span"); name.className = "cell-main"; name.textContent = account.bank_name;
      const tail = document.createElement("span"); tail.className = "cell-sub"; tail.textContent = `尾号 ${accountTail(account.account_number_masked)}`;
      identity.append(name, tail);

      const type = document.createElement("td"); type.innerHTML = `<span class="tag">${account.account_type || "—"}</span>`;

      const meta = companyAccountMeta(account.status);
      const statusCell = document.createElement("td"); statusCell.innerHTML = `<span class="pill ${meta.status.cls}">${meta.status.label}</span>`;

      const coverage = document.createElement("td");
      coverage.className = "num muted";
      coverage.textContent = account.usable ? "尚未上传" : "—";

      const auditCell = document.createElement("td"); auditCell.innerHTML = `<span class="pill ${meta.audit.cls}">${meta.audit.label}</span>`;

      const action = document.createElement("td"); action.innerHTML = '<button type="button" class="btn btn-sm" data-account-view>查看明细</button>';

      row.append(identity, type, statusCell, coverage, auditCell, action);
      return row;
    }));
  }
  const count = $("#account-count");
  if (count) count.textContent = accounts.length;
  const foot = $("#account-foot");
  if (foot) foot.textContent = `共 ${accounts.length} 个账户`;
  const empty = $("#account-empty");
  if (empty) empty.hidden = accounts.length > 0;
  fillAccountSelects(accounts);
}

async function loadCompanyAccounts() {
  const tbody = $("#account-tbody");
  showTableLoading(tbody, 6);
  const response = await fetch("/api/company/accounts").catch(() => null);
  if (response?.status === 401 || response?.status === 403) {
    window.location.href = "index.html";
    return;
  }
  if (!response?.ok) {
    showTableError(tbody, 6);
    return;
  }
  const result = await response.json().catch(() => null);
  if (result?.accounts) renderCompanyAccounts(result.accounts);
  else showTableError(tbody, 6);
}

const manualSubjectLabels = {
  receivable: "应收",
  payable: "应付",
  other_receivable: "其他应收",
  other_payable: "其他应付",
};

const manualDirectionLabels = {
  incoming: "收入",
  outgoing: "支出",
};

function appendAdminReviewRow(record, kind) {
  const tbody = $("#auditRows");
  if (!tbody) return;
  const isAccount = kind === "account";
  const isManual = kind === "manual";
  const statusLabel = isAccount
    ? accountStatusLabel(record.status)
    : (record.state === "pending" ? "待管理复核" : (record.state || "待复核"));
  const row = document.createElement("tr");
  row.dataset.storedReview = String(record.id);
  row.dataset.recordId = String(record.id);
  row.dataset.recordKind = kind;
  if (isAccount) row.dataset.accountId = String(record.id);
  if (isManual) {
    row.dataset.decisionId = String(record.decision_id || "");
    row.dataset.requestedSubject = record.requested_subject || "";
  }
  row.dataset.auditType = isAccount ? "账户" : "手工";
  row.dataset.company = isAccount
    ? (record.company_name || "")
    : (record.company_name || record.company || "");
  row.dataset.evidence = isAccount
    ? "公司提交资料、开户行、账号、账户类型与启用日期"
    : "公司手工记录、关联银行流水号、证明附件与提交说明";
  if (isAccount) {
    if (record.status !== "pending") row.dataset.resolved = "true";
    row.dataset.accountStatus = record.status;
  } else if (record.state && record.state !== "pending") {
    row.dataset.resolved = "true";
  }

  const riskCell = document.createElement("td");
  riskCell.innerHTML = '<span class="pill pill-warn">中</span>';

  const identityCell = document.createElement("td");
  const identity = document.createElement("span"); identity.className = "cell-main";
  const detail = document.createElement("span"); detail.className = "cell-sub";
  if (isAccount) {
    identity.textContent = `${record.company_name} · ${record.bank_name} ${String(record.account_number).slice(-4)}`;
    detail.textContent = `${record.account_type} · 完整账号 ${record.account_number}`;
  } else {
    const subject = manualSubjectLabels[record.requested_subject] || record.requested_subject || "—";
    const direction = manualDirectionLabels[record.direction] || record.direction || "";
    const counterparty = record.counterparty_company_name || record.counterparty || "—";
    identity.textContent = `${row.dataset.company} · 手工单 #${record.id}`;
    detail.textContent = `${direction} ${formatCurrency(record.amount)} 元 · ${counterparty} · ${subject}`;
  }
  identityCell.append(identity, detail);

  const typeCell = document.createElement("td");
  typeCell.textContent = isAccount ? "账户登记" : "手工记录";

  const periodCell = document.createElement("td");
  if (isAccount) {
    periodCell.textContent = record.effective_from || "待审核确定";
  } else {
    periodCell.className = "num";
    periodCell.textContent = String(record.occurred_at || record.transactionDate || "").slice(0, 10) || "—";
  }

  const impactCell = document.createElement("td");
  impactCell.className = "wrap";
  impactCell.textContent = isAccount
    ? "账户识别与流水上传"
    : `待确认往来 ${(Number(record.amount) / 10000).toFixed(2)} 万元`;

  const statusCell = document.createElement("td");
  const status = recordStatus(statusLabel);
  statusCell.innerHTML = `<span class="pill ${pillClass(status.className)}">${status.label}</span>`;

  const actionCell = document.createElement("td");
  if (row.dataset.resolved) {
    const isActiveAccount = isAccount && record.status === "active";
    if (isActiveAccount) {
      actionCell.innerHTML = '<button type="button" class="btn btn-sm btn-danger" data-audit-action="disable">停用</button>';
    } else {
      const resolvedLabel = isAccount
        ? ({ returned: "已退回", disabled: "已停用" })[record.status] || "已通过"
        : (statusLabel === "已确认" || record.state === "approved" ? "已通过" : "已驳回");
      actionCell.innerHTML = `<span class="meta">${resolvedLabel} · 系统管理员</span>`;
    }
  } else {
    actionCell.innerHTML = '<div class="row" style="gap:6px;"><button type="button" class="btn btn-sm btn-primary" data-audit-action="approve">通过</button><button type="button" class="btn btn-sm btn-danger" data-audit-action="reject">驳回</button></div>';
  }

  row.append(riskCell, identityCell, typeCell, periodCell, impactCell, statusCell, actionCell);
  tbody.append(row);
}

function appendMatchExceptionRow(item) {
  const tbody = $("#auditRows");
  if (!tbody) return;
  const payer = item.payer_company_name || "—";
  const payee = item.payee_company_name || "—";
  const companyLabel = payer !== "—" ? payer : payee;
  const row = document.createElement("tr");
  row.dataset.storedReview = `match-${item.event_id}`;
  row.dataset.recordId = String(item.event_id);
  row.dataset.recordKind = "match";
  row.dataset.eventId = String(item.event_id);
  row.dataset.revision = String(item.revision ?? "");
  row.dataset.auditType = "单边";
  row.dataset.company = companyLabel;
  row.dataset.evidence = "匹配异常事件、观察流水、参与方与历史决定";
  row.dataset.classification = item.classification || "";

  const riskCell = document.createElement("td");
  riskCell.innerHTML = '<span class="pill pill-danger">高</span>';

  const identityCell = document.createElement("td");
  const identity = document.createElement("span"); identity.className = "cell-main";
  const detail = document.createElement("span"); detail.className = "cell-sub";
  identity.textContent = `${payer} ↔ ${payee} · 事件 #${item.event_id}`;
  detail.textContent = `${item.classification || item.status || "unresolved"} · ${formatCurrency(item.amount)} ${item.currency || "CNY"} · 证据 ${item.evidence_count ?? 0} 条`;
  identityCell.append(identity, detail);

  const typeCell = document.createElement("td");
  typeCell.textContent = "单边匹配";

  const periodCell = document.createElement("td");
  periodCell.className = "num";
  periodCell.textContent = String(item.effective_at || "").slice(0, 10) || "—";

  const impactCell = document.createElement("td");
  impactCell.className = "wrap";
  impactCell.textContent = `匹配异常 ${(Number(item.amount) / 10000).toFixed(2)} 万元 · 阻断往来归集`;

  const statusCell = document.createElement("td");
  statusCell.innerHTML = '<span class="pill pill-danger">待处理</span>';

  const actionCell = document.createElement("td");
  actionCell.innerHTML = '<div class="row" style="gap:6px;"><button type="button" class="btn btn-sm btn-primary" data-audit-action="approve">关闭异常</button><button type="button" class="btn btn-sm btn-danger" data-audit-action="reject">退回重匹配</button></div>';

  row.append(riskCell, identityCell, typeCell, periodCell, impactCell, statusCell, actionCell);
  tbody.append(row);
}

async function loadAdminAuditQueue() {
  const tbody = $("#auditRows");
  if (!tbody) return;
  $$('[data-stored-review]', tbody).forEach((row) => row.remove());
  showTableLoading(tbody, 7);

  const [accountsRes, manualsRes, exceptionsRes] = await Promise.all([
    fetch("/api/admin/accounts?status=pending").catch(() => null),
    fetch("/api/admin/manual-records?state=pending").catch(() => null),
    fetch("/api/admin/match-exceptions").catch(() => null),
  ]);

  if ([accountsRes, manualsRes, exceptionsRes].some((res) => res?.status === 401)) {
    window.location.href = "index.html";
    return;
  }
  if (![accountsRes, manualsRes, exceptionsRes].every((res) => res?.ok)) {
    showTableError(tbody, 7);
    showToast("审核队列加载失败", "请稍后重试", "danger");
    return;
  }

  const [accountsPayload, manualsPayload, exceptionsPayload] = await Promise.all([
    accountsRes.json().catch(() => null),
    manualsRes.json().catch(() => null),
    exceptionsRes.json().catch(() => null),
  ]);

  tbody.replaceChildren();
  (accountsPayload?.accounts || []).forEach((account) => appendAdminReviewRow(account, "account"));
  (manualsPayload?.records || []).forEach((record) => appendAdminReviewRow(record, "manual"));
  (exceptionsPayload?.exceptions || []).forEach((item) => appendMatchExceptionRow(item));

  updateAuditCounts();
  updatePendingAccountNotice();
  await refreshAuditCountsFromApi();
  // 列表条数必须与后端口径一致：不一致时以真实列表为准覆盖标题/角标，避免再出现「数字 3 / 列表 1」。
  const unresolved = $$("#auditRows tr").filter((row) => row.dataset.resolved !== "true");
  if (!state.dashAudit || Number(state.dashAudit.total) !== unresolved.length) {
    const high = unresolved.filter((row) => row.querySelector(".pill-danger")).length;
    const medium = unresolved.filter((row) => row.querySelector(".pill-warn")).length;
    const low = Math.max(0, unresolved.length - high - medium);
    applyAuditCounts({ total: unresolved.length, high, medium, low });
  }
}

function renderStoredAdminReviews() {
  if (!$("#auditRows")) return;
  // 完整队列：待复核账户 + 待审手工单 + 匹配异常，三处口径同源。
  loadAdminAuditQueue();
}

function updateAuditCounts() {
  const rows = $$("#auditRows tr").filter((row) => !row.classList.contains("loading-row"));
  const unresolved = rows.filter((row) => row.dataset.resolved !== "true");
  $$('[data-audit-filter]').forEach((button) => {
    const type = button.dataset.auditFilter;
    const count = unresolved.filter((row) => type === "all" || row.dataset.auditType === type).length;
    const badge = $(".tab-count", button);
    if (badge) badge.textContent = count;
  });
  const foot = $("#auditFoot");
  if (foot) foot.textContent = `共 ${unresolved.length} 项 · 待审核 ${unresolved.length} 项`;
  // 角标 / 首页待审核卡 / 审核中心标题数：优先后端口径；列表加载完成后由 loadAdminAuditQueue 再对齐。
  if (!state.dashAuditFromApi) {
    const high = unresolved.filter((row) => row.querySelector(".pill-danger")).length;
    const medium = unresolved.filter((row) => row.querySelector(".pill-warn")).length;
    const low = Math.max(0, unresolved.length - high - medium);
    applyAuditCounts({ total: unresolved.length, high, medium, low });
  } else {
    const pending = $("#pending-count");
    if (pending) pending.textContent = String(unresolved.length);
  }
}

function applyAuditCounts(audit, { fromApi = false } = {}) {
  if (!audit) return;
  const total = Number(audit.total) || 0;
  const high = Number(audit.high) || 0;
  const medium = Number(audit.medium) || 0;
  const low = Number(audit.low) || 0;
  state.dashAudit = { total, high, medium, low };
  if (fromApi) state.dashAuditFromApi = true;

  const totalEl = $("#dashAuditTotal");
  const footEl = $("#dashAuditFoot");
  if (totalEl) totalEl.innerHTML = `${total}<span class="unit">项</span>`;
  if (footEl) footEl.textContent = `高 ${high} 项 · 中 ${medium} 项 · 其余 ${low} 项低风险`;

  const badge = $("#auditNavBadge") || $('.side-nav a[data-view="audit"] .nav-badge');
  if (badge) {
    badge.textContent = String(total);
    badge.style.display = total > 0 ? "" : "none";
  }
  const pending = $("#pending-count");
  if (pending) pending.textContent = String(total);
}

async function refreshAuditCountsFromApi() {
  const from = await resolveDashStartDate();
  const response = await fetch(`/api/admin/dashboard?from=${encodeURIComponent(from)}`).catch(() => null);
  if (!response?.ok) return null;
  const data = await response.json().catch(() => null);
  if (!data || data.status !== "ok" || !data.audit) return null;
  applyAuditCounts(data.audit, { fromApi: true });
  return data.audit;
}

function companyStatusBadge(status) {
  if (status === "preparing") return { className: "neutral", label: "筹备中" };
  if (status === "disabled") return { className: "danger", label: "已停用" };
  return { className: "success", label: "正常" };
}

function renderAdminCompanyTable(companies) {
  const tbody = $("#companyTable tbody");
  if (!tbody) return;
  state.companies = companies;
  tbody.replaceChildren(...companies.map((company) => {
    const row = document.createElement("tr");
    row.dataset.companyId = company.id;
    const nameCell = document.createElement("td");
    const name = document.createElement("span"); name.className = "cell-main"; name.textContent = company.name;
    const code = document.createElement("span"); code.className = "cell-sub"; code.textContent = company.credit_code || "统一社会信用代码待补充";
    nameCell.append(name, code);
    const accounts = document.createElement("td"); accounts.className = "num-col"; accounts.textContent = `${company.account_count ?? 0}`;
    const usernames = document.createElement("td"); usernames.className = "num"; usernames.textContent = company.usernames || "未创建";
    const cashier = document.createElement("td"); cashier.textContent = company.cashier_name || "未指定";
    const statusCell = document.createElement("td");
    const badge = companyStatusBadge(company.status);
    statusCell.innerHTML = `<span class="pill ${pillClass(badge.className)}">${badge.label}</span>`;
    const actionCell = document.createElement("td");
    actionCell.innerHTML = `<button type="button" class="btn btn-sm" data-company-view="${company.id}">查看</button>`;
    row.append(nameCell, accounts, usernames, cashier, statusCell, actionCell);
    return row;
  }));
  const foot = $("#companyFoot");
  if (foot) foot.textContent = `共 ${companies.length} 家公司`;
}

function updatePendingAccountNotice() {
  const rows = $$('#auditRows tr[data-record-kind="account"]');
  const pending = rows.filter((r) => r.dataset.resolved !== "true");
  const notice = $("#notice-pending-account");
  if (!notice) return;
  if (pending.length) {
    notice.style.display = "";
    $("#pending-account-count").textContent = pending.length;
    const names = [...new Set(pending.map((r) => r.dataset.company))].join("、");
    $("#pending-account-body").textContent = (names || "成员公司") + " 提交了新银行账户登记，等待审核通过后纳入账期流水归集范围。";
  } else {
    notice.style.display = "none";
  }
}

function setSelectOptions(select, names, { keepFirst = false } = {}) {
  if (!select || !names.length) return;
  const kept = keepFirst && select.options.length ? [select.options[0].cloneNode(true)] : [];
  select.replaceChildren(...kept, ...names.map((name) => new Option(name, name)));
}

function fillCompanySelects(names, companies) {
  // Every company picker is driven by master data: a newly created company
  // appears in pair queries, audit filters, flow filters and reminders
  // without any code change.
  $$("[data-pair-form]").forEach((form) => {
    setSelectOptions($('[name="from"]', form), names);
    setSelectOptions($('[name="to"]', form), names);
    const toSelect = $('[name="to"]', form);
    if (toSelect && names.length > 1) toSelect.value = names[1];
  });
  setSelectOptions($("#auditCompany"), names, { keepFirst: true });
  setSelectOptions($("#flowCompany"), names, { keepFirst: true });
  const reminderList = $("#reminderCompanyList");
  if (reminderList && companies?.length) {
    reminderList.replaceChildren(...companies.map((company) => {
      const label = document.createElement("label");
      label.className = "pick";
      const input = document.createElement("input");
      input.type = "checkbox";
      input.name = "company";
      input.value = String(company.id);
      label.append(input, document.createTextNode(company.name));
      return label;
    }));
    syncPendingPicks();
  }
  setSelectOptions($('#openingDialog [name="from"]'), names);
  setSelectOptions($('#openingDialog [name="to"]'), names);
  setSelectOptions($("#ql-self"), names);
  setSelectOptions($("#ql-peer"), names);
  if ($("#ql-peer") && names.length > 1) $("#ql-peer").value = names[0];
  if ($("#ql-self") && names.length > 1) $("#ql-self").value = names[names.length - 1];
}


function openingStatusPill(status) {
  if (status === "confirmed") return { cls: "pill-success", label: "已确认" };
  if (status === "void") return { cls: "pill-danger", label: "已作废" };
  if (status === "superseded") return { cls: "pill-muted", label: "已替代" };
  return { cls: "pill-warn", label: "待确认" };
}

function formatMoneyYuan(value) {
  const num = Number(value);
  if (Number.isNaN(num)) return "—";
  return num.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

function humanizeChangeValue(value) {
  if (value == null) return "—";
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
    return String(value);
  }
  if (typeof value === "object") {
    const entries = Object.entries(value);
    if (!entries.length) return "—";
    return entries.map(([k, v]) => `${k}=${v == null ? "空" : v}`).join("，");
  }
  return String(value);
}

function openModal(id) { $("#" + id)?.classList.add("open"); }
function closeModal(id) { $("#" + id)?.classList.remove("open"); }

function askReason({ title, subtitle, confirmLabel } = {}) {
  return new Promise((resolve) => {
    const dialog = $("#reasonDialog");
    const form = $("#reasonForm");
    const input = $("#reasonInput");
    if (!dialog || !form || !input) {
      resolve(null);
      return;
    }
    const titleEl = $("#reasonDialogTitle");
    const subEl = $("#reasonDialogSub");
    const submitBtn = $("#reasonSubmit");
    if (titleEl && title) titleEl.textContent = title;
    if (subEl && subtitle) subEl.textContent = subtitle;
    if (submitBtn && confirmLabel) submitBtn.textContent = confirmLabel;
    input.value = "";
    const cleanup = () => {
      form.removeEventListener("submit", onSubmit);
      $$("[data-close-reason]").forEach((btn) => btn.removeEventListener("click", onCancel));
      closeModal("reasonDialog");
    };
    const onCancel = () => { cleanup(); resolve(null); };
    const onSubmit = (event) => {
      event.preventDefault();
      const reason = String(input.value || "").trim();
      if (reason.length < 2) {
        showToast("原因过短", "请至少填写 2 个字", "warn");
        return;
      }
      cleanup();
      resolve(reason);
    };
    form.addEventListener("submit", onSubmit);
    $$("[data-close-reason]").forEach((btn) => btn.addEventListener("click", onCancel));
    openModal("reasonDialog");
    input.focus();
  });
}

async function loadCalculationSettings() {
  const response = await fetch("/api/admin/settings/calculation-start").catch(() => null);
  if (!response?.ok) return null;
  const data = await response.json().catch(() => null);
  if (!data) return null;
  const startInput = $("#cs-start");
  const hint = $("#cs-start-hint");
  const locked = $("#cs-start-locked");
  if (startInput) {
    startInput.value = data.calculation_start_date || "";
    startInput.disabled = Boolean(data.locked);
    startInput.dataset.locked = data.locked ? "1" : "0";
    startInput.dataset.current = data.calculation_start_date || "";
  }
  if (data.calculation_start_date) state.calculationStart = data.calculation_start_date;
  if (hint) {
    if (!data.calculation_start_date) {
      hint.textContent = "未设置起算日，系统暂按期间净变动口径显示";
      hint.style.color = "var(--warn)";
    } else {
      hint.textContent = "期初余额以此日前一日的期末数为准";
      hint.style.color = "";
    }
  }
  if (locked) locked.style.display = data.locked ? "" : "none";
  const subtitle = $("#openingSub");
  if (subtitle && data.calculation_start_date) {
    subtitle.textContent = `${data.calculation_start_date} 起算的公司间往来期初数`;
  }
  const summary = $("#openingSummary");
  if (summary && data.summary) {
    summary.style.display = "";
    summary.className = "notice info";
    summary.replaceChildren();
    const wrap = document.createElement("div");
    const title = document.createElement("div");
    title.className = "n-title";
    title.textContent = `共 ${data.summary.company_count} 家公司 · 已确认 ${data.summary.confirmed_pair_count} 对公司对期初`;
    wrap.append(title);
    summary.append(wrap);
  }
  return data;
}

function appendEmptyRow(tbody, cols, text) {
  tbody.replaceChildren();
  const tr = document.createElement("tr");
  const td = document.createElement("td");
  td.colSpan = cols;
  td.className = "empty";
  td.textContent = text;
  tr.append(td);
  tbody.append(tr);
}

async function loadOpeningBalances() {
  const tbody = $("#openingRows");
  if (!tbody) return;
  const response = await fetch("/api/admin/opening-balances").catch(() => null);
  if (!response?.ok) {
    appendEmptyRow(tbody, 8, "加载失败");
    return;
  }
  const data = await response.json().catch(() => null);
  const items = data?.items || [];
  if (!items.length) {
    appendEmptyRow(tbody, 8, "暂无期初记录");
    return;
  }
  tbody.replaceChildren();
  items.forEach((item) => {
    const pill = openingStatusPill(item.status);
    const amount = Number(item.amount);
    const direction = amount >= 0 ? "应收" : "应付";
    const tr = document.createElement("tr");
    if (item.status === "void") tr.style.textDecoration = "line-through";
    tr.dataset.openingId = String(item.id);
    const cells = [
      ["td", "cell-main", item.company_low_name || "—"],
      ["td", "", item.company_high_name || "—"],
      ["td", "", direction],
      ["td", "num-col", formatMoneyYuan(Math.abs(amount))],
      ["td", "", item.actor_username || "—"],
      ["td", "num", (item.created_at || "").slice(0, 10)],
    ];
    cells.forEach(([tag, cls, text]) => {
      const td = document.createElement(tag);
      if (cls) td.className = cls;
      if (cls === "" && text === direction) {
        td.style.color = amount >= 0 ? "var(--success)" : "var(--danger)";
      }
      td.textContent = text;
      tr.append(td);
    });
    const statusTd = document.createElement("td");
    const span = document.createElement("span");
    span.className = `pill ${pill.cls}`;
    span.textContent = pill.label;
    statusTd.append(span);
    tr.append(statusTd);
    const actionTd = document.createElement("td");
    if (item.status === "draft") {
      const btn = document.createElement("button");
      btn.type = "button";
      btn.className = "btn btn-sm";
      btn.dataset.confirmOpening = String(item.id);
      btn.textContent = "确认";
      actionTd.append(btn);
    } else if (item.status === "confirmed") {
      const btn = document.createElement("button");
      btn.type = "button";
      btn.className = "btn btn-sm btn-ghost btn-danger";
      btn.dataset.voidOpening = String(item.id);
      btn.textContent = "作废";
      actionTd.append(btn);
    }
    tr.append(actionTd);
    tbody.append(tr);
  });
}

async function loadCalculationChanges() {
  const tbody = $("#calculationChangeRows");
  if (!tbody) return;
  const response = await fetch("/api/admin/calculation-changes").catch(() => null);
  if (!response?.ok) return;
  const data = await response.json().catch(() => null);
  const items = data?.items || [];
  if (!items.length) {
    appendEmptyRow(tbody, 5, "暂无变更记录");
    return;
  }
  tbody.replaceChildren();
  items.forEach((item) => {
    const tr = document.createElement("tr");
    const values = [
      (item.created_at || "").replace("T", " ").slice(0, 16),
      item.actor_username || "—",
      item.target || "—",
      `${humanizeChangeValue(item.before)} → ${humanizeChangeValue(item.after)}`,
      item.reason || "—",
    ];
    values.forEach((text, index) => {
      const td = document.createElement("td");
      if (index === 0) td.className = "num";
      if (index >= 3) td.className = "wrap";
      td.textContent = text;
      tr.append(td);
    });
    tbody.append(tr);
  });
}

function appendAuditGapRows(gaps) {
  const tbody = $("#auditRows");
  if (!tbody) return;
  $$('#auditRows tr[data-audit-type="断档"]').forEach((row) => row.remove());
  (gaps || []).forEach((gap) => {
    const tr = document.createElement("tr");
    tr.dataset.auditType = "断档";
    tr.dataset.gapId = String(gap.id || "");
    tr.dataset.attestationId = gap.pending_attestation_id ? String(gap.pending_attestation_id) : "";
    tr.dataset.accountId = String(gap.bank_account_id || "");
    tr.dataset.gapStart = gap.gap_start || "";
    tr.dataset.gapEnd = gap.gap_end || "";
    const risk = document.createElement("td");
    risk.innerHTML = '<span class="pill pill-danger">高</span>';
    const company = document.createElement("td");
    company.className = "cell-main";
    company.textContent = `${gap.company_name || "—"} · ${gap.account_number_masked || gap.bank_account_id}`;
    const type = document.createElement("td");
    type.textContent = "流水断档";
    const period = document.createElement("td");
    period.className = "num";
    period.textContent = `${gap.gap_start || "—"} — ${gap.gap_end || "—"}`;
    const impact = document.createElement("td");
    impact.className = "wrap";
    impact.textContent = `${gap.gap_kind || "gap"} · ${gap.day_count || "?"} 天`;
    const status = document.createElement("td");
    const statusPill = document.createElement("span");
    statusPill.className = "pill pill-warn";
    statusPill.textContent = gap.pending_attestation_id ? "待审说明" : "待补传";
    status.append(statusPill);
    const action = document.createElement("td");
    if (gap.pending_attestation_id) {
      action.innerHTML = '<div class="row" style="gap:6px;"><button type="button" class="btn btn-sm btn-primary" data-audit-action="approve-attestation">通过说明</button><button type="button" class="btn btn-sm btn-danger" data-audit-action="reject-attestation">驳回说明</button></div>';
    } else {
      action.innerHTML = '<button type="button" class="btn btn-sm" data-view-link="reminders">去提醒</button>';
    }
    tr.append(risk, company, type, period, impact, status, action);
    tbody.prepend(tr);
  });
}

async function loadAdminCoverageGaps() {
  const response = await fetch("/api/admin/coverage-gaps?status=open").catch(() => null);
  if (!response?.ok) return;
  const data = await response.json().catch(() => null);
  state.coverageGaps = data?.items || [];
  appendAuditGapRows(state.coverageGaps);
  updateAuditCounts();
  const badge = $('.side-nav a[data-view="audit"] .nav-badge');
  if (badge) {
    const n = state.coverageGaps.length || 0;
    badge.textContent = String(n || "");
    badge.hidden = n <= 0;
  }
}

async function loadCompanyCoverageGaps() {
  const response = await fetch("/api/company/coverage-gaps").catch(() => null);
  if (!response?.ok) return [];
  const data = await response.json().catch(() => null);
  const items = (data?.items || []).filter((item) => item.status === "open");
  state.companyCoverageGaps = items;
  const renderNotice = (rootId, bodyId) => {
    const root = $(rootId);
    const body = $(bodyId);
    if (!root || !body) return;
    if (!items.length) {
      root.style.display = "none";
      return;
    }
    root.style.display = "";
    body.textContent = items.slice(0, 3).map((g) => {
      const acct = g.account_number_masked || g.bank_account_id;
      return `${acct}：${g.gap_start} — ${g.gap_end}（${g.day_count || "?"}天）`;
    }).join("；") + (items.length > 3 ? ` 等 ${items.length} 处` : "");
  };
  renderNotice("#companyCoverageNotice", "#companyCoverageBody");
  renderNotice("#flowsCoverageNotice", "#flowsCoverageBody");
  return items;
}

function openAttestationDialog(gap) {
  if (!gap) {
    const gaps = state.companyCoverageGaps || [];
    gap = gaps[0];
  }
  if (!gap) {
    showToast("暂无断档", "当前没有可说明的断档区间", "warn");
    return;
  }
  $("#att-account-id").value = gap.bank_account_id || "";
  $("#att-gap-start").value = gap.gap_start || "";
  $("#att-gap-end").value = gap.gap_end || "";
  const label = $("#att-gap-label");
  if (label) {
    label.textContent = `${gap.account_number_masked || gap.bank_account_id} · ${gap.gap_start} — ${gap.gap_end}`;
  }
  $("#att-reason").value = "";
  $("#att-evidence").value = "";
  openModal("attestationDialog");
}


function formatWan(value) {
  const num = Number(value);
  if (!Number.isFinite(num)) return "0.00";
  const abs = Math.abs(num).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  if (num > 0) return `+${abs}`;
  if (num < 0) return `-${abs}`;
  return abs;
}

function netClass(value) {
  const num = Number(value);
  if (num > 0) return "amt-in";
  if (num < 0) return "amt-out";
  return "";
}

function updateDashAuditCard(audit) {
  applyAuditCounts(audit, { fromApi: true });
}

function renderDashCompanyRows(companies, selectedId) {
  const list = $("#dashCompanyRows");
  if (!list) return;
  if (!companies.length) {
    list.innerHTML = '<div class="dash-company-empty muted">暂无公司或尚无归集往来</div>';
    return;
  }
  const monthLabel = state.dashPeriodMonth ? `${state.dashPeriodMonth}月` : "";
  list.replaceChildren(...companies.map((company) => {
    const item = document.createElement("div");
    item.className = "dash-company-item";
    item.setAttribute("role", "option");
    item.dataset.companyId = company.id;
    item.tabIndex = 0;
    if (String(company.id) === String(selectedId)) {
      item.classList.add("is-selected");
      item.setAttribute("aria-selected", "true");
    } else {
      item.setAttribute("aria-selected", "false");
    }
    const main = document.createElement("div");
    main.className = "dash-company-main";
    const name = document.createElement("span");
    name.className = "dash-company-name";
    name.title = company.name;
    name.textContent = company.name;
    const meta = document.createElement("span");
    meta.className = "dash-company-meta";
    const statusLabel = company.period_status_label || "—";
    meta.textContent = monthLabel
      ? `${company.detail_count ?? 0}笔 · ${monthLabel} ${statusLabel}`
      : `${company.detail_count ?? 0}笔 · ${statusLabel}`;
    main.append(name, meta);
    const net = document.createElement("span");
    net.className = `dash-company-net ${netClass(company.net_wan)}`;
    net.textContent = formatWan(company.net_wan);
    item.append(main, net);
    return item;
  }));
}

function chevronSvg(expanded) {
  // Same inline chevron style used elsewhere in the deployed admin shell.
  if (expanded) {
    return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" style="width:12px;height:12px;display:inline-block;vertical-align:-1px;margin-right:6px;"><path d="m6 9 6 6 6-6"/></svg>';
  }
  return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" style="width:12px;height:12px;display:inline-block;vertical-align:-1px;margin-right:6px;"><path d="m9 18 6-6-6-6"/></svg>';
}

function formatPlainWan(value) {
  const num = Number(value);
  if (!Number.isFinite(num)) return "0.00";
  return Math.abs(num).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

function updateDashDetailSummary(company, payload) {
  const bar = $("#dashDetailSummary");
  if (!bar) return;
  if (!company) {
    bar.hidden = true;
    return;
  }
  bar.hidden = false;
  const count = payload?.groups
    ? payload.groups.reduce((sum, g) => sum + Number(g.count || 0), 0)
    : Number(company.detail_count || 0);
  const debit = payload?.groups
    ? payload.groups.reduce((sum, g) => sum + Number(g.debit_wan || 0), 0)
    : Number(company.debit_wan || 0);
  const credit = payload?.groups
    ? payload.groups.reduce((sum, g) => sum + Number(g.credit_wan || 0), 0)
    : Number(company.credit_wan || 0);
  const ending = payload?.groups
    ? payload.groups.reduce((sum, g) => sum + Number(g.ending_wan || 0), 0)
    : Number(company.net_wan || 0);
  const setText = (id, text, className = "") => {
    const el = $(id);
    if (!el) return;
    el.textContent = text;
    el.className = `dash-summary-value num ${className}`.trim();
  };
  setText("#dashSumCount", String(count));
  setText("#dashSumOpening", "—", "meta");
  setText("#dashSumDebit", formatPlainWan(debit));
  setText("#dashSumCredit", formatPlainWan(credit));
  setText("#dashSumEnding", formatWan(ending), netClass(ending));
}

function showDashDetailEmpty(company) {
  const wrap = $("#dashDetailWrap");
  const empty = $("#dashDetailEmpty");
  const desc = $("#dashDetailEmptyDesc");
  if (wrap) wrap.hidden = true;
  if (empty) empty.hidden = false;
  if (desc) {
    const cutoff = state.dashCutoff || "—";
    // 方案 A 空态：两行克制说明；期初口径仍为不可用（—），不伪造 0.00
    desc.textContent = `期初 — · 截至 ${cutoff} 无明细记录 · 导入流水并归集后在此展示`;
  }
  updateDashDetailSummary(null);
}

function showDashDetailTable() {
  const wrap = $("#dashDetailWrap");
  const empty = $("#dashDetailEmpty");
  if (wrap) wrap.hidden = false;
  if (empty) empty.hidden = true;
}

function renderDashDetail(payload, { expandFirst = false, company = null } = {}) {
  const tbody = $("#dashDetailRows");
  const title = $("#dashDetailTitle");
  if (!tbody) return;
  const selected = company || (state.dashCompanies || []).find(
    (c) => String(c.id) === String(payload?.company_id || state.dashSelectedId)
  ) || null;

  if (!payload) {
    if (title) title.textContent = "请选择左侧公司";
    showDashDetailTable();
    tbody.innerHTML = '<tr><td colspan="6" class="muted">请选择左侧公司</td></tr>';
    updateDashDetailSummary(null);
    return;
  }
  if (title) title.textContent = payload.company_name || selected?.name || "公司往来明细";
  const sub = $("#dashDetailSub");
  if (sub) sub.textContent = "按对方公司分组 · 二级默认收起 · 已确认 / 待确认分列";

  const groups = payload.groups || [];
  if (!groups.length) {
    showDashDetailEmpty(selected || { detail_count: 0, debit_wan: 0, credit_wan: 0, net_wan: 0 });
    tbody.replaceChildren();
    return;
  }

  showDashDetailTable();
  updateDashDetailSummary(selected, payload);
  const rows = [];
  groups.forEach((group, index) => {
    const expanded = expandFirst && index === 0;
    const groupTr = document.createElement("tr");
    groupTr.className = "clickable";
    groupTr.dataset.peerGroup = String(group.peer_id);
    groupTr.dataset.peerName = group.peer_name;
    groupTr.dataset.expanded = expanded ? "true" : "false";
    groupTr.innerHTML = `
      <td style="font-weight:550;">${chevronSvg(expanded)}<span data-peer-label>${group.peer_name}</span></td>
      <td class="num-col">${group.count}</td>
      <td class="num-col meta">—</td>
      <td class="num-col">${Number(group.debit_wan).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</td>
      <td class="num-col">${Number(group.credit_wan).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</td>
      <td class="num-col ${netClass(group.ending_wan)}">${formatWan(group.ending_wan)}</td>`;
    rows.push(groupTr);
    (group.lines || []).forEach((line) => {
      const lineTr = document.createElement("tr");
      lineTr.dataset.peerChild = String(group.peer_id);
      lineTr.style.display = expanded ? "" : "none";
      const dirTag = line.direction === "debit"
        ? '<span class="tag">借</span>'
        : '<span class="tag">贷</span>';
      lineTr.innerHTML = `
        <td style="padding-left: 28px;">${dirTag} <span class="meta">${line.date}</span> ${line.summary}</td>
        <td class="num-col">1</td>
        <td class="num-col meta">—</td>
        <td class="num-col">${line.direction === "debit" ? Number(line.amount_wan).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : "—"}</td>
        <td class="num-col">${line.direction === "credit" ? Number(line.amount_wan).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : "—"}</td>
        <td class="num-col meta">—</td>`;
      rows.push(lineTr);
    });
  });
  tbody.replaceChildren(...rows);
}

async function resolveDashStartDate() {
  if (state.dashFrom) return state.dashFrom;
  const response = await fetch("/api/admin/settings").catch(() => null);
  if (response?.ok) {
    const result = await response.json().catch(() => null);
    const startDate = result?.settings?.start_date;
    if (startDate) {
      state.dashFrom = startDate;
      return startDate;
    }
  }
  return "2026-01-01";
}

async function loadDashCompanyDetail(companyId) {
  if (!companyId) return;
  state.dashSelectedId = companyId;
  const company = (state.dashCompanies || []).find((c) => String(c.id) === String(companyId)) || null;
  renderDashCompanyRows(state.dashCompanies || [], companyId);
  const title = $("#dashDetailTitle");
  if (title && company) title.textContent = company.name;
  updateDashDetailSummary(company, null);
  showDashDetailTable();
  const tbody = $("#dashDetailRows");
  if (tbody) tbody.innerHTML = '<tr class="loading-row"><td colspan="6">加载中…</td></tr>';
  const from = await resolveDashStartDate();
  const response = await fetch(`/api/admin/dashboard/companies/${companyId}?from=${encodeURIComponent(from)}`).catch(() => null);
  if (!response?.ok) {
    if (tbody) tbody.innerHTML = '<tr><td colspan="6" class="muted">明细加载失败</td></tr>';
    return;
  }
  const result = await response.json().catch(() => null);
  renderDashDetail(result, { company });
}

async function loadAdminDashboard() {
  if (!$("#dashCompanyRows")) return;
  const from = await resolveDashStartDate();
  const response = await fetch(`/api/admin/dashboard?from=${encodeURIComponent(from)}`).catch(() => null);
  if (!response?.ok) {
    $("#dashCompanyRows").innerHTML = '<div class="dash-company-empty muted">总览加载失败</div>';
    return;
  }
  const data = await response.json().catch(() => null);
  if (!data || data.status !== "ok") {
    $("#dashCompanyRows").innerHTML = '<div class="dash-company-empty muted">总览加载失败</div>';
    return;
  }
  state.dashCompanies = data.companies || [];
  state.dashFrom = data.from_date;
  state.dashCutoff = data.cutoff;
  state.dashPeriodMonth = data.period_month;
  updateDashAuditCard(data.audit);
  const listSub = $("#dashListSub");
  if (listSub) {
    listSub.textContent = `${data.from_date} 至 ${data.cutoff} · 单位：万元 · 左侧选择公司，右侧查看其往来明细`;
  }
  const meta = $("#dashMasterMeta");
  if (meta) {
    const totals = data.totals || {};
    const companyCount = totals.company_count ?? state.dashCompanies.length;
    const detailCount = totals.detail_count ?? 0;
    meta.textContent = `${companyCount} 家公司 · 明细 ${detailCount} 笔`;
  }
  const selected = state.dashSelectedId || state.dashCompanies[0]?.id;
  renderDashCompanyRows(state.dashCompanies, selected);
  if (selected) await loadDashCompanyDetail(selected);
}

function initDashboard() {
  if (!$("#companyMasterDetail")) return;
  const selectCompany = (companyId) => {
    if (!companyId) return;
    loadDashCompanyDetail(companyId);
  };
  $("#dashCompanyRows")?.addEventListener("click", (event) => {
    const item = event.target.closest(".dash-company-item[data-company-id]");
    if (!item) return;
    selectCompany(item.dataset.companyId);
  });
  $("#dashCompanyRows")?.addEventListener("keydown", (event) => {
    if (event.key !== "Enter" && event.key !== " ") return;
    const item = event.target.closest(".dash-company-item[data-company-id]");
    if (!item) return;
    event.preventDefault();
    selectCompany(item.dataset.companyId);
  });
  $("#dashDetailRows")?.addEventListener("click", (event) => {
    const tr = event.target.closest("tr[data-peer-group]");
    if (!tr) return;
    const peerId = tr.dataset.peerGroup;
    const open = tr.dataset.expanded !== "true";
    tr.dataset.expanded = open ? "true" : "false";
    const nameCell = tr.children[0];
    if (nameCell) {
      nameCell.innerHTML = `${chevronSvg(open)}<span data-peer-label>${tr.dataset.peerName || ""}</span>`;
    }
    $$('#dashDetailRows tr[data-peer-child]').forEach((child) => {
      if (child.dataset.peerChild === peerId) child.style.display = open ? "" : "none";
    });
  });
  $("#company-search")?.addEventListener("input", function () {
    const q = this.value.trim();
    $$("#dashCompanyRows .dash-company-item[data-company-id]").forEach((item) => {
      item.style.display = (!q || item.textContent.includes(q)) ? "" : "none";
    });
  });
  loadAdminDashboard();
}

function formatReminderTime(value) {
  if (!value) return "—";
  const text = String(value).replace("T", " ").slice(0, 16);
  return text;
}

function reminderTypePill(ruleKey) {
  if (ruleKey === "pending_review") return "pill-warn";
  if (ruleKey === "manual") return "pill-warn";
  return "pill-danger";
}

function reminderStatusPill(statusUi) {
  return { unread: "pill-danger", doing: "pill-warn", done: "pill-success" }[statusUi] || "pill-muted";
}

function reminderStatusLabel(statusUi) {
  return { unread: "未读", doing: "处理中", done: "已完成" }[statusUi] || statusUi;
}

function updatePickCount() {
  const countEl = $("#pick-count");
  if (!countEl) return;
  const n = $$('#reminderCompanyList input[name="company"]:checked').length;
  countEl.textContent = "已选 " + n + " 家";
}

function syncPendingPicks() {
  $$(".pending-check:checked").forEach((input) => {
    const companyId = input.closest(".list-row")?.dataset.companyId;
    if (!companyId) return;
    const chip = document.querySelector('#reminderCompanyList input[name="company"][value="' + companyId + '"]');
    if (chip) chip.checked = true;
  });
  updatePickCount();
}

async function loadAdminRemindersPending() {
  const list = $("#pending-list");
  const empty = $("#pending-empty");
  const summary = $("#pending-summary");
  const sendAll = $("#pending-send-all");
  if (!list) return;
  const response = await fetch("/api/admin/reminders/pending").catch(() => null);
  if (response?.status === 401) { window.location.href = "index.html"; return; }
  const result = await response?.json().catch(() => ({}));
  if (!response?.ok) return;
  const findings = result.findings || [];
  list.querySelectorAll(".list-row").forEach((row) => row.remove());
  if (!findings.length) {
    if (empty) empty.style.display = "";
    if (summary) summary.textContent = "0 家公司 · 0 项";
    if (sendAll) { sendAll.disabled = true; sendAll.textContent = "全部一键发送"; }
    return;
  }
  if (empty) empty.style.display = "none";
  const companies = new Set(findings.map((item) => item.company_id)).size;
  if (summary) summary.textContent = `${companies} 家公司 · ${findings.length} 项`;
  if (sendAll) {
    sendAll.disabled = false;
    sendAll.textContent = "全部一键发送";
    sendAll.dataset.keys = JSON.stringify(findings.map((item) => item.dedupe_key));
  }
  findings.forEach((item) => {
    const row = document.createElement("div");
    row.className = "list-row";
    row.dataset.dedupeKey = item.dedupe_key;
    row.dataset.companyId = String(item.company_id);
    const sentHint = item.send_count > 0 ? ` · 已提醒 ${item.send_count} 次` : "";
    row.innerHTML =
      '<label class="row" style="gap:6px;flex:none;"><input type="checkbox" class="pending-check" style="width:auto;" /></label>' +
      `<span class="pill ${reminderTypePill(item.rule_key)}">${item.rule_label}</span>` +
      `<div class="lr-main"><div class="lr-title">${item.title}</div>` +
      `<div class="lr-sub"><span class="meta">${item.reason}${sentHint}</span></div></div>` +
      `<span class="pill ${item.duration_pill}" style="flex:none;font-family:var(--font-mono);">${item.days_open} 天</span>` +
      `<div class="lr-side"><button type="button" class="btn btn-sm btn-primary pending-send-one">发送提醒</button></div>`;
    list.append(row);
  });
}

async function loadAdminRemindersHistory(sourceFilter) {
  const tbody = $("#reminder-tbody");
  if (!tbody) return;
  const query = sourceFilter && sourceFilter !== "all" ? `?source=${sourceFilter === "system" ? "auto" : "manual"}` : "";
  const response = await fetch(`/api/admin/reminders${query}`).catch(() => null);
  if (response?.status === 401) { window.location.href = "index.html"; return; }
  const result = await response?.json().catch(() => ({}));
  if (!response?.ok) return;
  const items = result.reminders || [];
  tbody.replaceChildren(...items.map((item) => {
    const tr = document.createElement("tr");
    tr.dataset.source = item.source === "auto" ? "system" : "manual";
    tr.dataset.reminderId = String(item.id);
    const summary = item.content.length > 46 ? item.content.slice(0, 46) + "…" : item.content;
    const sourceCell = item.source === "auto"
      ? '<span class="pill pill-info">系统</span>'
      : '<span class="tag">人工</span>';
    tr.innerHTML =
      `<td class="cell-main">${item.company_name}</td>` +
      `<td>${sourceCell}</td>` +
      `<td><span class="pill ${reminderTypePill(item.rule_key)}">${item.display_type}</span></td>` +
      `<td class="wrap">${summary.replace(/</g, "&lt;")}</td>` +
      `<td class="num-col">${formatReminderTime(item.last_sent_at)}</td>` +
      `<td class="num-col">${item.deadline || "—"}</td>` +
      `<td><span class="pill ${reminderStatusPill(item.status_ui)}">${reminderStatusLabel(item.status_ui)}</span></td>` +
      '<td><div class="row" style="gap:6px;"><button type="button" class="btn btn-sm act-remind">再提醒</button><button type="button" class="btn btn-sm btn-ghost act-detail">详情</button></div></td>';
    return tr;
  }));
  refreshReminderCounts(result.stats || {});
}

function refreshReminderCounts(stats) {
  const rows = $$("#reminder-tbody tr");
  let all = rows.length, sys = 0, man = 0;
  rows.forEach((r) => { if (r.dataset.source === "system") sys++; else man++; });
  $("#count-all").textContent = all;
  $("#count-system").textContent = sys;
  $("#count-manual").textContent = man;
  $("#table-foot-count").textContent = `共 ${all} 条提醒记录`;
  if (stats) {
    $("#table-foot-state").textContent = `未读 ${stats.unread || 0} · 处理中 ${stats.doing || 0} · 已完成 ${stats.done || 0}`;
  }
}

async function sendPendingReminders(keys) {
  const response = await fetch("/api/admin/reminders/send", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ dedupe_keys: keys }),
  }).catch(() => null);
  if (response?.status === 401) { window.location.href = "index.html"; return null; }
  const result = await response?.json().catch(() => ({}));
  if (!response?.ok) {
    showToast("发送失败", result?.message || "请稍后重试", "danger");
    return null;
  }
  return result;
}

function openReminderDetailDrawer(reminderId) {
  fetch(`/api/admin/reminders/${reminderId}`)
    .then((response) => response.json())
    .then((result) => {
      const item = result.reminder;
      if (!item) return;
      $("#rd-title").textContent = item.title;
      $("#rd-sub").textContent = item.content;
      const sourcePill = $("#rd-source-pill");
      if (sourcePill) {
        sourcePill.textContent = item.source === "auto" ? "系统" : "人工";
        sourcePill.className = item.source === "auto" ? "pill pill-info" : "tag";
      }
      $("#rd-fields").innerHTML =
        `<dt>公司</dt><dd>${item.company_name}</dd>` +
        `<dt>类型</dt><dd>${item.display_type}</dd>` +
        `<dt>触发原因</dt><dd>${item.rule_params?.pending_count ? `待处理 ${item.rule_params.pending_count} 项` : (item.rule_params?.period || "—")}</dd>` +
        `<dt>发送时间</dt><dd>${formatReminderTime(item.last_sent_at)}</dd>` +
        `<dt>催办次数</dt><dd>${item.send_count}</dd>` +
        `<dt>处理状态</dt><dd>${reminderStatusLabel(item.status_ui)}</dd>`;
      $("#rd-events").innerHTML = (item.events || []).map((ev) =>
        `<div class="list-row"><div class="lr-main"><div class="lr-title">${ev.event_type}</div><div class="lr-sub meta">${formatReminderTime(ev.created_at)} · ${ev.actor}${ev.detail ? " · " + ev.detail : ""}</div></div></div>`,
      ).join("") || '<div class="empty"><div class="e-title">暂无事件</div></div>';
      $("#reminder-detail-drawer")?.classList.add("is-open");
    })
    .catch(() => showToast("加载详情失败", "", "danger"));
}

function initAdminReminders() {
  loadAdminRemindersPending();
  loadAdminRemindersHistory("all");

  $("#reminder-scan-btn")?.addEventListener("click", async () => {
    const response = await fetch("/api/admin/reminders/scan", { method: "POST" }).catch(() => null);
    if (response?.status === 401) { window.location.href = "index.html"; return; }
    const result = await response?.json().catch(() => ({}));
    if (!response?.ok) {
      showToast("扫描失败", result?.message || "请稍后重试", "danger");
      return;
    }
    await loadAdminRemindersPending();
    showToast("扫描完成", `发现 ${result.summary?.items || 0} 项待提醒`, "success");
  });

  $("#pending-send-all")?.addEventListener("click", async () => {
    const checked = $$(".pending-check:checked");
    let keys;
    if (checked.length) {
      keys = checked.map((input) => input.closest(".list-row")?.dataset.dedupeKey).filter(Boolean);
    } else {
      keys = JSON.parse($("#pending-send-all").dataset.keys || "[]");
    }
    if (!keys.length) return;
    const result = await sendPendingReminders(keys);
    if (!result) return;
    showToast(checked.length ? "已发送选中提醒" : "已全部发送", `共 ${result.count} 项`, "success");
    await loadAdminRemindersPending();
    await loadAdminRemindersHistory($("#reminder-tabs .active")?.dataset.filter || "all");
  });

  $("#pending-list")?.addEventListener("click", async (event) => {
    const btn = event.target.closest(".pending-send-one");
    if (!btn) return;
    const row = btn.closest(".list-row");
    const key = row?.dataset.dedupeKey;
    if (!key) return;
    const result = await sendPendingReminders([key]);
    if (!result) return;
    if (!motionQuery.matches) {
      row.style.transition = "opacity 0.2s ease";
      row.style.opacity = "0";
      setTimeout(() => row.remove(), 200);
    } else {
      row.remove();
    }
    showToast("已发送提醒", "", "success");
    await loadAdminRemindersPending();
    await loadAdminRemindersHistory($("#reminder-tabs .active")?.dataset.filter || "all");
  });

  $("#pending-list")?.addEventListener("change", (event) => {
    if (!event.target.classList.contains("pending-check")) return;
    const checked = $$(".pending-check:checked").length;
    const sendAll = $("#pending-send-all");
    if (sendAll) sendAll.textContent = checked ? `发送选中（${checked}）` : "全部一键发送";
    if (event.target.checked) {
      const companyId = event.target.closest(".list-row")?.dataset.companyId;
      const chip = companyId && document.querySelector('#reminderCompanyList input[name="company"][value="' + companyId + '"]');
      if (chip) chip.checked = true;
    }
    updatePickCount();
  });

  $("#reminderCompanyList")?.addEventListener("change", (event) => {
    if (event.target.name === "company") updatePickCount();
  });

  $("#reminderForm")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const form = event.currentTarget;
    const checked = Array.prototype.slice.call(form.querySelectorAll('input[name="company"]:checked'));
    const type = $("#reminder-type").value;
    const content = $("#reminder-content").value.trim();
    const deadline = $("#reminder-deadline").value;
    $("#company-error").style.display = checked.length ? "none" : "";
    $("#content-error").style.display = content ? "none" : "";
    if (!checked.length || !content) return;
    for (const input of checked) {
      const response = await fetch("/api/admin/reminders/manual", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          company_id: Number(input.value),
          display_type: type,
          content,
          deadline: deadline || null,
        }),
      }).catch(() => null);
      if (response?.status === 401) { window.location.href = "index.html"; return; }
      const result = await response?.json().catch(() => ({}));
      if (!response?.ok) {
        showToast("发送失败", result?.message || "请稍后重试", "danger");
        return;
      }
    }
    const companies = checked.map((c) => {
      const company = (state.companies || []).find((item) => String(item.id) === c.value);
      return company?.name || c.value;
    }).join("、");
    form.querySelectorAll('input[name="company"]').forEach((c) => { c.checked = false; });
    updatePickCount();
    const sendHint = $("#send-hint");
    sendHint.style.display = "";
    sendHint.textContent = `已发送给 ${companies}，共 ${checked.length} 家公司。`;
    clearTimeout(sendHint._t);
    sendHint._t = setTimeout(() => { sendHint.style.display = "none"; }, 4000);
    showToast("人工提醒已发送", companies, "success");
    await loadAdminRemindersHistory($("#reminder-tabs .active")?.dataset.filter || "all");
  });

  $("#reminder-tabs")?.addEventListener("click", (event) => {
    const btn = event.target.closest("button[data-filter]");
    if (!btn) return;
    $("#reminder-tabs").querySelectorAll("button").forEach((b) => b.classList.remove("active"));
    btn.classList.add("active");
    loadAdminRemindersHistory(btn.dataset.filter);
  });

  $("#reminder-tbody")?.addEventListener("click", async (event) => {
    const detailBtn = event.target.closest(".act-detail");
    if (detailBtn) {
      const tr = detailBtn.closest("tr");
      openReminderDetailDrawer(tr.dataset.reminderId);
      return;
    }
    const remindBtn = event.target.closest(".act-remind");
    if (!remindBtn) return;
    const tr = remindBtn.closest("tr");
    const response = await fetch(`/api/admin/reminders/${tr.dataset.reminderId}/resend`, { method: "POST" }).catch(() => null);
    if (response?.status === 401) { window.location.href = "index.html"; return; }
    const result = await response?.json().catch(() => ({}));
    if (!response?.ok) {
      showToast("再提醒失败", result?.message || "请稍后重试", "danger");
      return;
    }
    remindBtn.textContent = "已再提醒";
    remindBtn.disabled = true;
    await loadAdminRemindersHistory($("#reminder-tabs .active")?.dataset.filter || "all");
    showToast("已再次发送提醒", "", "success");
  });

  $$("[data-close-rd]").forEach((btn) => btn.addEventListener("click", () => {
    $("#reminder-detail-drawer")?.classList.remove("is-open");
  }));
}

async function loadAdminCompanies() {
  const tbody = $("#companyTable tbody");
  showTableLoading(tbody, 6);
  const response = await fetch("/api/admin/companies").catch(() => null);
  if (!response?.ok) {
    showTableError(tbody, 6);
    return;
  }
  const result = await response.json().catch(() => null);
  const companies = result?.companies || [];
  state.companies = companies;
  renderAdminCompanyTable(companies);
  fillCompanySelects(companies.map((company) => company.name), companies);
  if (companies.length >= 2) setPair(companies[0].name, companies[1].name);
}

function nextYearMonth(ym) {
  const [y, m] = String(ym).split("-").map(Number);
  const month = m === 12 ? 1 : m + 1;
  const year = m === 12 ? y + 1 : y;
  return `${year}-${String(month).padStart(2, "0")}`;
}

function collapseTimeline(items) {
  if (window.innerWidth > 460 || !items?.length) return items;
  const locked = items.filter((item) => item.cell === "locked");
  const rest = items.filter((item) => item.cell !== "locked");
  if (locked.length < 2) return items;
  return [
    {
      year_month: `${locked[0].year_month}—${locked[locked.length - 1].year_month.slice(5)}`,
      cell: "locked",
      label: "均已锁定",
      merged: true,
    },
    ...rest,
  ];
}

function renderTimeline(items) {
  const root = $("#periodTimeline");
  if (!root) return;
  root.replaceChildren();
  collapseTimeline(items || []).forEach((item) => {
    const cell = document.createElement("div");
    const klass = item.cell === "locked" ? "locked" : item.cell === "current" ? "current" : item.cell === "reopened" ? "reopened" : item.cell === "failed" ? "failed" : "open";
    cell.className = `tl-cell ${klass}`;
    const month = document.createElement("div");
    month.className = "tl-month";
    month.textContent = item.year_month;
    const stateEl = document.createElement("div");
    stateEl.className = "tl-state";
    stateEl.textContent = item.label;
    cell.append(month, stateEl);
    root.append(cell);
  });
}

function setClosingChecks(checks) {
  (checks || []).forEach((check) => {
    const row = $(`#closingPanel [data-closing-key="${check.key}"]`);
    if (!row) return;
    const ok = !!check.ok;
    const mark = $("[data-closing-check]", row);
    const sub = $("[data-closing-sub]", row);
    const stateEl = $("[data-closing-state]", row);
    if (mark) {
      mark.className = `pill ${ok ? "pill-success" : "pill-danger"}`;
      mark.textContent = check.title;
    }
    if (sub) sub.textContent = check.detail || "—";
    if (stateEl) {
      stateEl.className = `pill ${ok ? "pill-success" : check.blocking ? "pill-danger" : "pill-warn"}`;
      stateEl.textContent = ok ? "已通过" : check.blocking ? "阻断" : "待处理";
    }
  });
}

function applyClosePanel(overview) {
  state.periodClose = overview;
  const close = overview.close || {};
  const ym = close.year_month || overview.target_month || "—";
  renderTimeline(overview.timeline);
  const start = state.calculationStart || "—";
  if ($("#timelineSub")) $("#timelineSub").textContent = `全局起算日 ${start} 起，每月 ${overview.closing_day || "—"} 日结账`;
  setClosingChecks(close.checks);
  $("#ckMonth").textContent = ym;
  const totals = close.snapshot?.totals || {};
  const net = totals.net_wan ?? totals.debit_wan;
  if ($("#ckNet")) $("#ckNet").textContent = net != null ? `${formatCurrency(net)} 万元` : "—";
  $("#ckCarry").textContent = ym !== "—" ? `结转至 ${nextYearMonth(ym)} 期初` : "—";
  $("#ckReport").textContent = close.report_no || "尚未生成";
  ["block-notice", "failed-notice", "closed-notice", "reopened-notice"].forEach((id) => {
    const el = $(`#${id}`);
    if (el) el.style.display = "none";
  });
  const status = $("#closingStatus");
  const btn = $("#executeClosing");
  const panel = $("#closingPanel");
  const busy = $("#closingBusy");
  const dl = $("#downloadMonthReport");
  panel?.classList.remove("is-processing");
  if (busy) busy.hidden = true;
  if (dl) dl.hidden = !close.report_no;
  const history = overview.history;
  if ($("#closingHistory")) {
    $("#closingHistory").textContent = history
      ? `最近：${history.year_month} · ${history.closed_by_username || "—"} · ${String(history.closed_at || "").slice(0, 16).replace("T", " ")}`
      : "尚无结账记录";
  }
  const desc = $("#closingDescription");
  if (close.status === "closing") {
    panel?.classList.add("is-processing");
    if (busy) busy.hidden = false;
    status.className = "pill pill-info";
    status.textContent = "处理中";
    desc.textContent = `${ym} · 正在锁定账期并生成月报`;
    btn.disabled = true;
    return;
  }
  if (close.status === "failed") {
    status.className = "pill pill-danger";
    status.textContent = "结账失败";
    desc.textContent = `${ym} · 未改动任何数据`;
    const failed = $("#failed-notice");
    if (failed) {
      failed.style.display = "";
      $("#failed-notice-body").textContent = `${close.fail_reason || "结账失败"} · 未改动任何数据，可重新执行。`;
    }
    btn.disabled = !close.ready;
    btn.className = "btn btn-primary";
    btn.textContent = "重新执行结账";
    return;
  }
  if (close.status === "closed" || close.locked) {
    status.className = "pill pill-lock";
    status.textContent = "已锁定";
    desc.textContent = `${ym} · 已结账锁定`;
    const closed = $("#closed-notice");
    if (closed) {
      closed.style.display = "";
      $("#closed-notice-title").textContent = `${ym} 已结账`;
      $("#closed-notice-body").textContent = `月报 ${close.report_no || "—"} · 结账人 ${close.closed_by_username || "—"} · ${String(close.closed_at || "").slice(0, 16).replace("T", " ")}`;
    }
    btn.disabled = false;
    btn.className = "btn";
    btn.textContent = `申请重开 ${ym} 账期`;
    btn.dataset.mode = "reopen";
    return;
  }
  if (close.status === "reopened") {
    status.className = "pill pill-warn";
    status.textContent = "已重开";
    const days = close.reopen_remaining_days;
    desc.textContent = `${ym} · 重开窗口内可更正`;
    const reopened = $("#reopened-notice");
    if (reopened) {
      reopened.style.display = "";
      $("#reopened-notice-body").textContent = `审批通过后窗口截止 ${close.reopen_window_end || "—"} · 剩 ${days == null ? "—" : days} 天，到期自动恢复锁定。`;
    }
    btn.disabled = !close.ready;
    btn.className = "btn btn-warn";
    btn.textContent = "提前结束重开并重新结账";
    btn.dataset.mode = "close";
    return;
  }
  const blocked = (close.blockers || []).length > 0 || !close.ready;
  status.className = `pill ${blocked ? "pill-danger" : "pill-success"}`;
  status.textContent = blocked ? "已阻断" : "可结账";
  desc.textContent = blocked ? `${ym} · 当前未达到结账条件` : `${ym} · 全部前置检查已通过`;
  if (blocked) {
    const block = $("#block-notice");
    if (block) {
      block.style.display = "";
      $("#block-notice-title").textContent = `存在阻断项，暂不能执行 ${ym} 月度结账`;
      $("#block-notice-body").textContent = (close.blockers || []).map((item) => item.detail).join("；") || "请先处理待审核事项与账户断档。";
    }
  }
  btn.disabled = blocked;
  btn.className = "btn btn-primary";
  btn.textContent = `执行 ${ym} 月度结账`;
  btn.dataset.mode = "close";
  if (blocked) btn.title = "请先处理全部阻断事项";
  else btn.removeAttribute("title");
}

async function loadPeriodClose() {
  if (!$("#closingPanel")) return;
  const response = await fetch("/api/admin/period-closes").catch(() => null);
  if (response?.status === 401) { window.location.href = "index.html"; return; }
  const result = await response?.json().catch(() => null);
  if (!response?.ok || result?.status !== "ok") {
    showToast("结账状态加载失败", result?.message || "请稍后重试", "danger");
    return;
  }
  applyClosePanel(result);
}

function initPeriodClose() {
  $("#runClosingCheck")?.addEventListener("click", () => loadPeriodClose());
  $("#downloadMonthReport")?.addEventListener("click", () => {
    const ym = state.periodClose?.close?.year_month;
    if (ym) window.location.href = `/api/admin/period-closes/${ym}/report.json`;
  });
  $("#executeClosing")?.addEventListener("click", () => {
    const mode = $("#executeClosing")?.dataset.mode || "close";
    const ym = state.periodClose?.close?.year_month || "";
    if (mode === "reopen") {
      $("#reopenRequestTitle").textContent = `申请重开 ${ym} 账期`;
      openModal("reopenRequestDialog");
      return;
    }
    $("#closingDialogTitle").textContent = `确认执行 ${ym} 月度结账`;
    $("#closingDialogSub").textContent = `结账后 ${ym} 流水与往来确认将锁定，不可再直接修改。`;
    $("#cdMonth").textContent = ym;
    $("#cdChecks").textContent = (state.periodClose?.close?.checks || []).every((c) => c.ok) ? "全部前置检查已通过" : "仍有未通过项";
    $("#cdCarry").textContent = `往来净额将结转至 ${nextYearMonth(ym)} 期初`;
    const box = $("#closingConfirmBox");
    if (box) box.checked = false;
    openModal("closingDialog");
  });
  $$("[data-close-closing]").forEach((button) => button.addEventListener("click", () => closeModal("closingDialog")));
  $("#closingForm")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const ym = state.periodClose?.close?.year_month;
    if (!ym || !$("#closingConfirmBox")?.checked) {
      showToast("请先勾选确认", "须复核结账结果并知晓锁定后果", "warn");
      return;
    }
    closeModal("closingDialog");
    $("#closingPanel")?.classList.add("is-processing");
    if ($("#closingBusy")) $("#closingBusy").hidden = false;
    const response = await fetch(`/api/admin/period-closes/${ym}/execute`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ confirm: true }),
    }).catch(() => null);
    const result = await response?.json().catch(() => ({}));
    if (!response?.ok) {
      showToast("结账失败", result?.message || "未改动任何数据", "danger");
      await loadPeriodClose();
      return;
    }
    showToast(`${ym} 已完成结账`, `月报 ${result.report_no || ""} 已生成并锁定`, "success");
    await loadPeriodClose();
  });
  $$("[data-close-reopen-req]").forEach((button) => button.addEventListener("click", () => closeModal("reopenRequestDialog")));
  $("#reopenRequestForm")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const ym = state.periodClose?.close?.year_month;
    const reason = $("#reopenReason")?.value.trim() || "";
    if (reason.length < 10) {
      showToast("原因过短", "重开原因不少于 10 个字，将写入审计记录", "warn");
      return;
    }
    const response = await fetch(`/api/admin/period-closes/${ym}/reopen`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        reason,
        companies_note: $("#reopenCompanies")?.value || "",
        window_days: Number($("#reopenDays")?.value || 3),
      }),
    }).catch(() => null);
    const result = await response?.json().catch(() => ({}));
    if (!response?.ok) {
      showToast("申请失败", result?.message || "请稍后重试", "danger");
      return;
    }
    closeModal("reopenRequestDialog");
    showToast("重开申请已提交", result.item?.number || "", "success");
    await loadPeriodClose();
    await loadReopenQueue();
  });
  $$("[data-close-reopen-dec]").forEach((button) => button.addEventListener("click", () => closeModal("reopenDecideDialog")));
  $("#reopenApproveBtn")?.addEventListener("click", () => decideReopen(true));
  $("#reopenRejectBtn")?.addEventListener("click", () => decideReopen(false));
  $("#paApply")?.addEventListener("click", () => loadPeriodAudit());
  $("#paReload")?.addEventListener("click", () => loadPeriodAudit());
  $("#paReset")?.addEventListener("click", () => {
    ["paSince", "paUntil", "paCompany"].forEach((id) => { if ($(`#${id}`)) $(`#${id}`).value = ""; });
    if ($("#paAction")) $("#paAction").value = "";
    loadPeriodAudit();
  });
}

function renderDiffGrid(diff) {
  const root = $("#reopenDiff");
  if (!root) return;
  root.replaceChildren();
  (diff || []).forEach((row) => {
    const wrap = document.createElement("div");
    wrap.className = `diff-row${row.changed ? " changed" : ""}`;
    const label = document.createElement("div");
    label.className = "diff-label";
    label.textContent = row.path || "状态";
    const before = document.createElement("div");
    before.className = "diff-before";
    before.textContent = row.before == null ? "—" : typeof row.before === "object" ? JSON.stringify(row.before) : String(row.before);
    const arrow = document.createElement("div");
    arrow.className = "diff-arrow";
    arrow.textContent = "→";
    const after = document.createElement("div");
    after.className = "diff-after";
    after.textContent = row.after == null ? "—" : typeof row.after === "object" ? JSON.stringify(row.after) : String(row.after);
    wrap.append(label, before, arrow, after);
    root.append(wrap);
  });
}

async function openReopenDecide(id) {
  const response = await fetch(`/api/admin/period-reopens/${id}`).catch(() => null);
  const result = await response?.json().catch(() => ({}));
  if (!response?.ok) {
    showToast("加载失败", result?.message || "请稍后重试", "danger");
    return;
  }
  const item = result.item || {};
  state.reopenDecideId = item.id;
  $("#reopenDecideTitle").textContent = `重开审批 · ${item.year_month}`;
  $("#reopenDecideSub").textContent = `${item.number || ""} · ${item.requester_username || ""} · ${String(item.requested_at || "").slice(0, 16).replace("T", " ")}`;
  const kv = $("#reopenDecideKv");
  if (kv) {
    kv.replaceChildren();
    [["账期", item.year_month], ["原因", item.reason], ["涉及", item.companies_note || "—"], ["窗口", `${item.window_days} 天`], ["原月报", item.report_no || "—"]].forEach(([dt, dd]) => {
      const t = document.createElement("dt"); t.textContent = dt;
      const d = document.createElement("dd"); d.textContent = dd;
      kv.append(t, d);
    });
  }
  renderDiffGrid(item.diff);
  $("#reopenComment").value = "";
  $("#reopenApproveBtn").textContent = `同意重开 ${item.year_month}`;
  openModal("reopenDecideDialog");
}

async function decideReopen(approve) {
  const id = state.reopenDecideId;
  const comment = $("#reopenComment")?.value.trim() || "";
  if (!approve && comment.length < 2) {
    showToast("请填写驳回意见", "驳回必须填写审批意见", "warn");
    return;
  }
  const response = await fetch(`/api/admin/period-reopens/${id}/decide`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ approve, comment }),
  }).catch(() => null);
  const result = await response?.json().catch(() => ({}));
  if (!response?.ok) {
    showToast("审批失败", result?.message || "请稍后重试", "danger");
    return;
  }
  closeModal("reopenDecideDialog");
  showToast(approve ? "已同意重开" : "已驳回申请", result.item?.number || "", approve ? "warn" : "danger");
  await loadPeriodClose();
  await loadReopenQueue();
}

function reopenStatusPill(status) {
  if (status === "approved") return { cls: "pill-warn", label: "已通过" };
  if (status === "rejected") return { cls: "pill-danger", label: "已驳回" };
  return { cls: "pill-info", label: "待审批" };
}

async function loadReopenQueue() {
  const list = $("#reopenQueueList");
  if (!list) return;
  const response = await fetch("/api/admin/period-reopens").catch(() => null);
  const result = await response?.json().catch(() => ({}));
  const items = result.items || [];
  const pending = items.filter((item) => item.status === "pending").length;
  if ($("#reopenTabCount")) $("#reopenTabCount").textContent = String(pending);
  list.replaceChildren();
  items.forEach((item) => {
    const row = document.createElement("div");
    row.className = "list-row";
    const pill = document.createElement("span");
    const meta = reopenStatusPill(item.status);
    pill.className = `pill ${meta.cls}`;
    pill.textContent = meta.label;
    const main = document.createElement("div");
    main.className = "lr-main";
    const title = document.createElement("div");
    title.className = "lr-title";
    title.textContent = `申请重开 ${item.year_month}`;
    const sub = document.createElement("div");
    sub.className = "lr-sub";
    sub.textContent = item.reason || "";
    const metaLine = document.createElement("div");
    metaLine.className = "meta";
    metaLine.textContent = `${item.number} · ${item.requester_username || ""} · ${String(item.requested_at || "").slice(0, 16).replace("T", " ")}`;
    main.append(title, sub, metaLine);
    const side = document.createElement("div");
    side.className = "lr-side";
    if (item.status === "pending") {
      const btn = document.createElement("button");
      btn.type = "button";
      btn.className = "btn btn-sm btn-primary";
      btn.textContent = "审批";
      btn.addEventListener("click", () => openReopenDecide(item.id));
      side.append(btn);
    } else {
      const num = document.createElement("span");
      num.className = "meta";
      num.textContent = item.number;
      side.append(num);
    }
    row.append(pill, main, side);
    list.append(row);
  });
  const empty = $("#reopenQueueEmpty");
  if (empty) empty.hidden = items.length > 0;
}

function auditActionPill(action) {
  if (action === "close_execute") return { cls: "pill-success", label: "执行月结" };
  if (action === "reopen_request") return { cls: "pill-info", label: "重开申请" };
  if (action === "reopen_approve") return { cls: "pill-warn", label: "重开审批通过" };
  if (action === "close_fail") return { cls: "pill-danger", label: "月结失败" };
  if (action === "reopen_reject") return { cls: "pill-danger", label: "重开驳回" };
  return { cls: "pill-muted", label: action || "—" };
}

async function loadPeriodAudit() {
  const tbody = $("#periodAuditRows");
  if (!tbody) return;
  showTableLoading(tbody, 6);
  $("#paError").style.display = "none";
  const params = new URLSearchParams();
  if ($("#paSince")?.value) params.set("since", $("#paSince").value);
  if ($("#paUntil")?.value) params.set("until", $("#paUntil").value);
  if ($("#paAction")?.value) params.set("action", $("#paAction").value);
  if ($("#paCompany")?.value) params.set("company", $("#paCompany").value);
  const response = await fetch(`/api/admin/period-audit?${params.toString()}`).catch(() => null);
  const result = await response?.json().catch(() => ({}));
  if (!response?.ok) {
    showTableError(tbody, 6);
    $("#paError").style.display = "";
    return;
  }
  const items = result.items || [];
  tbody.replaceChildren();
  const cards = $("#periodAuditCards");
  if (cards) cards.replaceChildren();
  items.forEach((item) => {
    const tr = document.createElement("tr");
    const time = document.createElement("td"); time.className = "num"; time.textContent = String(item.created_at || "").slice(0, 19).replace("T", " ");
    const actor = document.createElement("td");
    const name = document.createElement("div"); name.textContent = item.actor_username || "—";
    const role = document.createElement("div"); role.className = "cell-sub"; role.textContent = item.actor_role || "";
    actor.append(name, role);
    const action = document.createElement("td");
    const pill = document.createElement("span");
    const meta = auditActionPill(item.action);
    pill.className = `pill ${meta.cls}`;
    pill.textContent = meta.label;
    action.append(pill);
    const obj = document.createElement("td"); obj.className = "wrap"; obj.textContent = `${item.year_month || ""} ${item.reason || item.object_label || ""}`.trim();
    const change = document.createElement("td"); change.className = "num";
    const before = item.before?.snapshot_hash || item.before?.status || "";
    const after = item.after?.snapshot_hash || item.after?.status || "";
    change.textContent = before || after ? `${String(before).slice(0, 8)} → ${String(after).slice(0, 8)}` : "—";
    const proof = document.createElement("td"); proof.textContent = item.report_no || "—";
    tr.append(time, actor, action, obj, change, proof);
    tbody.append(tr);
    if (cards) {
      const card = document.createElement("div");
      card.className = "card";
      card.style.padding = "12px 14px";
      [["时间", time.textContent], ["操作人", item.actor_username], ["动作", meta.label], ["对象", obj.textContent], ["变化", change.textContent], ["凭证", item.report_no || "—"]].forEach(([k, v]) => {
        const line = document.createElement("div");
        line.className = "row-between";
        const l = document.createElement("span"); l.className = "meta"; l.textContent = k;
        const r = document.createElement("span"); r.textContent = v || "—";
        line.append(l, r);
        card.append(line);
      });
      cards.append(card);
    }
  });
  $("#periodAuditFoot").textContent = `共 ${items.length} 条`;
  const empty = $("#periodAuditEmpty");
  if (empty) empty.hidden = items.length > 0;
}


function initAdmin() {
  renderStoredAdminReviews();
  updateAuditCounts();
  loadAdminCompanies();
  loadCalculationSettings();
  loadOpeningBalances();
  loadCalculationChanges();
  loadAdminCoverageGaps();
  initDashboard();
  loadPeriodClose();
  loadReopenQueue();

  let activeAuditType = "all";
  function filterAuditRows() {
    const company = $("#auditCompany")?.value || "全部公司";
    $$(".audit-table tbody tr").forEach((row) => {
      const typeMatches = activeAuditType === "all" || row.dataset.auditType === activeAuditType;
      const companyMatches = company === "全部公司" || row.dataset.company === company;
      row.hidden = !(typeMatches && companyMatches);
    });
  }
  $$("[data-audit-filter]").forEach((button) => button.addEventListener("click", () => {
    activeAuditType = button.dataset.auditFilter;
    $$("[data-audit-filter]").forEach((item) => {
      const active = item === button;
      item.classList.toggle("active", active);
      item.setAttribute("aria-pressed", String(active));
    });
    const reopen = activeAuditType === "reopen";
    if ($("#auditTableWrap")) $("#auditTableWrap").hidden = reopen;
    if ($("#reopenQueue")) $("#reopenQueue").hidden = !reopen;
    if (reopen) loadReopenQueue();
    else filterAuditRows();
  }));
  $("#auditCompany")?.addEventListener("change", filterAuditRows);

  document.querySelectorAll("[data-close]").forEach((el) => el.addEventListener("click", () => closeModal(el.dataset.close)));
  document.querySelectorAll(".modal-backdrop").forEach((bd) => bd.addEventListener("click", (e) => { if (e.target === bd) bd.classList.remove("open"); }));
  document.addEventListener("keydown", (e) => { if (e.key === "Escape") document.querySelectorAll(".modal-backdrop.open").forEach((m) => m.classList.remove("open")); });

  async function submitAuditResult(row) {
    const decision = state.auditDecision;
    const reason = state.auditReason || "";
    const approved = decision.includes("通过") || decision.includes("确认并纳入") || decision.includes("启用") || decision.includes("关闭异常");
    const returned = decision.includes("退回");
    const kind = row.dataset.recordKind;
    let storedStatus;
    let reviewedAccount = null;

    if (kind === "account" && row.dataset.accountId) {
      const apiDecision = approved ? "approve" : returned ? "return" : "disable";
      const response = await fetch(`/api/admin/accounts/${row.dataset.accountId}/review`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ decision: apiDecision, reason }),
      }).catch(() => null);
      if (response?.status === 401) {
        window.location.href = "index.html";
        return;
      }
      const result = await response?.json().catch(() => ({}));
      if (!response || !response.ok) {
        if (!toastIfLocked(result)) showToast("审核结果提交失败", result?.message || "请稍后重试", "danger");
        return;
      }
      reviewedAccount = result.account;
      storedStatus = accountStatusLabel(result.account?.status);
    } else if (kind === "manual" && row.dataset.recordId) {
      const action = approved ? "approve_new" : "return";
      const response = await fetch(`/api/admin/manual-records/${row.dataset.recordId}/decisions`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action,
          reason: reason || decision,
          expected_decision_id: row.dataset.decisionId ? Number(row.dataset.decisionId) : null,
          request_key: `audit-manual-${row.dataset.recordId}-${Date.now()}`,
          subject_code: row.dataset.requestedSubject || undefined,
        }),
      }).catch(() => null);
      if (response?.status === 401) {
        window.location.href = "index.html";
        return;
      }
      const result = await response?.json().catch(() => ({}));
      if (!response || !response.ok) {
        if (!toastIfLocked(result)) showToast("手工单审核失败", result?.message || "请稍后重试", "danger");
        return;
      }
      storedStatus = approved ? "已确认" : "已退回";
    } else if (kind === "match" && row.dataset.eventId) {
      // 既有 decision：撤销当前匹配决定，使异常退出待办队列（与 reverse 语义一致）。
      const response = await fetch(`/api/admin/transfer-events/${row.dataset.eventId}/decisions`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action: "reverse",
          reason: reason || decision || "审核中心关闭匹配异常",
          expected_revision: row.dataset.revision ? Number(row.dataset.revision) : null,
          request_key: `audit-match-${row.dataset.eventId}-${Date.now()}`,
        }),
      }).catch(() => null);
      if (response?.status === 401) {
        window.location.href = "index.html";
        return;
      }
      const result = await response?.json().catch(() => ({}));
      if (!response || !response.ok) {
        showToast("匹配异常处置失败", result?.message || "请稍后重试", "danger");
        return;
      }
      storedStatus = approved ? "已确认" : "已退回";
    } else {
      showToast("审核结果提交失败", "未知待办类型", "danger");
      return;
    }

    // 处置后从列表移除并重拉三队列，保证页脚/过滤/标题与角标同步减一。
    row.remove();
    updateAuditCounts();
    await refreshAuditCountsFromApi();
    const unresolved = $$("#auditRows tr").filter((r) => r.dataset.resolved !== "true");
    const high = unresolved.filter((r) => r.querySelector(".pill-danger")).length;
    const medium = unresolved.filter((r) => r.querySelector(".pill-warn")).length;
    const low = Math.max(0, unresolved.length - high - medium);
    applyAuditCounts({ total: unresolved.length, high, medium, low }, { fromApi: true });
    updatePendingAccountNotice();
    filterAuditRows();
    showToast(
      "审核结果已记录",
      approved ? "已通过的数据取得相应使用或核算资格" : "当前记录不参与自动核算",
      "success",
    );
  }

  $("#auditRows")?.addEventListener("click", async (event) => {
    const button = event.target.closest("[data-audit-action]");
    if (!button) return;
    if (button.dataset.auditAction === "approve-attestation" || button.dataset.auditAction === "reject-attestation") {
      const row = button.closest("tr");
      const attestationId = row?.dataset.attestationId;
      if (!attestationId) return;
      const approve = button.dataset.auditAction === "approve-attestation";
      const reason = await askReason({
        title: approve ? "通过无业务说明" : "驳回无业务说明",
        subtitle: "审核结论将写入留痕；通过后仅关闭断档，不生成银行行。",
        confirmLabel: approve ? "通过" : "驳回",
      });
      if (!reason) return;
      const response = await fetch(`/api/admin/no-business-attestations/${attestationId}/review`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ decision: approve ? "approve" : "reject", review_reason: reason }),
      }).catch(() => null);
      const result = await response?.json().catch(() => ({}));
      if (!response?.ok) {
        showToast("审核失败", result?.message || "请稍后重试", "danger");
        return;
      }
      await loadAdminCoverageGaps();
      showToast(approve ? "已通过说明" : "已驳回说明", "断档状态已更新", "success");
      return;
    }
    const row = button.closest("tr");
    state.auditRow = row;
    const cells = $$("td", row);
    const company = cells[1]?.querySelector(".cell-main")?.textContent.trim() || "";
    const period = cells[3]?.textContent.trim() || "";
    const type = cells[2]?.textContent.trim() || "";
    const basis = row.dataset.evidence || "银行原始行、导入批次、账户覆盖区间与公司说明";
    const action = button.dataset.auditAction;
    if (action === "approve") {
      $("#approve-company").textContent = company;
      $("#approve-period").textContent = period;
      $("#approve-type").textContent = type;
      $("#approve-basis").textContent = basis;
      openModal("modal-approve");
    } else if (action === "reject") {
      $("#reject-reason").value = "";
      $("#reject-hint").style.display = "none";
      openModal("modal-reject");
    } else if (action === "disable") {
      state.auditDecision = "停用并驳回";
      state.auditReason = "停用并驳回该账户";
      submitAuditResult(row);
    }
  });

  $("#approve-confirm")?.addEventListener("click", () => {
    if (!state.auditRow) return;
    closeModal("modal-approve");
    const kind = state.auditRow.dataset.recordKind;
    state.auditDecision = kind === "account"
      ? "复核通过并启用账户"
      : kind === "match"
        ? "关闭异常"
        : "确认并纳入计算";
    state.auditReason = state.auditDecision;
    submitAuditResult(state.auditRow);
  });

  $("#reject-confirm")?.addEventListener("click", () => {
    if (!state.auditRow) return;
    const reason = $("#reject-reason").value.trim();
    if (reason.length < 5) { $("#reject-hint").style.display = ""; return; }
    closeModal("modal-reject");
    const kind = state.auditRow.dataset.recordKind;
    state.auditDecision = kind === "account"
      ? "退回公司修改"
      : kind === "match"
        ? "退回重匹配"
        : "退回公司补充材料";
    state.auditReason = reason;
    submitAuditResult(state.auditRow);
  });

  $("#openCompanyDialog")?.addEventListener("click", () => openModal("companyDialog"));
  $("#companyForm")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const form = event.currentTarget;
    const data = new FormData(form);
    const companyName = String(data.get("companyName") || "").trim();
    const loginName = String(data.get("loginName") || "").trim();
    const createUser = data.get("createUser") !== null;

    const payload = {
      name: companyName,
      credit_code: String(data.get("creditCode") || "").trim(),
      cashier_name: String(data.get("cashier") || "").trim(),
    };
    if (createUser && loginName) payload.username = loginName;

    const response = await fetch("/api/admin/companies", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    }).catch(() => null);
    if (response?.status === 401) {
      window.location.href = "index.html";
      return;
    }
    const result = await response?.json().catch(() => ({}));
    if (!response || !response.ok) {
      showToast("公司创建失败", result?.message || "请稍后重试", "danger");
      return;
    }
    const accountCreated = Boolean(result.username);
    closeModal("companyDialog");
    form.reset();
    await loadAdminCompanies();
    showToast(
      accountCreated ? "公司与账号已创建" : "公司已创建",
      accountCreated ? `账号 ${result.username} 的初始密码已生成（仅此一次显示）：${result.initial_password}，首次登录必须修改` : "可稍后在账号管理中创建公司账号",
      "success",
    );
  });

  function openCompanyDetail(companyId) {
    const company = (state.companies || []).find((c) => String(c.id) === String(companyId));
    if (!company) return;
    $("#d-title").textContent = company.name;
    $("#d-sub").textContent = `统一社会信用代码 ${company.credit_code || "待补充"}`;
    $("#d-kv").innerHTML =
      '<dt>公司名称</dt><dd>' + company.name + '</dd>' +
      '<dt>统一社会信用代码</dt><dd>' + (company.credit_code || "待补充") + '</dd>' +
      '<dt>出纳</dt><dd>' + (company.cashier_name || "未指定") + '</dd>' +
      '<dt>银行账户</dt><dd>' + (company.account_count ?? 0) + ' 个</dd>' +
      '<dt>状态</dt><dd>' + companyStatusBadge(company.status).label + '</dd>';
    $("#d-login-account").textContent = company.usernames || "未创建公司账号";
    $("#d-login-meta").textContent = company.usernames ? "公司端登录账号" : "该公司尚未创建登录账号";
    const resetBtn = $("#btn-reset-pwd");
    resetBtn.disabled = !company.usernames;
    resetBtn.textContent = "重置密码";
    resetBtn.dataset.companyId = company.id;
    openModal("modal-detail");
  }

  $("#companyTable tbody")?.addEventListener("click", (event) => {
    const btn = event.target.closest("[data-company-view]");
    if (btn) openCompanyDetail(btn.dataset.companyView);
  });

  $("#btn-reset-pwd")?.addEventListener("click", async () => {
    const companyId = $("#btn-reset-pwd").dataset.companyId;
    const usersResponse = await fetch("/api/admin/users").catch(() => null);
    const usersResult = await usersResponse?.json().catch(() => ({}));
    const user = (usersResult?.users || []).find((u) => String(u.company_id) === String(companyId) && u.role === "company");
    if (!user) { showToast("重置失败", "该公司尚无公司登录账号", "danger"); return; }
    const response = await fetch(`/api/admin/users/${user.id}/reset-password`, { method: "POST" }).catch(() => null);
    if (response?.status === 401) { window.location.href = "index.html"; return; }
    const result = await response?.json().catch(() => ({}));
    if (!response || !response.ok) { showToast("重置失败", result?.message || "请稍后重试", "danger"); return; }
    $("#btn-reset-pwd").disabled = true;
    $("#btn-reset-pwd").textContent = "已重置";
    $("#d-login-meta").textContent = `临时密码已生成（仅此一次）：${result.initial_password}，首次登录必须修改`;
    showToast("密码已重置", "临时密码仅本次显示，首次登录必须修改", "success");
  });

  const remind = $("#cs-remind");
  const track = $("#cs-switch-track");
  const thumb = $("#cs-switch-thumb");
  const daysSel = $("#cs-remind-days");
  function renderSwitch() {
    if (!remind) return;
    track.style.background = remind.checked ? "var(--accent)" : "var(--fg-soft)";
    thumb.style.left = remind.checked ? "18px" : "2px";
    if (daysSel) daysSel.disabled = !remind.checked;
  }
  remind?.addEventListener("change", renderSwitch);
  renderSwitch();

  function applySystemSettings(values) {
    if (!values) return;
    const start = $("#cs-start");
    if (start && values.start_date) start.value = values.start_date;
    const dayInput = $("#cs-day");
    if (dayInput && values.closing_day) dayInput.value = values.closing_day;
    const remindInput = $("#cs-remind");
    if (remindInput && "auto_remind" in values) {
      remindInput.checked = values.auto_remind === "1";
      renderSwitch();
    }
    const daysSelect = $("#cs-remind-days");
    if (daysSelect && values.remind_days) daysSelect.value = values.remind_days;
    const closingDay = values.closing_day || "5";
    const startDate = values.start_date || "2026-01-01";
    const dash = $("#dashboardPeriodSub");
    if (dash) dash.textContent = `每月 ${closingDay} 日结账 · 全局起算日 ${startDate}`;
    const timeline = $("#timelineSub");
    if (timeline) timeline.textContent = `全局起算日 ${startDate} 起，每月 ${closingDay} 日结账`;
    const flows = $("#flowsRangeMeta");
    if (flows) flows.textContent = `数据范围：${startDate} 起算 · 每月 ${closingDay} 日结账`;
    const opening = $("#openingSub");
    if (opening) opening.textContent = `${startDate} 起算的公司间往来期初数`;
  }

  async function loadSystemSettings() {
    const response = await fetch("/api/admin/settings").catch(() => null);
    if (response?.status === 401) {
      window.location.href = "index.html";
      return;
    }
    const result = await response?.json().catch(() => ({}));
    if (response?.ok && result.settings) applySystemSettings(result.settings);
  }

  $("#systemSettings")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const tip = $("#cs-save-tip");
    const day = parseInt($("#cs-day")?.value, 10);
    if (!day || day < 1 || day > 28) {
      if (tip) { tip.style.display = ""; tip.style.color = "var(--danger)"; tip.textContent = "结账日须为 1-28 之间的整数"; }
      return;
    }
    const startInput = $("#cs-start");
    const nextStart = startInput?.value || "";
    const prevStart = startInput?.dataset.current || "";
    const locked = startInput?.dataset.locked === "1";
    if (nextStart !== prevStart) {
      if (locked) {
        showToast("起算日已锁定", "已有结账月份，起算日不可修改", "warn");
        return;
      }
      const reason = await askReason({
        title: "修改起算日",
        subtitle: "修改起算日必须填写原因，并写入变更留痕。",
        confirmLabel: "确认修改",
      });
      if (!reason) return;
      const calcResp = await fetch("/api/admin/settings/calculation-start", {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ calculation_start_date: nextStart, reason }),
      }).catch(() => null);
      if (calcResp?.status === 401) { window.location.href = "index.html"; return; }
      const calcResult = await calcResp?.json().catch(() => ({}));
      if (!calcResp?.ok) {
        showToast("起算日保存失败", calcResult?.message || "请稍后重试", "danger");
        return;
      }
      await loadCalculationSettings();
      await loadCalculationChanges();
    }
    const payload = {
      closing_day: String(day),
      auto_remind: $("#cs-remind")?.checked ? "1" : "0",
      remind_days: $("#cs-remind-days")?.value || "3",
    };
    // Keep display start_date in settings store in sync when calculation start exists.
    if (nextStart) payload.start_date = nextStart;
    const response = await fetch("/api/admin/settings", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    }).catch(() => null);
    if (response?.status === 401) {
      window.location.href = "index.html";
      return;
    }
    const result = await response?.json().catch(() => ({}));
    if (!response || !response.ok) {
      const message = result?.message || "保存失败，请稍后重试";
      if (tip) { tip.style.display = ""; tip.style.color = "var(--danger)"; tip.textContent = message; }
      showToast("设置保存失败", message, "danger");
      return;
    }
    if (tip) { tip.style.display = ""; tip.style.color = "var(--success)"; tip.textContent = "已保存 · 立即生效"; }
    applySystemSettings(result.settings || {});
    await loadCalculationSettings();
    const gapDays = parseInt($("#cs-remind-days")?.value, 10) || 5;
    const reminderResp = await fetch("/api/admin/reminder-settings", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ settings: { monthly_start_day: String(day || 5), gap_days: String(gapDays) } }),
    }).catch(() => null);
    if (reminderResp?.status === 401) { window.location.href = "index.html"; return; }
    if (!reminderResp?.ok) {
      showToast("提醒扫描参数保存失败", "", "danger");
      return;
    }
    showToast("系统计算口径已保存", "结账日与起算日变更已留痕 · 提醒扫描参数已同步更新", "success");
  });

  initPeriodClose();
  loadPeriodClose();

  $("#openOpeningDialog")?.addEventListener("click", () => openModal("openingDialog"));
  $$("[data-close-opening]").forEach((button) => button.addEventListener("click", () => closeModal("openingDialog")));
  $("#openingForm")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const form = event.currentTarget;
    const data = new FormData(form);
    const fromName = data.get("from");
    const toName = data.get("to");
    if (fromName === toName) {
      showToast("本方与对方不能相同", "同公司账户余额不属于公司间期初", "warn");
      return;
    }
    const fromCompany = (state.companies || []).find((c) => c.name === fromName);
    const toCompany = (state.companies || []).find((c) => c.name === toName);
    if (!fromCompany || !toCompany) {
      showToast("公司无效", "请刷新页面后重试", "warn");
      return;
    }
    const amount = Number(data.get("amount"));
    if (Number.isNaN(amount)) {
      showToast("金额无效", "请输入有效数字", "warn");
      return;
    }
    const response = await fetch("/api/admin/opening-balances", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        from_company_id: fromCompany.id,
        to_company_id: toCompany.id,
        amount: String(amount),
        reason: String(data.get("reason") || ""),
      }),
    }).catch(() => null);
    const result = await response?.json().catch(() => ({}));
    if (!response?.ok) {
      showToast("提交失败", result?.message || "请稍后重试", "danger");
      return;
    }
    closeModal("openingDialog");
    form.reset();
    await loadOpeningBalances();
    await loadCalculationChanges();
    await loadCalculationSettings();
    showToast("期初余额已提交", "待确认后才会计入公司端余额", "success");
  });

  $("#openingRows")?.addEventListener("click", async (event) => {
    const confirmBtn = event.target.closest("[data-confirm-opening]");
    const voidBtn = event.target.closest("[data-void-opening]");
    const id = confirmBtn?.dataset.confirmOpening || voidBtn?.dataset.voidOpening;
    if (!id) return;
    const reason = await askReason({
      title: confirmBtn ? "确认期初余额" : "作废期初余额",
      subtitle: "该操作必须填写原因，并写入变更留痕。",
      confirmLabel: confirmBtn ? "确认" : "作废",
    });
    if (!reason) return;
    const path = confirmBtn
      ? `/api/admin/opening-balances/${id}/confirm`
      : `/api/admin/opening-balances/${id}/void`;
    const response = await fetch(path, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ reason }),
    }).catch(() => null);
    const result = await response?.json().catch(() => ({}));
    if (!response?.ok) {
      showToast("操作失败", result?.message || "请稍后重试", "danger");
      return;
    }
    await loadOpeningBalances();
    await loadCalculationChanges();
    showToast(confirmBtn ? "期初已确认" : "期初已作废", "变更已留痕", "success");
  });

  initAdminReminders();

  loadSystemSettings();
}

async function loadFlows() {
  const tbody = $("#flowTable tbody");
  if (!tbody) return;
  const params = new URLSearchParams();
  const companyName = $("#flowCompany")?.value;
  if (companyName && companyName !== "全部公司") {
    const company = companyByName(companyName);
    if (company) params.set("company_id", String(company.id));
  }
  const bank = $("#flowBank")?.value;
  if (bank && bank !== "全部银行") params.set("bank", bank);
  const account = $("#flowAccount")?.value;
  if (account && account !== "全部账户") params.set("account", account);
  if ($("#flowStart")?.value) params.set("start", $("#flowStart").value);
  if ($("#flowEnd")?.value) params.set("end", $("#flowEnd").value);
  if ($("#flowKeyword")?.value.trim()) params.set("keyword", $("#flowKeyword").value.trim());
  params.set("limit", "200");
  const response = await fetch(`/api/flows?${params.toString()}`).catch(() => null);
  if (response?.status === 401) { window.location.href = "index.html"; return; }
  const result = await response?.json().catch(() => null);
  if (!response?.ok || result?.status !== "ok") {
    showTableError(tbody, portal === "admin" ? 9 : 8);
    showToast("流水加载失败", result?.message || "请稍后重试", "danger");
    return;
  }
  state.flowRows = result.items || [];
  state.flowTotal = result.total;
  renderFlowRows();
}

function renderFlowRows() {
  const tbody = $("#flowTable tbody");
  if (!tbody) return;
  const data = state.flowRows || [];
  tbody.replaceChildren();
  data.forEach((row, index) => {
    const tr = document.createElement("tr");
    tr.className = "clickable";
    tr.dataset.flowIdx = String(index);
    tr.dataset.company = row.company || "";
    tr.dataset.bank = row.bank || "";
    tr.dataset.account = row.account || "";
    tr.dataset.date = row.date || "";
    tr.dataset.dir = row.direction || "";
    const date = document.createElement("td"); date.className = "num"; date.textContent = row.date || "—";
    tr.append(date);
    if (portal === "admin") {
      const company = document.createElement("td");
      company.className = "cell-main";
      company.textContent = row.company || "—";
      tr.append(company);
    }
    const acct = document.createElement("td"); acct.textContent = row.account_label || row.account || "—";
    const dir = document.createElement("td");
    const dirPill = document.createElement("span");
    dirPill.className = `pill ${row.direction === "收" ? "pill-success" : "pill-danger"}`;
    dirPill.textContent = row.direction || "—";
    dir.append(dirPill);
    const peer = document.createElement("td"); peer.className = "wrap"; peer.textContent = row.peer || "—";
    const summary = document.createElement("td"); summary.className = "wrap"; summary.textContent = row.summary || "—";
    const serial = document.createElement("td"); serial.className = "num"; serial.textContent = row.serial || "—";
    const status = document.createElement("td");
    const statusPill = document.createElement("span");
    const kind = row.status_kind === "success" ? "pill-success" : row.status_kind === "warn" ? "pill-warn" : row.status_kind === "danger" ? "pill-danger" : "pill-muted";
    statusPill.className = `pill ${kind}`;
    statusPill.textContent = row.status || "未归集";
    status.append(statusPill);
    const amt = document.createElement("td");
    amt.className = `num-col ${row.direction === "收" ? "amt-in" : "amt-out"}`;
    amt.textContent = `¥ ${formatCurrency(row.amount)}`;
    tr.append(acct, dir, peer, summary, serial, status, amt);
    tbody.append(tr);
  });
  const count = $("#flowCount");
  if (count) count.textContent = `共 ${resultTotalLabel(data.length, state.flowTotal)}`;
  const empty = $("#flowEmpty");
  if (empty) empty.hidden = data.length > 0;
  const sum = $("#flowSum");
  if (sum) {
    sum.replaceChildren();
    if (!data.length) return;
    const inflow = document.createElement("span");
    inflow.className = "amt-in";
    inflow.textContent = `+¥ ${formatCurrency(data.filter((r) => r.direction === "收").reduce((n, r) => n + Number(r.amount || 0), 0))}`;
    const outflow = document.createElement("span");
    outflow.className = "amt-out";
    outflow.textContent = `-¥ ${formatCurrency(data.filter((r) => r.direction !== "收").reduce((n, r) => n + Number(r.amount || 0), 0))}`;
    sum.append(document.createTextNode("收 "), inflow, document.createTextNode(" · 付 "), outflow);
  }
}

function resultTotalLabel(shown, total) {
  if (total != null && total !== shown) return `${total} 笔 · 本页 ${shown}`;
  return `${shown} 笔`;
}

function openFlowDetail(index) {
  const modal = $("#tx-modal");
  if (!modal) return;
  const row = (state.flowRows || [])[index];
  if (!row) return;
  $("#tx-modal-sub").textContent = `${row.company || ""} · ${row.account_label || ""} · ${row.date || ""}`;
  $("#d-serial").textContent = row.serial || "—";
  $("#d-bank").textContent = row.bank || "—";
  $("#d-account").textContent = row.own_name ? `${row.own_name} · ${row.account || ""}` : (row.account || "—");
  $("#d-time").textContent = row.time || row.date || "—";
  $("#d-peer").textContent = row.peer || "—";
  $("#d-peer-acct").textContent = row.peer_account || "—";
  $("#d-amount").textContent = `¥ ${formatCurrency(row.amount)}（${row.direction || ""}）`;
  $("#d-status").textContent = row.status || "—";
  $("#d-pair").textContent = "—";
  $("#d-subject").textContent = "—";
  $("#d-batch").textContent = row.batch || "—";
  $("#d-note").textContent = row.locator ? `源行 ${row.locator}` : "—";
  modal.classList.add("open");
}

function exportFlows() {
  const params = new URLSearchParams();
  const companyName = $("#flowCompany")?.value;
  if (companyName && companyName !== "全部公司") {
    const company = companyByName(companyName);
    if (company) params.set("company_id", String(company.id));
  }
  window.location.href = `/api/export.csv${params.toString() ? `?${params}` : ""}`;
  showToast("开始导出", "仅含出纳已确认工作表的银行原始流水", "success");
}

function initFlowTools() {
  $("#applyFlowFilters")?.addEventListener("click", loadFlows);
  $("#exportFlows")?.addEventListener("click", exportFlows);
  $("#resetFlowFilters")?.addEventListener("click", () => {
    const company = $("#flowCompany");
    if (company) company.value = "全部公司";
    if ($("#flowBank")) $("#flowBank").value = "全部银行";
    if ($("#flowAccount")) $("#flowAccount").value = "全部账户";
    if ($("#flowStart")) $("#flowStart").value = "";
    if ($("#flowEnd")) $("#flowEnd").value = "";
    if ($("#flowKeyword")) $("#flowKeyword").value = "";
    loadFlows();
  });
  $("#flowTable tbody")?.addEventListener("click", (event) => {
    const row = event.target.closest("tr[data-flow-idx]");
    if (row) openFlowDetail(Number(row.dataset.flowIdx));
  });
  $("#tx-modal-close")?.addEventListener("click", () => $("#tx-modal")?.classList.remove("open"));
  $("#tx-modal-ok")?.addEventListener("click", () => $("#tx-modal")?.classList.remove("open"));
  $("#tx-modal")?.addEventListener("click", (event) => { if (event.target === event.currentTarget) event.currentTarget.classList.remove("open"); });
}

function manualApiStatus(stateName) {
  if (stateName === "approved") return { cls: "pill-success", label: "已通过" };
  if (stateName === "returned" || stateName === "reversed") return { cls: "pill-danger", label: stateName === "returned" ? "已退回" : "已冲销" };
  if (stateName === "exception") return { cls: "pill-danger", label: "异常" };
  return { cls: "pill-info", label: "待审核" };
}

function renderCompanyManualRecords(records) {
  const tbody = $("#manualRecordRows");
  if (!tbody) return;
  tbody.replaceChildren();
  const rows = records || state.manualRecords || [];
  rows.forEach((record) => {
    const tr = document.createElement("tr");
    tr.dataset.recordId = String(record.id);
    const date = document.createElement("td"); date.className = "num"; date.textContent = String(record.occurred_at || "").slice(0, 10);
    const direction = document.createElement("td"); direction.textContent = record.direction === "incoming" ? "收款" : "付款";
    const counterparty = document.createElement("td");
    const name = document.createElement("span"); name.className = "cell-main"; name.textContent = record.counterparty_company_name || "—";
    counterparty.append(name);
    const subject = document.createElement("td");
    const tag = document.createElement("span"); tag.className = "tag"; tag.textContent = SUBJECT_CODE_LABEL[record.requested_subject] || record.requested_subject || "—";
    subject.append(tag);
    const isIn = record.direction === "incoming";
    const amount = document.createElement("td");
    amount.className = `num-col ${isIn ? "amt-in" : "amt-out"}`;
    amount.textContent = `${isIn ? "+" : "-"}¥ ${formatCurrency(record.amount)}`;
    const summary = document.createElement("td"); summary.className = "wrap"; summary.textContent = record.summary || "—";
    const statusMeta = manualApiStatus(record.state);
    const statusCell = document.createElement("td");
    const pill = document.createElement("span"); pill.className = `pill ${statusMeta.cls}`; pill.textContent = statusMeta.label;
    statusCell.append(pill);
    const action = document.createElement("td");
    action.innerHTML = '<span class="muted">—</span>';
    tr.append(date, direction, counterparty, subject, amount, summary, statusCell, action);
    tbody.append(tr);
  });
  const pending = rows.filter((record) => record.state === "pending").length;
  const pendingEl = $("#manualPendingStatus");
  if (pendingEl) pendingEl.textContent = pending;
  const foot = $("#manualFoot");
  if (foot) foot.textContent = `共 ${rows.length} 条 · 待复核 ${pending} 条`;
  const empty = $("#manualEmpty");
  if (empty) empty.hidden = rows.length > 0;
}

async function loadCompanyManualRecords() {
  if (!$("#manualRecordRows")) return;
  const response = await fetch("/api/company/manual-records").catch(() => null);
  if (response?.status === 401 || response?.status === 403) { window.location.href = "index.html"; return; }
  const result = await response?.json().catch(() => ({}));
  if (!response?.ok) {
    showToast("手工记录加载失败", result?.message || "请稍后重试", "danger");
    return;
  }
  state.manualRecords = result.records || [];
  renderCompanyManualRecords(state.manualRecords);
}

async function loadCompanyPeerSelect() {
  const select = $("#manualCounterparty");
  if (!select) return;
  const response = await fetch("/api/company/companies").catch(() => null);
  const result = await response?.json().catch(() => ({}));
  const companies = (result.companies || []).filter((item) => String(item.id) !== String(state.me?.company_id));
  const first = select.options[0];
  select.replaceChildren(first);
  companies.forEach((company) => {
    const option = document.createElement("option");
    option.value = String(company.id);
    option.textContent = company.name;
    select.append(option);
  });
}


function resetUpload() {
  state.selectedFile = null;
  state.parseResult = null;
  $("#uploadForm")?.reset();
  if ($("#filePreview")) $("#filePreview").hidden = true;
  if ($("#parseResult")) $("#parseResult").hidden = true;
  if ($("#sheetReview")) $("#sheetReview").hidden = true;
  if ($("#sheetList")) $("#sheetList").replaceChildren();
  if ($("#dropzone")) $("#dropzone").hidden = false;
  if ($("#parseButton")) {
    $("#parseButton").disabled = true;
    $("#parseButton span").textContent = "开始解析";
    delete $("#parseButton").dataset.stage;
  }
}

function updateParseButton() {
  const button = $("#parseButton");
  if (button) button.disabled = !(state.selectedFile && $("#accountSelect").value);
}

function acceptFile(file) {
  if (!file) return;
  const extension = file.name.split(".").pop().toLowerCase();
  if (!["xls", "xlsx"].includes(extension)) {
    showToast("文件格式不支持", "请选择银行导出的 .xls 或 .xlsx 文件", "danger");
    return;
  }
  state.selectedFile = file;
  state.parseResult = null;
  $("#fileName").textContent = file.name;
  $("#fileMeta").textContent = `${(file.size / 1024).toFixed(1)} KB · 等待表头识别`;
  $("#filePreview").hidden = false;
  $("#dropzone").hidden = true;
  $("#parseResult").hidden = true;
  delete $("#parseButton").dataset.stage;
  updateParseButton();
}

async function parseFile() {
  const formData = new FormData();
  formData.append("file", state.selectedFile);
  const selectedAccount = $("#accountSelect")?.selectedOptions?.[0];
  if (selectedAccount?.dataset.accountId) {
    formData.append("bank_account_id", selectedAccount.dataset.accountId);
  }
  let result;
  let parsed = false;
  try {
    const response = await fetch("/api/parse", { method: "POST", body: formData });
    if (response.status === 401 || response.status === 403) {
      window.location.href = "index.html";
      return;
    }
    result = await response.json();
    parsed = response.ok && ["parsed", "duplicate"].includes(result.status);
  } catch {
    result = { status: "error", message: "解析服务暂时不可用，请稍后重试。" };
  }
  state.parseResult = result;
  renderParseResult(result, parsed);
}

function renderParseResult(result, parsed) {
  const panel = $("#parseResult");
  if (!panel) return;
  const sheets = Array.isArray(result.sheets) ? result.sheets : [];
  const duplicated = result.status === "duplicate";
  const opaque = duplicated && !sheets.length;
  panel.classList.toggle("info", parsed);
  panel.classList.toggle("warn", !parsed);
  const parseIcon = $("use", panel);
  if (parseIcon) parseIcon.setAttribute("href", parsed ? "icons.svg#circle-check" : "icons.svg#circle-alert");
  $("#parseTitle").textContent = duplicated ? "文件已导入过" : parsed ? "文件解析完成" : "未识别到银行模板";
  const pendingCount = sheets.filter((s) => s.outcome === "parsed" && s.review_status === "pending").length;
  const exceptionCount = sheets.filter((s) => s.outcome === "exception").length;
  const ignoredCount = sheets.filter((s) => s.outcome === "ignored" || s.review_status === "ignored").length;
  let summary;
  if (opaque) {
    summary = "相同内容的文件已由其他公司导入，仅记录重复状态，不重复入账。";
  } else if (sheets.length) {
    summary = `${sheets.length} 个工作表：${pendingCount} 个待确认${exceptionCount ? `、${exceptionCount} 个异常` : ""}${ignoredCount ? `、${ignoredCount} 个忽略` : ""}。解析成功不等于业务确认。`;
  } else {
    summary = `${result.message || ""} 系统不会猜测模板或自动入账。`;
  }
  $("#parseSummary").textContent = summary;
  panel.hidden = false;
  renderSheetList(sheets, result.batch_id);

  const button = $("#parseButton");
  button.dataset.stage = "confirm";
  button.disabled = false;
  if (!parsed) {
    button.querySelector("span").textContent = "关闭";
  } else if (opaque) {
    button.querySelector("span").textContent = "完成";
  } else if (sheets.length) {
    button.querySelector("span").textContent = pendingCount ? `确认全部（${pendingCount} 个待确认）` : "完成";
  } else {
    button.querySelector("span").textContent = "完成";
  }
}

function sheetStatusMeta(sheet) {
  if (sheet.review_status === "confirmed") return { className: "success", label: "已确认" };
  if (sheet.review_status === "ignored") return { className: "neutral", label: "已忽略" };
  if (sheet.outcome === "exception") return { className: "danger", label: "异常待处理" };
  if (sheet.outcome === "ignored") return { className: "neutral", label: "空表忽略" };
  return { className: "warning", label: "待确认" };
}

function renderSheetList(sheets, batchId) {
  const wrap = $("#sheetReview");
  const list = $("#sheetList");
  if (!wrap || !list || !sheets.length) {
    if (wrap) wrap.hidden = true;
    return;
  }
  wrap.hidden = false;
  list.replaceChildren(...sheets.map((sheet) => buildSheetItem(sheet, batchId)));
}

function buildSheetItem(sheet, batchId) {
  const item = document.createElement("div");
  item.className = "list-row";
  const meta = sheetStatusMeta(sheet);
  const main = document.createElement("div");
  main.className = "lr-main";
  const title = document.createElement("div");
  title.className = "lr-title";
  title.textContent = sheet.sheet_name;
  main.append(title);
  const details = document.createElement("div");
  details.className = "lr-sub";
  if (sheet.outcome === "parsed" && sheet.bank) {
    const period = sheet.period_start ? ` · ${sheet.period_start}—${sheet.period_end}` : "";
    details.textContent = `${sheet.bank} · ${sheet.transactions} 条明细${period}`;
  } else if (sheet.message) {
    details.textContent = sheet.message;
  } else {
    details.textContent = "空工作表。";
  }
  if (sheet.review_reason) {
    details.textContent += ` · 原因：${sheet.review_reason}`;
  }
  main.append(details);

  const badge = document.createElement("span");
  badge.className = `pill ${pillClass(meta.className)}`;
  badge.textContent = meta.label;

  item.append(main, badge);

  if (sheet.review_status === "pending") {
    const actions = document.createElement("div");
    actions.className = "lr-side";
    actions.style.cssText = "display:flex;gap:6px;flex:none;";
    if (sheet.outcome === "parsed") {
      const confirmButton = document.createElement("button");
      confirmButton.type = "button";
      confirmButton.className = "btn btn-sm btn-primary";
      confirmButton.textContent = "确认";
      confirmButton.addEventListener("click", () => sheetReviewAction(batchId, sheet.sheet_name, "confirm"));
      actions.append(confirmButton);
    }
    const ignoreButton = document.createElement("button");
    ignoreButton.type = "button";
    ignoreButton.className = "btn btn-sm";
    ignoreButton.textContent = "忽略";
    ignoreButton.addEventListener("click", () => sheetReviewAction(batchId, sheet.sheet_name, "ignore"));
    actions.append(ignoreButton);
    item.append(actions);
  }
  return item;
}

async function sheetReviewAction(batchId, sheetName, decision) {
  const payload = { sheets: [sheetName] };
  if (decision === "ignore") {
    const reason = (window.prompt("请填写忽略原因（必填）：", "") || "").trim();
    if (!reason) {
      showToast("忽略未提交", "必须填写忽略原因", "warn");
      return;
    }
    payload.reason = reason;
  }
  const response = await fetch(`/api/batches/${batchId}/${decision}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  }).catch(() => null);
  if (response?.status === 401 || response?.status === 403) {
    window.location.href = "index.html";
    return;
  }
  const result = await response?.json().catch(() => ({}));
  if (!response || !response.ok) {
    showToast("操作失败", result?.message || "请稍后重试", "danger");
    return;
  }
  showToast(decision === "confirm" ? "工作表已确认" : "工作表已忽略", `${sheetName}`, "success");
  await refreshAfterSheetAction(result, batchId);
}

async function refreshAfterSheetAction(result, batchId) {
  if (Array.isArray(result.sheets)) renderSheetList(result.sheets, batchId);
  await loadImportBatches();
  const button = $("#parseButton");
  if (!button) return;
  const pending = (result.sheets || []).filter((s) => s.outcome === "parsed" && s.review_status === "pending").length;
  if (pending === 0 && (result.sheets || []).length) {
    button.querySelector("span").textContent = "完成";
    button.dataset.stage = "done";
  } else {
    button.querySelector("span").textContent = `确认全部（${pending} 个待确认）`;
  }
}

async function confirmImport() {
  const result = state.parseResult;
  const batchId = result?.batch_id;
  const sheets = Array.isArray(result?.sheets) ? result.sheets : [];
  const pending = sheets
    .filter((s) => s.outcome === "parsed" && s.review_status === "pending")
    .map((s) => s.sheet_name);
  if (!pending.length) {
    resetUpload();
    await loadImportBatches();
    showView("upload");
    return;
  }
  const button = $("#parseButton");
  button.disabled = true;
  button.querySelector("span").textContent = "正在确认...";
  const response = await fetch(`/api/batches/${batchId}/confirm`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ sheets: pending }),
  }).catch(() => null);
  if (response?.status === 401 || response?.status === 403) {
    window.location.href = "index.html";
    return;
  }
  const outcome = await response?.json().catch(() => ({}));
  if (!response || !response.ok) {
    button.disabled = false;
    button.querySelector("span").textContent = "确认失败，点击重试";
    showToast("确认失败", outcome?.message || "请稍后重试", "danger");
    return;
  }
  button.disabled = false;
  if (Array.isArray(outcome.sheets)) renderSheetList(outcome.sheets, batchId);
  await loadImportBatches();
  resetUpload();
  showView("upload");
  showToast("流水已确认", `${outcome.updated.length} 个工作表已确认；未确认的工作表不参与计算`, "success");
}

function submitImportException() {
  resetUpload();
  showView("upload");
  showToast("解析异常未入账", `${state.selectedFile?.name || "该文件"} 不会进入匹配与计算，请核对模板后重新导出`, "warn");
}

function renderBatchRow(batch) {
  const row = document.createElement("tr");
  row.dataset.batchId = batch.id;
  const idCell = document.createElement("td");
  const id = document.createElement("span"); id.className = "cell-main num"; id.textContent = `IMP-${String(batch.id).padStart(6, "0")}`;
  const file = document.createElement("span"); file.className = "cell-sub"; file.textContent = batch.original_filename || "";
  idCell.append(id, file);

  const bank = document.createElement("td");
  bank.textContent = batch.bank_name || "—";

  const period = document.createElement("td");
  period.className = "num";
  period.textContent = batch.period_start && batch.period_end
    ? `${batch.period_start} ~ ${batch.period_end}`
    : "—";

  const count = document.createElement("td");
  count.className = "num-col";
  count.textContent = `${batch.confirmed_transactions ?? 0}`;

  const coverage = document.createElement("td");
  const coverageStatus = batch.status === "exception"
    ? { className: "danger", label: "未导入" }
    : batch.pending_sheets > 0
      ? { className: "warning", label: `${batch.pending_sheets} 个待确认` }
      : batch.confirmed_sheets > 0
        ? { className: "success", label: "已确认" }
        : { className: "neutral", label: "待处理" };
  coverage.innerHTML = `<span class="pill ${pillClass(coverageStatus.className)}">${coverageStatus.label}</span>`;

  const parseState = document.createElement("td");
  const statusParts = [];
  if (batch.exception_sheets > 0) statusParts.push(`${batch.exception_sheets} 个异常`);
  if (batch.ignored_sheets > 0) statusParts.push(`${batch.ignored_sheets} 个忽略`);
  if (statusParts.length) {
    parseState.innerHTML = `<span class="pill pill-warn">${statusParts.join("、")}</span>`;
  } else {
    parseState.innerHTML = '<span class="pill pill-success">解析成功</span>';
  }

  const time = document.createElement("td");
  time.className = "meta";
  time.textContent = String(batch.created_at || "").slice(0, 16).replace("T", " ");

  const action = document.createElement("td");
  action.innerHTML = '<button type="button" class="btn btn-sm" data-batch-view>查看</button>';

  row.append(idCell, bank, period, count, coverage, parseState, time, action);
  return row;
}

async function loadImportBatches() {
  const tbody = $("#importRows");
  if (!tbody) return;
  showTableLoading(tbody, 8);
  const response = await fetch("/api/batches").catch(() => null);
  if (response?.status === 401 || response?.status === 403) {
    window.location.href = "index.html";
    return;
  }
  if (!response?.ok) {
    showTableError(tbody, 8);
    return;
  }
  const result = await response?.json().catch(() => ({}));
  const batches = Array.isArray(result?.batches) ? result.batches : [];
  state.batches = batches;
  tbody.replaceChildren(...batches.map(renderBatchRow));
  const foot = $("#importFoot");
  if (foot) foot.textContent = batches.length ? `共 ${batches.length} 个批次` : "暂无批次";
}

function formatWorkspaceAmount(amount, currency) {
  const n = Number(amount);
  const text = Number.isFinite(n)
    ? n.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })
    : String(amount ?? "—");
  return `${currency || "CNY"} ${text}`;
}

function applyCompanyWorkspace(payload) {
  const events = Array.isArray(payload?.unilateral_events) ? payload.unilateral_events : [];
  const pending = Number(payload?.pending_unilateral ?? events.length) || 0;
  const total = Number(payload?.pending_total ?? pending) || 0;
  state.workspace = { pending_unilateral: pending, pending_total: total, unilateral_events: events };

  const cta = $("#workspaceUnilateralCta");
  if (cta) {
    cta.textContent = pending ? `去确认单边流水 (${pending})` : "单边流水已全部确认";
    cta.classList.toggle("btn-primary", pending > 0);
  }

  const status = $("#workspacePendingStatus");
  if (status) {
    status.textContent = total ? `${total} 项待处理` : "已完成";
    status.className = `pill ${total ? "pill-warn" : "pill-success"}`;
  }

  const sub = $("#workspaceTodoSub");
  if (sub) sub.textContent = total ? `权威待确认单边流水 ${pending} 笔` : "本月单边流水待办已清空";

  const list = $("#workspaceTodoList");
  if (list) {
    list.replaceChildren();
    if (!events.length) {
      const empty = document.createElement("div");
      empty.className = "empty";
      empty.style.padding = "18px 16px";
      empty.innerHTML = '<div class="e-title">暂无待确认单边流水</div><div>刷新或重登后仍以服务端权威状态为准</div>';
      list.append(empty);
    } else {
      events.forEach((event) => {
        const row = document.createElement("div");
        row.className = "list-row";
        row.dataset.taskType = "match";
        row.dataset.eventId = String(event.event_id);
        row.innerHTML = `
          <span class="pill pill-danger">阻断</span>
          <div class="lr-main">
            <div class="lr-title">确认单边流水 · ${formatWorkspaceAmount(event.amount, event.currency)}</div>
            <div class="lr-sub">${event.counterparty_company_name || "对方待指定"} · ${event.effective_at || "—"}</div>
          </div>
          <button class="btn btn-sm" data-view-link="reconcile">去确认</button>`;
        list.append(row);
      });
    }
  }

  const foot = $("#workspaceTodoFoot");
  if (foot) {
    foot.innerHTML = total
      ? `<span>处理完 ${total} 笔单边流水后，工作台数字与列表将同步归零</span>`
      : "<span>单边流水待办已完成</span>";
  }

  const flowState = $("#workspaceConfirmState");
  if (flowState) flowState.textContent = pending ? `待处理 ${pending} 笔` : "已完成";
  const flowMeta = $("#workspaceConfirmMeta");
  if (flowMeta) {
    flowMeta.textContent = pending
      ? `单边流水 ${pending} 笔待确认`
      : "已全部确认，等待集团结账";
  }
  // 完成态必须切到 success 绿（.flow-step.done），待确认保留 warn 黄（.doing）
  const flowStep = flowState?.closest(".flow-step");
  if (flowStep) {
    flowStep.classList.toggle("doing", pending > 0);
    flowStep.classList.toggle("done", pending === 0);
  }
  const flowSub = $("#workspaceFlowSub");
  if (flowSub) {
    flowSub.textContent = pending
      ? "当前停在第 3 步「往来确认」，完成后即可等待集团结账"
      : "第 3 步「往来确认」已完成，等待集团复核与结账";
  }

  const countMatch = $("#count-match");
  if (countMatch) countMatch.textContent = String(pending);

  const noticeTitle = $("#notice-title");
  const noticeBody = $("#notice-body");
  const notice = $("#blocking-notice");
  if (noticeTitle) {
    noticeTitle.textContent = pending
      ? `${pending} 项单边流水待确认，是结账阻断项`
      : "单边流水已全部确认完成";
  }
  if (noticeBody) {
    noticeBody.textContent = pending
      ? `工作台「去确认单边流水」与「本月待办」均读取同一权威集合（${pending} 笔）。确认成功后立即同步减一。`
      : "本公司单边流水待办已清空；刷新或重新登录后仍为 0。";
  }
  if (notice) {
    notice.classList.toggle("warn", pending > 0);
    notice.classList.toggle("success", pending === 0);
  }

  const badge = $('.side-nav a[data-view="reconcile"] .nav-badge');
  if (badge) {
    badge.textContent = pending;
    badge.style.display = pending ? "" : "none";
  }
}

async function loadCompanyWorkspace() {
  const response = await fetch("/api/company/workspace").catch(() => null);
  if (response?.status === 401 || response?.status === 403) {
    window.location.href = "index.html";
    return null;
  }
  const result = await response?.json().catch(() => null);
  if (!response?.ok || !result || result.status !== "ok") {
    applyCompanyWorkspace({ pending_unilateral: 0, pending_total: 0, unilateral_events: [] });
    showToast("工作台待办读取失败", result?.message || "请稍后重试", "danger");
    return null;
  }
  applyCompanyWorkspace(result);
  return result;
}

function renderUnilateralMatchCard(event, peers) {
  const card = document.createElement("div");
  card.className = "card";
  card.dataset.matchCard = "true";
  card.dataset.eventId = String(event.event_id);
  card.dataset.revision = String(event.revision ?? "");
  const amountText = formatWorkspaceAmount(event.amount, event.currency);
  const peerName = event.counterparty_company_name || "对方待指定";
  const options = peers
    .filter((p) => Number(p.id) !== Number(event.own_company_id))
    .map((p) => {
      const selected = Number(p.id) === Number(event.counterparty_company_id) ? " selected" : "";
      return `<option value="${p.id}"${selected}>${p.name}</option>`;
    })
    .join("");
  card.innerHTML = `
    <div class="card-head">
      <span class="card-title">${event.effective_at || "—"} · <span class="num">${amountText}</span>
        <span class="sub">对方：${peerName}</span></span>
      <span class="pill pill-danger">单边</span>
    </div>
    <div class="detail-box" style="margin-bottom: 12px;">
      <dl class="kv">
        <dt>事件编号</dt><dd class="num">${event.event_id}</dd>
        <dt>状态</dt><dd>${event.status || event.classification || "待确认"}</dd>
        <dt>金额</dt><dd class="num">${amountText}</dd>
      </dl>
    </div>
    <div class="field" style="margin-bottom: 14px;">
      <label>确认对方公司</label>
      <select class="select" data-counterparty-select>
        <option value="">请选择对方公司</option>
        ${options}
      </select>
      <span class="hint">确认后按现有单边确认规则锁定对方参与方；工作台数字以服务端权威状态重拉。</span>
    </div>
    <div class="row">
      <button class="btn btn-primary" data-match-confirm disabled>确认匹配</button>
      <a class="btn btn-ghost" data-view-link="flows">查看本方流水</a>
    </div>`;
  const select = $("[data-counterparty-select]", card);
  const button = $("[data-match-confirm]", card);
  const syncEnabled = () => {
    if (button) button.disabled = !select?.value;
  };
  select?.addEventListener("change", syncEnabled);
  syncEnabled();
  button?.addEventListener("click", async () => {
    if (!select?.value || button.dataset.busy === "1") return;
    button.dataset.busy = "1";
    button.disabled = true;
    const requestKey = `company-confirm-${event.event_id}-${event.revision}-${select.value}`;
    const response = await fetch(`/api/company/transfer-events/${event.event_id}/confirm`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        expected_revision: event.revision,
        request_key: requestKey,
        counterparty_company_id: Number(select.value),
        reason: "公司端确认单边流水",
      }),
    }).catch(() => null);
    if (response?.status === 401 || response?.status === 403) {
      window.location.href = "index.html";
      return;
    }
    const result = await response?.json().catch(() => ({}));
    if (!response?.ok) {
      button.dataset.busy = "0";
      syncEnabled();
      showToast("确认失败", result?.message || "请刷新后重试", "danger");
      // 失败不得本地误减：重拉权威状态
      await loadCompanyWorkspace();
      await renderReconcileMatchStack();
      return;
    }
    const matchedList = $("#matched-list");
    const matchedSummary = $("#matched-summary");
    if (matchedList) {
      const row = document.createElement("div");
      row.className = "list-row";
      row.innerHTML = `<div class="lr-main"><div class="lr-title">${amountText} · 已确认对方 ${select.selectedOptions[0]?.textContent || ""}</div></div><span class="pill pill-success lr-side">已匹配</span>`;
      matchedList.append(row);
      if (matchedSummary) matchedSummary.style.display = "";
    }
    if (result.workspace) applyCompanyWorkspace(result.workspace);
    else await loadCompanyWorkspace();
    await renderReconcileMatchStack();
    showToast("匹配已确认", "工作台待办已按权威状态同步", "success");
  });
  return card;
}

async function loadCompanyPeerOptions() {
  if (Array.isArray(state.companyPeers) && state.companyPeers.length) return state.companyPeers;
  const response = await fetch("/api/company/companies").catch(() => null);
  const result = await response?.json().catch(() => null);
  const peers = Array.isArray(result?.companies) ? result.companies : [];
  state.companyPeers = peers;
  return peers;
}

async function renderReconcileMatchStack() {
  const stack = $("#match-stack");
  if (!stack) return;
  const events = state.workspace?.unilateral_events || [];
  const peers = await loadCompanyPeerOptions();
  stack.replaceChildren();
  if (!events.length) {
    const empty = document.createElement("div");
    empty.className = "empty";
    empty.innerHTML = '<div class="e-title">单边流水已全部匹配</div><div>确认结果已同步至工作台权威待办</div>';
    stack.append(empty);
    return;
  }
  events.forEach((event) => stack.append(renderUnilateralMatchCard(event, peers)));
}

function initReconcile() {
  const subjectRows = $$("[data-subject-row]");
  let pendingSubject = subjectRows.length;
  const countSubject = $("#count-subject");
  if (countSubject) countSubject.textContent = pendingSubject;

  $$(".subject-confirm-btn").forEach((button) => {
    button.addEventListener("click", () => {
      const row = button.closest("[data-subject-row]");
      const select = $("select", row);
      const subject = select.value;
      select.disabled = true;
      button.disabled = true;
      button.textContent = "已确认";
      const statusCell = $(".subject-status", row);
      statusCell.innerHTML = `<span class="pill pill-success">已确认 · ${subject}</span>`;
      pendingSubject -= 1;
      if (countSubject) countSubject.textContent = pendingSubject;
      showToast("科目已确认", "科目确认仍为页面演示，不计入工作台权威待办", "success");
    });
  });

  const tabMatch = $("#tab-match");
  const tabSubject = $("#tab-subject");
  const panelMatch = $("#panel-match");
  const panelSubject = $("#panel-subject");
  function switchTab(which) {
    tabMatch?.classList.toggle("active", which === "match");
    tabSubject?.classList.toggle("active", which === "subject");
    tabMatch?.setAttribute("aria-pressed", String(which === "match"));
    tabSubject?.setAttribute("aria-pressed", String(which === "subject"));
    if (panelMatch) panelMatch.style.display = which === "match" ? "" : "none";
    if (panelSubject) panelSubject.style.display = which === "subject" ? "" : "none";
  }
  tabMatch?.addEventListener("click", () => switchTab("match"));
  tabSubject?.addEventListener("click", () => switchTab("subject"));
}

function renderCompanyNoticeRow(item) {
  const row = document.createElement("div");
  row.className = "list-row";
  row.dataset.status = item.status_ui;
  row.dataset.reminderId = String(item.id);
  const titleWeight = item.status_ui === "unread" ? "650" : "400";
  const titleColor = item.status_ui === "unread" ? "var(--fg)" : "var(--muted)";
  const sourceTag = item.source === "auto" ? '<span class="pill pill-info">系统</span>' : '<span class="tag">管理员</span>';
  const actionLink = item.action_link;
  let side = "";
  if (item.status_ui === "unread") {
    side = '<button class="btn btn-sm btn-mark-read">标记已读</button>';
  } else if (item.status_ui === "doing" && actionLink) {
    side = `<button class="btn btn-sm btn-primary" data-view-link="${actionLink}">去处理</button>`;
  } else if (item.status_ui === "doing") {
    side = '<button class="btn btn-sm btn-ghost btn-mark-done">标记完成</button>';
  }
  row.innerHTML =
    `<span class="pill ${reminderStatusPill(item.status_ui)}" style="flex:none;">${reminderStatusLabel(item.status_ui)}</span>` +
    sourceTag +
    `<div class="lr-main"><div class="lr-title" style="font-weight:${titleWeight};color:${titleColor};">${item.title}</div>` +
    `<div class="lr-sub"><span class="meta">${formatReminderTime(item.last_sent_at)}</span> · ${item.content}</div></div>` +
    `<div class="lr-side">${side}</div>`;
  return row;
}

function initNotifications() {
  const tabs = $("#notice-tabs");
  const list = $("#notice-list");
  if (!tabs || !list) return;
  const emptyBox = $("#notice-empty");
  let currentFilter = "all";
  let rows = [];

  function refreshCounts(stats, unreadCount) {
    const c = stats || { all: rows.length, unread: 0, doing: 0, done: 0 };
    if (!stats) {
      c.all = rows.length;
      rows.forEach((row) => { c[row.getAttribute("data-status")] += 1; });
    } else {
      c.all = rows.length;
    }
    $("#count-all").textContent = c.all;
    $("#count-unread").textContent = c.unread;
    $("#count-doing").textContent = c.doing;
    $("#count-done").textContent = c.done;
    const navBadge = $('.side-nav a[data-view="notifications"] .nav-badge');
    const unread = unreadCount ?? c.unread;
    if (navBadge) {
      navBadge.textContent = unread;
      navBadge.style.display = unread ? "" : "none";
    }
    const wsLink = $("#workspace-notice-link");
    if (wsLink) wsLink.textContent = unread ? `全部通知 (${c.all}) →` : "全部通知 →";
  }

  function applyFilter() {
    let visible = 0;
    rows.forEach((row) => {
      const show = currentFilter === "all" || row.getAttribute("data-status") === currentFilter;
      row.style.display = show ? "" : "none";
      if (show) visible += 1;
    });
    if (emptyBox) emptyBox.style.display = visible === 0 ? "" : "none";
  }

  async function loadReminders() {
    const response = await fetch("/api/company/reminders").catch(() => null);
    if (response?.status === 401) { window.location.href = "index.html"; return; }
    const result = await response?.json().catch(() => ({}));
    if (!response?.ok) return;
    const items = result.reminders || [];
    list.replaceChildren(...items.map((item) => renderCompanyNoticeRow(item)));
    rows = $$(".list-row", list);
    refreshCounts(result.stats, result.unread_count);
    applyFilter();
    const wsList = $("#workspace-notice-list");
    if (wsList) {
      const preview = items.slice(0, 3);
      if (!preview.length) {
        wsList.innerHTML = '<div class="empty"><div class="e-title">暂无通知</div></div>';
      } else {
        wsList.replaceChildren(...preview.map((item) => {
          const row = document.createElement("div");
          row.className = "list-row";
          row.innerHTML =
            `<span class="pill ${reminderTypePill(item.rule_key)}">${item.display_type}</span>` +
            `<div class="lr-main"><div class="lr-title">${item.title}</div><div class="lr-sub">${item.content.slice(0, 48)}</div></div>` +
            `<span class="lr-side meta">${formatReminderTime(item.last_sent_at).slice(5, 10)}</span>`;
          return row;
        }));
      }
    }
  }

  tabs.addEventListener("click", (event) => {
    const btn = event.target.closest("button[data-filter]");
    if (!btn) return;
    $$("button", tabs).forEach((b) => { b.classList.remove("active"); b.setAttribute("aria-pressed", "false"); });
    btn.classList.add("active");
    btn.setAttribute("aria-pressed", "true");
    currentFilter = btn.dataset.filter;
    applyFilter();
  });

  list.addEventListener("click", async (event) => {
    const viewLink = event.target.closest("[data-view-link]");
    if (viewLink && list.contains(viewLink)) {
      event.preventDefault();
      showView(viewLink.dataset.viewLink);
      return;
    }
    const readBtn = event.target.closest(".btn-mark-read");
    const doneBtn = event.target.closest(".btn-mark-done");
    const btn = readBtn || doneBtn;
    if (!btn) return;
    const row = btn.closest(".list-row");
    const id = row?.dataset.reminderId;
    if (!id) return;
    const action = readBtn ? "acknowledge" : "resolve";
    const response = await fetch(`/api/company/reminders/${id}/${action}`, { method: "POST" }).catch(() => null);
    if (response?.status === 401) { window.location.href = "index.html"; return; }
    if (!response?.ok) return;
    await loadReminders();
  });

  $("#mark-all-read")?.addEventListener("click", async () => {
    const unreadRows = rows.filter((row) => row.getAttribute("data-status") === "unread");
    for (const row of unreadRows) {
      const id = row.dataset.reminderId;
      await fetch(`/api/company/reminders/${id}/acknowledge`, { method: "POST" }).catch(() => null);
    }
    await loadReminders();
    showToast("通知已全部标为已读", "", "success");
  });

  loadReminders();
}

function openAccountDetail(account) {
  const meta = companyAccountMeta(account.status);
  $("#ad-title").textContent = `${account.bank_name || ""} · 尾号 ${accountTail(account.account_number_masked)}`;
  $("#ad-sub").textContent = "本公司 · 登记账户明细";
  $("#ad-bank").textContent = account.bank_name || "—";
  $("#ad-tail").textContent = `尾号 ${accountTail(account.account_number_masked)}`;
  $("#ad-type").textContent = account.account_type || "—";
  $("#ad-branch").textContent = account.account_name || "—";
  $("#ad-reg").textContent = String(account.created_at || "").slice(0, 10) || "—";
  $("#ad-status").textContent = meta.status.label;
  $("#ad-audit").textContent = meta.audit.label;
  $("#ad-purpose").textContent = "—";
  $("#ad-effective").textContent = account.effective_from || "待审核确定";
  $("#ad-range").textContent = account.usable ? "尚未上传" : "—";
  $("#ad-reason").textContent = account.status === "returned" && account.review_reason ? account.review_reason : "—";
  $("#modal-account-detail")?.classList.add("open");
}

function yuanToWan(value) {
  const n = Number(value);
  if (!Number.isFinite(n)) return null;
  return n / 10000;
}

function formatWanHtml(value, { signed = false } = {}) {
  const wan = yuanToWan(value);
  if (wan === null) return "—";
  const abs = Math.abs(wan).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  if (!signed) return `${abs}<span class="unit">万元</span>`;
  const sign = wan > 0 ? "+" : wan < 0 ? "−" : "";
  return `${sign}${abs}<span class="unit">万元</span>`;
}

function formatWanText(value, { signed = false } = {}) {
  const wan = yuanToWan(value);
  if (wan === null) return "—";
  const abs = Math.abs(wan).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  if (!signed) return `${abs} 万元`;
  const sign = wan > 0 ? "+" : wan < 0 ? "−" : "";
  return `${sign}${abs} 万元`;
}

function netDirectionMeta(netValue, netDirection) {
  const wan = yuanToWan(netValue);
  if (wan === null || wan === 0 || !netDirection) {
    return { label: "持平", className: "flat", signedClass: "" };
  }
  if (netDirection === "receivable" || wan > 0) {
    return { label: "应收", className: "recv", signedClass: "pos" };
  }
  return { label: "应付", className: "pay", signedClass: "neg" };
}

function netLabelForWindow(windowInfo) {
  return windowInfo?.has_opening ? "期末净往来" : "期间净变动";
}

function showTransfersOverviewLayer() {
  const overview = $("#transfersOverviewLayer");
  const detail = $("#transfersDetailLayer");
  if (overview) overview.hidden = false;
  if (detail) detail.hidden = true;
}

function showTransfersDetailLayer() {
  const overview = $("#transfersOverviewLayer");
  const detail = $("#transfersDetailLayer");
  if (overview) overview.hidden = true;
  if (detail) detail.hidden = false;
}

function setTransfersUiState(mode) {
  ["transfersLoading", "transfersError", "transfersEmpty", "transfersData"].forEach((id) => {
    const el = $(`#${id}`);
    if (el) el.hidden = id !== mode;
  });
}

function applyTransfersNavBadge(pendingCount) {
  const badge = $("#transfersNavBadge") || $('.side-nav a[data-view="transfers"] .nav-badge');
  if (!badge) return;
  const n = Number(pendingCount) || 0;
  badge.textContent = String(n);
  badge.hidden = n <= 0;
  if (n <= 0) badge.setAttribute("hidden", "");
  else badge.removeAttribute("hidden");
}

function renderWorkspaceTransfersCard(summary) {
  const sub = $("#workspaceTransfersSub");
  if (!sub || !summary) return;
  const win = summary.window || {};
  const confirmed = summary.confirmed || {};
  const pending = summary.pending || {};
  const netMeta = netDirectionMeta(confirmed.net_change, confirmed.net_direction);
  sub.textContent = `${win.start || "—"} 至 ${win.end || "—"} · 集团内公司间 · 单位：万元`;
  const inflow = $("#wsTfIn");
  const outflow = $("#wsTfOut");
  const net = $("#wsTfNet");
  const pendingEl = $("#wsTfPending");
  const netLabel = $("#wsTfNetLabel");
  if (inflow) inflow.innerHTML = formatWanHtml(confirmed.inflow_total, { signed: true });
  if (outflow) {
    outflow.innerHTML = formatWanHtml(
      confirmed.outflow_total != null ? -Math.abs(Number(confirmed.outflow_total)) : null,
      { signed: true },
    );
  }
  if (netLabel) netLabel.textContent = `${netLabelForWindow(win)}${netMeta.label !== "持平" ? ` · ${netMeta.label}` : ""}`;
  if (net) {
    net.className = `ms-value ${netMeta.signedClass}`.trim();
    net.innerHTML = formatWanHtml(confirmed.net_change, { signed: true });
  }
  if (pendingEl) {
    const pCount = Number(pending.count) || 0;
    pendingEl.innerHTML = pCount
      ? `${formatWanHtml(pending.amount_total)}<span class="unit"> · ${pCount} 笔</span>`
      : `0.00<span class="unit">万元 · 0 笔</span>`;
  }
}

async function loadTransfersSummary({ asOf } = {}) {
  if (!$("#transfersOverviewLayer")) return null;
  setTransfersUiState("transfersLoading");
  const params = new URLSearchParams();
  if (asOf) params.set("as_of", asOf);
  const qs = params.toString();
  const response = await fetch(`/api/company/intercompany/summary${qs ? `?${qs}` : ""}`).catch(() => null);
  if (response?.status === 401 || response?.status === 403) {
    window.location.href = "index.html";
    return null;
  }
  const result = await response?.json().catch(() => null);
  if (!response?.ok || !result || result.status !== "ok") {
    setTransfersUiState("transfersError");
    const body = $("#transfersErrorBody");
    if (body) body.textContent = result?.message || "服务器连接异常。为避免误读，本页不展示任何金额。";
    return null;
  }
  state.transfersSummary = result;
  applyTransfersNavBadge(result.pending?.count);
  renderWorkspaceTransfersCard(result);
  renderTransfersOverview(result);
  return result;
}

function renderTransfersOverview(summary) {
  const win = summary.window || {};
  const confirmed = summary.confirmed || {};
  const pending = summary.pending || {};
  const cps = Array.isArray(summary.counterparties) ? summary.counterparties : [];
  const ownName = summary.own_company?.name || "本公司";
  const pageSub = $("#transfersPageSub");
  if (pageSub) {
    pageSub.textContent = `${ownName} · 统计区间 ${win.start || "—"} 至 ${win.end || "—"}，仅含集团内公司间转账（HEL-169 口径）。单位：万元。`;
  }

  const goConfirm = $("#transfersGoConfirm");
  const pCount = Number(pending.count) || 0;
  if (goConfirm) {
    goConfirm.hidden = pCount <= 0;
    goConfirm.textContent = pCount ? `去确认待确认 ${pCount} 笔` : "去确认待确认";
  }

  const hasAny = cps.length > 0 || Number(confirmed.outflow_count || 0) > 0 || Number(confirmed.inflow_count || 0) > 0 || pCount > 0;
  if (!hasAny) {
    setTransfersUiState("transfersEmpty");
    const emptyStats = $("#transfersEmptyStats");
    if (emptyStats) {
      emptyStats.innerHTML = `
        <div class="card stat-card"><div class="stat-label">往来公司数</div><div class="stat-value">0<span class="unit">家</span></div><div class="stat-foot">${win.start || "—"} ~ ${win.end || "—"}</div></div>
        <div class="card stat-card"><div class="stat-label">本期转入 · 流入</div><div class="stat-value">0.00<span class="unit">万元</span></div></div>
        <div class="card stat-card"><div class="stat-label">本期转出 · 流出</div><div class="stat-value">0.00<span class="unit">万元</span></div></div>
        <div class="card stat-card"><div class="stat-label">${netLabelForWindow(win)}</div><div class="stat-value">0.00<span class="unit">万元</span></div></div>`;
    }
    return;
  }

  setTransfersUiState("transfersData");
  const companies = $("#tfStatCompanies");
  const inflow = $("#tfStatIn");
  const outflow = $("#tfStatOut");
  const net = $("#tfStatNet");
  const netTitle = $("#tfStatNetTitle");
  const netFoot = $("#tfStatNetFoot");
  const netMeta = netDirectionMeta(confirmed.net_change, confirmed.net_direction);
  if (companies) companies.innerHTML = `${cps.length}<span class="unit">家</span>`;
  if (inflow) inflow.innerHTML = formatWanHtml(confirmed.inflow_total, { signed: true });
  if (outflow) {
    const wan = yuanToWan(confirmed.outflow_total);
    const abs = wan === null ? "—" : Math.abs(wan).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    outflow.innerHTML = wan === null ? "—" : `−${abs}<span class="unit">万元</span>`;
  }
  if (netTitle) netTitle.textContent = netLabelForWindow(win);
  if (net) {
    const tag = netMeta.label !== "持平"
      ? ` <span class="xfer-dir-tag ${netMeta.className}">${netMeta.label}</span>`
      : "";
    net.innerHTML = `${formatWanHtml(confirmed.net_change, { signed: true })}${tag}`;
  }
  if (netFoot) {
    netFoot.textContent = win.has_opening
      ? "期末 = 期初 + 已确认转出 − 已确认转入"
      : "起算日未就绪 · 展示期间净变动（转出 − 转入），不得当作期末余额";
  }

  const confirmedCount = (Number(confirmed.outflow_count) || 0) + (Number(confirmed.inflow_count) || 0);
  const tfConfirmedCount = $("#tfConfirmedCount");
  const tfConfirmedNet = $("#tfConfirmedNet");
  const tfPendingCount = $("#tfPendingCount");
  const tfPendingAmount = $("#tfPendingAmount");
  if (tfConfirmedCount) tfConfirmedCount.textContent = `${confirmedCount} 笔`;
  if (tfConfirmedNet) tfConfirmedNet.innerHTML = formatWanHtml(confirmed.net_change, { signed: true });
  const openingCard = $("#tfStatOpeningCard");
  const endingCard = $("#tfStatEndingCard");
  const openingEl = $("#tfStatOpening");
  const endingEl = $("#tfStatEnding");
  if (win.has_opening) {
    if (openingCard) openingCard.hidden = false;
    if (endingCard) endingCard.hidden = false;
    if (openingEl) openingEl.innerHTML = formatWanHtml(win.opening, { signed: true });
    if (endingEl) endingEl.innerHTML = formatWanHtml(win.ending, { signed: true });
  } else {
    if (openingCard) openingCard.hidden = true;
    if (endingCard) endingCard.hidden = true;
  }

  if (tfPendingCount) tfPendingCount.textContent = `${pCount} 笔`;
  if (tfPendingAmount) tfPendingAmount.innerHTML = formatWanHtml(pending.amount_total || 0);

  const tbody = $("#transfersCpBody");
  const tfoot = $("#transfersCpFoot");
  if (!tbody) return;
  if (!cps.length) {
    tbody.innerHTML = `<tr><td colspan="7"><div class="empty" style="border:0;padding:24px 0;"><div class="e-title">暂无对方公司汇总</div></div></td></tr>`;
    if (tfoot) tfoot.innerHTML = "";
    return;
  }
  tbody.innerHTML = cps.map((cp) => {
    const meta = netDirectionMeta(cp.net, Number(cp.net) > 0 ? "receivable" : Number(cp.net) < 0 ? "payable" : null);
    const pendingN = Number(cp.pending_count) || 0;
    const pendingPill = pendingN
      ? `<span class="pill pill-warn">${pendingN} 笔待确认</span>`
      : `<span class="pill pill-success">全部已确认</span>`;
    const last = cp.last_effective_at ? String(cp.last_effective_at).slice(0, 10) : "—";
    const inWan = yuanToWan(cp.confirmed_inflow);
    const outWan = yuanToWan(cp.confirmed_outflow);
    const netWan = yuanToWan(cp.net);
    const fmtAbs = (wan) => wan === null ? "—" : Math.abs(wan).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    const inText = inWan === null ? "—" : `+${fmtAbs(inWan)}`;
    const outText = outWan === null ? "—" : `−${fmtAbs(outWan)}`;
    const netText = netWan === null ? "—" : `${netWan > 0 ? "+" : netWan < 0 ? "−" : ""}${fmtAbs(netWan)}`;
    return `<tr class="clickable" data-cp-id="${cp.company_id}" data-cp-name="${String(cp.company_name || "").replace(/"/g, "&quot;")}">
      <td class="cell-main">${cp.company_name || "—"}</td>
      <td class="num-col amt-in">${inText}</td>
      <td class="num-col amt-out">${outText}</td>
      <td class="num-col">${netText}</td>
      <td><span class="xfer-dir-tag ${meta.className}">${meta.label}</span></td>
      <td>${pendingPill}</td>
      <td class="num">${last}</td>
    </tr>`;
  }).join("");

  if (tfoot) {
    const totalPending = cps.reduce((s, cp) => s + (Number(cp.pending_count) || 0), 0);
    const inWan = yuanToWan(confirmed.inflow_total);
    const outWan = yuanToWan(confirmed.outflow_total);
    const netWan = yuanToWan(confirmed.net_change);
    const fmtAbs = (wan) => wan === null ? "—" : Math.abs(wan).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    tfoot.innerHTML = `<tr>
      <td>合计</td>
      <td class="num-col amt-in">${inWan === null ? "—" : `+${fmtAbs(inWan)}`}</td>
      <td class="num-col amt-out">${outWan === null ? "—" : `−${fmtAbs(outWan)}`}</td>
      <td class="num-col">${netWan === null ? "—" : `${netWan > 0 ? "+" : netWan < 0 ? "−" : ""}${fmtAbs(netWan)}`}</td>
      <td><span class="xfer-dir-tag ${netMeta.className}">${netMeta.label}</span></td>
      <td>${totalPending ? `<span class="pill pill-warn">${totalPending} 笔待确认</span>` : `<span class="pill pill-success">全部已确认</span>`}</td>
      <td></td>
    </tr>`;
  }
}

function transfersEventQueryParams({ includeCursor = false } = {}) {
  const detail = state.transfersDetail || {};
  const filters = detail.filters || {};
  const params = new URLSearchParams();
  if (detail.company_id) params.set("counterparty_id", String(detail.company_id));
  if (filters.from) params.set("from", filters.from);
  if (filters.to) params.set("to", filters.to);
  if (filters.direction) params.set("direction", filters.direction);
  if (filters.state) params.set("state", filters.state);
  params.set("limit", "50");
  if (includeCursor && detail.nextCursor) params.set("cursor", detail.nextCursor);
  return params;
}

async function openTransfersDetail(companyId, companyName, { keepFilters = false } = {}) {
  const summary = state.transfersSummary;
  const win = summary?.window || {};
  const existing = state.transfersDetail;
  const filters = keepFilters && existing?.filters
    ? { ...existing.filters }
    : {
      from: win.start || "",
      to: win.end || "",
      direction: "",
      state: "",
    };
  state.transfersDetail = {
    company_id: Number(companyId),
    company_name: companyName || "对方公司",
    filters,
    nextCursor: null,
    events: [],
  };
  state.transfersKeepDetail = true;
  showTransfersDetailLayer();
  const title = $("#currentViewName");
  if (title) title.textContent = `转账往来 / ${state.transfersDetail.company_name}`;
  $("#transfersDetailTitle").textContent = state.transfersDetail.company_name;
  $("#tfFilterFrom").value = filters.from || "";
  $("#tfFilterTo").value = filters.to || "";
  $("#tfFilterDirection").value = filters.direction || "";
  $("#tfFilterState").value = filters.state || "";

  const cp = (summary?.counterparties || []).find((c) => Number(c.company_id) === Number(companyId));
  const pendingN = Number(cp?.pending_count) || 0;
  $("#tfDetailCount").innerHTML = `—`;
  $("#tfDetailCountFoot").textContent = pendingN ? `其中待确认 ${pendingN} 笔（汇总）` : "按筛选加载明细";
  if (cp) {
    $("#tfDetailIn").innerHTML = formatWanHtml(cp.confirmed_inflow, { signed: true });
    const wan = yuanToWan(cp.confirmed_outflow);
    $("#tfDetailOut").innerHTML = wan === null
      ? "—"
      : `−${Math.abs(wan).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}<span class="unit">万元</span>`;
  }
  if (state.currentView !== "transfers") {
    state.transfersKeepDetail = true;
    showView("transfers");
  }
  await loadTransfersEvents({ reset: true });
}

async function loadTransfersEvents({ reset = false } = {}) {
  if (!state.transfersDetail?.company_id) return;
  const tbody = $("#transfersEventBody");
  const empty = $("#transfersEventEmpty");
  const foot = $("#transfersEventFoot");
  const moreBtn = $("#transfersLoadMore");
  if (reset) {
    state.transfersDetail.nextCursor = null;
    state.transfersDetail.events = [];
    if (tbody) showTableLoading(tbody, 6);
    if (empty) empty.hidden = true;
    if (moreBtn) moreBtn.hidden = true;
  }
  const params = transfersEventQueryParams({ includeCursor: !reset && !!state.transfersDetail.nextCursor });
  // Always need a distinguishing filter so HEL-176 path is used (counterparty_id is enough)
  const response = await fetch(`/api/company/intercompany/events?${params}`).catch(() => null);
  if (response?.status === 401 || response?.status === 403) {
    window.location.href = "index.html";
    return;
  }
  const result = await response?.json().catch(() => null);
  if (!response?.ok || !result || result.status !== "ok") {
    if (tbody) showTableError(tbody, 6);
    showToast("明细加载失败", result?.message || "请稍后重试", "danger");
    return;
  }
  const events = Array.isArray(result.events) ? result.events : [];
  if (reset) state.transfersDetail.events = events;
  else state.transfersDetail.events = [...(state.transfersDetail.events || []), ...events];
  state.transfersDetail.nextCursor = result.next_cursor || null;
  state.transfersDetail.hasMore = !!result.has_more;
  renderTransfersEvents();
  if (foot) foot.textContent = `已加载 ${state.transfersDetail.events.length} 笔${result.has_more ? " · 还有更多" : ""}`;
  if (moreBtn) moreBtn.hidden = !result.has_more;
  $("#tfDetailCount").innerHTML = `${state.transfersDetail.events.length}${result.has_more ? "+" : ""}<span class="unit">笔</span>`;
}

function transfersStateLabel(event) {
  if (event.state === "confirmed") {
    if (event.pairing === "paired") return { pill: "pill-success", text: "已确认 · 双边" };
    if (event.locked) return { pill: "pill-success", text: "已确认 · 单边锁定" };
    return { pill: "pill-success", text: "已确认" };
  }
  return { pill: "pill-warn", text: "待确认" };
}

function renderTransfersEvents() {
  const tbody = $("#transfersEventBody");
  const empty = $("#transfersEventEmpty");
  if (!tbody) return;
  const events = state.transfersDetail?.events || [];
  if (!events.length) {
    tbody.innerHTML = "";
    if (empty) empty.hidden = false;
    return;
  }
  if (empty) empty.hidden = true;
  tbody.innerHTML = events.map((ev) => {
    const dir = ev.direction === "out" ? "转出" : ev.direction === "in" ? "转入" : "—";
    const dirClass = ev.direction === "out" ? "amt-out" : ev.direction === "in" ? "amt-in" : "";
    const wan = yuanToWan(ev.amount);
    const amt = wan === null
      ? "—"
      : `${ev.direction === "out" ? "−" : "+"}${Math.abs(wan).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
    const st = transfersStateLabel(ev);
    const date = String(ev.effective_at || "").slice(0, 10) || "—";
    const pendingClass = ev.state === "pending" ? " is-pending" : "";
    return `<tr class="${pendingClass.trim()}" data-event-id="${ev.event_id}">
      <td class="num">${date}</td>
      <td class="wrap">${ev.summary || "—"}</td>
      <td>${dir}${ev.direction === "in" ? " ↓" : ev.direction === "out" ? " ↑" : ""}</td>
      <td class="num-col ${dirClass}">${amt}</td>
      <td><span class="pill ${st.pill}">${st.text}</span></td>
      <td><button type="button" class="btn btn-sm" data-transfer-evidence="${ev.event_id}">原始流水</button></td>
    </tr>`;
  }).join("");
}

async function openTransferEvidence(eventId) {
  const drawer = $("#transferEvidenceDrawer");
  if (!drawer) return;
  $("#tfEvTitle").textContent = "加载中…";
  $("#tfEvDesc").textContent = "";
  $("#tfEvFields").replaceChildren();
  drawer.classList.add("is-open");
  drawer.setAttribute("aria-hidden", "false");
  const response = await fetch(`/api/company/transfer-events/${eventId}`).catch(() => null);
  if (response?.status === 401 || response?.status === 403) {
    window.location.href = "index.html";
    return;
  }
  const result = await response?.json().catch(() => null);
  if (!response?.ok || !result || result.status !== "ok") {
    $("#tfEvTitle").textContent = "无法打开原始流水";
    $("#tfEvDesc").textContent = result?.message || "事件不存在或无权查看";
    return;
  }
  const event = result.event || {};
  const obs = Array.isArray(event.observations) ? event.observations[0] : null;
  const cp = event.counterparty || {};
  $("#tfEvTag").className = "pill pill-info";
  $("#tfEvTag").textContent = "银行原始流水 · 只读";
  $("#tfEvTitle").textContent = obs?.reference || `事件 #${event.event_id}`;
  $("#tfEvDesc").textContent = obs?.import_batch_id
    ? `导入批次 IMP-${String(obs.import_batch_id).padStart(6, "0")}${obs.original_filename ? ` · ${obs.original_filename}` : ""}`
    : "本方银行流水原文";
  const income = obs?.income != null && Number(obs.income) !== 0;
  const amountText = obs
    ? `${Number(income ? obs.income : obs.expense).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })} 元（${income ? "收 / 转入" : "付 / 转出"}）`
    : `${event.amount || "—"} ${event.currency || "CNY"}`;
  const fields = [
    ["本方账户", obs?.own_account_masked || "—"],
    ["交易时间", obs?.transaction_at || event.effective_at || "—"],
    ["方向", income ? "收入（转入）" : "支出（转出）"],
    ["金额", amountText],
    ["对方户名", obs?.counterparty_name || cp.company_name || "—"],
    ["对方账号", obs?.counterparty_account_masked || cp.account_number_masked || "—"],
    ["工作表", obs?.sheet_name || "—"],
    ["原始文件行号", obs?.source_row != null ? String(obs.source_row) : "—"],
    ["摘要", obs?.summary || event.reason || "—"],
    ["确认状态", event.pairing === "paired" ? "已确认 · 双边一致" : event.status || "—"],
  ];
  const dl = $("#tfEvFields");
  dl.replaceChildren(...fields.flatMap(([label, value]) => {
    const dt = document.createElement("dt");
    dt.textContent = label;
    const dd = document.createElement("dd");
    dd.className = "num";
    dd.textContent = value;
    return [dt, dd];
  }));
  $("[data-close-transfer-evidence]", drawer)?.focus();
}

function closeTransferEvidence({ restoreFocus = true } = {}) {
  const drawer = $("#transferEvidenceDrawer");
  if (!drawer) return;
  drawer.classList.remove("is-open");
  drawer.setAttribute("aria-hidden", "true");
}

function buildTransfersExportUrl() {
  const detail = state.transfersDetail;
  const summary = state.transfersSummary;
  const params = new URLSearchParams();
  if (detail?.company_id) {
    params.set("counterparty_id", String(detail.company_id));
    const f = detail.filters || {};
    if (f.from) params.set("from", f.from);
    if (f.to) params.set("to", f.to);
    if (f.direction) params.set("direction", f.direction);
  } else if (summary?.window) {
    if (summary.window.start) params.set("from", summary.window.start);
    if (summary.window.end) params.set("to", summary.window.end);
  }
  const qs = params.toString();
  return `/api/company/intercompany/export.csv${qs ? `?${qs}` : ""}`;
}

function initTransfers() {
  if (!$("[data-page='transfers']")) return;

  $("#transfersRetryBtn")?.addEventListener("click", () => loadTransfersSummary());
  $("#transfersExportBtn")?.addEventListener("click", () => {
    window.location.href = buildTransfersExportUrl();
  });
  $("#transfersDetailExportBtn")?.addEventListener("click", () => {
    window.location.href = buildTransfersExportUrl();
  });
  $("#transfersBackBtn")?.addEventListener("click", () => {
    state.transfersDetail = null;
    showTransfersOverviewLayer();
    const title = $("#currentViewName");
    if (title) title.textContent = "转账往来";
    if (!state.transfersSummary) loadTransfersSummary();
    else renderTransfersOverview(state.transfersSummary);
  });
  $("#transfersCpBody")?.addEventListener("click", (event) => {
    const row = event.target.closest("tr[data-cp-id]");
    if (!row) return;
    openTransfersDetail(row.dataset.cpId, row.dataset.cpName);
  });
  $("#transfersFilterForm")?.addEventListener("submit", (event) => {
    event.preventDefault();
    if (!state.transfersDetail) return;
    state.transfersDetail.filters = {
      from: $("#tfFilterFrom")?.value || "",
      to: $("#tfFilterTo")?.value || "",
      direction: $("#tfFilterDirection")?.value || "",
      state: $("#tfFilterState")?.value || "",
    };
    loadTransfersEvents({ reset: true });
  });
  const clearFilters = () => {
    if (!state.transfersDetail) return;
    const win = state.transfersSummary?.window || {};
    state.transfersDetail.filters = {
      from: win.start || "",
      to: win.end || "",
      direction: "",
      state: "",
    };
    $("#tfFilterFrom").value = state.transfersDetail.filters.from;
    $("#tfFilterTo").value = state.transfersDetail.filters.to;
    $("#tfFilterDirection").value = "";
    $("#tfFilterState").value = "";
    loadTransfersEvents({ reset: true });
  };
  $("#tfFilterReset")?.addEventListener("click", clearFilters);
  $("#tfEmptyClear")?.addEventListener("click", clearFilters);
  $("#transfersLoadMore")?.addEventListener("click", () => loadTransfersEvents({ reset: false }));
  $("#transfersEventBody")?.addEventListener("click", (event) => {
    const btn = event.target.closest("[data-transfer-evidence]");
    if (!btn) return;
    openTransferEvidence(btn.dataset.transferEvidence);
  });
  $$("[data-close-transfer-evidence]").forEach((btn) => btn.addEventListener("click", () => closeTransferEvidence()));
  document.addEventListener("keydown", (event) => {
    if (event.key === "Escape" && $("#transferEvidenceDrawer")?.classList.contains("is-open")) {
      event.stopPropagation();
      closeTransferEvidence();
    }
  });

  // 工作台概览与角标：与页面共用 summary
  loadTransfersSummary();
}

function initCompany() {
  $("#openAttestationFromWorkspace")?.addEventListener("click", () => openAttestationDialog());
  $("#openAttestationFromFlows")?.addEventListener("click", () => openAttestationDialog());
  $$("[data-close-attestation]").forEach((btn) => btn.addEventListener("click", () => closeModal("attestationDialog")));
  $("#attestationForm")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const payload = {
      bank_account_id: Number($("#att-account-id")?.value || 0),
      gap_start: $("#att-gap-start")?.value || "",
      gap_end: $("#att-gap-end")?.value || "",
      reason: $("#att-reason")?.value || "",
      evidence: $("#att-evidence")?.value || "",
    };
    const response = await fetch("/api/company/no-business-attestations", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    }).catch(() => null);
    const result = await response?.json().catch(() => ({}));
    if (!response?.ok) {
      showToast("提交失败", result?.message || "请稍后重试", "danger");
      return;
    }
    closeModal("attestationDialog");
    await loadCompanyCoverageGaps();
    showToast("已提交无业务说明", "等待管理员审核，通过后仅关闭断档提醒", "success");
  });

  loadCompanyPeerSelect();
  loadCompanyManualRecords();
  loadCompanyAccounts();
  loadImportBatches();

  $$("[data-close]").forEach((el) => el.addEventListener("click", () => closeModal(el.dataset.close)));
  $$(".modal-backdrop").forEach((bd) => bd.addEventListener("click", (e) => { if (e.target === bd) bd.classList.remove("open"); }));
  document.addEventListener("keydown", (e) => { if (e.key === "Escape") $$(".modal-backdrop.open").forEach((m) => m.classList.remove("open")); });

  $$('[data-open-upload]').forEach((button) => button.addEventListener("click", () => showView("upload")));

  // ── 流水导入：多步流程（选文件 → 解析 → 分 sheet 审核 → 确认） ──
  $("#accountSelect")?.addEventListener("change", updateParseButton);
  $("#fileInput")?.addEventListener("change", (event) => acceptFile(event.target.files[0]));
  $("#removeFile")?.addEventListener("click", () => {
    state.selectedFile = null;
    $("#fileInput").value = "";
    $("#filePreview").hidden = true;
    $("#dropzone").hidden = false;
    $("#parseResult").hidden = true;
    updateParseButton();
  });
  const dropzone = $("#dropzone");
  if (dropzone) {
    ["dragenter", "dragover"].forEach((type) => dropzone.addEventListener(type, (event) => { event.preventDefault(); dropzone.classList.add("dragover"); }));
    ["dragleave", "drop"].forEach((type) => dropzone.addEventListener(type, (event) => { event.preventDefault(); dropzone.classList.remove("dragover"); }));
    dropzone.addEventListener("click", () => $("#fileInput").click());
    dropzone.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); $("#fileInput").click(); } });
    dropzone.addEventListener("drop", (event) => acceptFile(event.dataTransfer.files[0]));
  }
  $("#uploadForm")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const stage = $("#parseButton").dataset.stage;
    if (stage === "confirm" || stage === "done") {
      const result = state.parseResult;
      const parsed = result && ["parsed", "duplicate"].includes(result.status);
      if (parsed && stage === "confirm") {
        await confirmImport();
      } else if (parsed) {
        resetUpload();
        showView("upload");
        await loadImportBatches();
      } else {
        submitImportException();
      }
      return;
    }
    $("#parseButton").disabled = true;
    $("#parseButton span").textContent = "正在识别表头...";
    await parseFile();
  });

  // ── 导入批次详情弹窗 ──
  $("#importRows")?.addEventListener("click", (event) => {
    const btn = event.target.closest("[data-batch-view]");
    if (!btn) return;
    const tr = btn.closest("tr");
    const batch = (state.batches || []).find((b) => String(b.id) === String(tr?.dataset.batchId));
    if (!batch) return;
    $("#mb-sub").textContent = `IMP-${String(batch.id).padStart(6, "0")} · ${batch.original_filename || ""}`;
    $("#mb-id").textContent = `IMP-${String(batch.id).padStart(6, "0")}`;
    $("#mb-bank").textContent = batch.bank_name || "—";
    $("#mb-period").textContent = batch.period_start && batch.period_end ? `${batch.period_start} ~ ${batch.period_end}` : "—";
    $("#mb-count").textContent = `${batch.confirmed_transactions ?? 0} 条`;
    $("#mb-cover").textContent = batch.status === "exception" ? "未导入" : batch.pending_sheets > 0 ? `${batch.pending_sheets} 个待确认` : batch.confirmed_sheets > 0 ? "已确认" : "待处理";
    $("#mb-parse").textContent = batch.exception_sheets > 0 ? `${batch.exception_sheets} 个异常` : batch.ignored_sheets > 0 ? `${batch.ignored_sheets} 个忽略` : "解析成功";
    $("#mb-time").textContent = String(batch.created_at || "").slice(0, 16).replace("T", " ");
    openModal("modal-batch");
  });

  // ── 手工记录：提交 + 撤回 ──
  $("#manualEntryForm")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const form = event.currentTarget;
    const data = new FormData(form);
    const counterpartyId = Number(data.get("counterparty"));
    if (!counterpartyId) {
      showToast("请选择对方公司", "手工记录仅登记集团内公司往来", "warn");
      return;
    }
    const source = String(data.get("sourceAccount") || "");
    const funding = source.includes("个人过账") ? "personal_transit" : (source ? "approved_bank_account" : "other");
    const response = await fetch("/api/company/manual-records", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        counterparty_company_id: counterpartyId,
        occurred_at: String(data.get("transactionDate") || ""),
        direction: String(data.get("direction")) === "收款" ? "incoming" : "outgoing",
        amount: String(data.get("amount") || ""),
        currency: "CNY",
        funding_source: funding === "approved_bank_account" ? "other" : funding,
        requested_subject: SUBJECT_LABEL_CODE[String(data.get("subject") || "")] || "other_receivable",
        request_key: `manual-${Date.now()}`,
        summary: String(data.get("summary") || "").trim(),
        reason: String(data.get("remark") || "").trim(),
      }),
    }).catch(() => null);
    const result = await response?.json().catch(() => ({}));
    if (!response?.ok) {
      if (!toastIfLocked(result)) showToast("提交失败", result?.message || "请稍后重试", "danger");
      return;
    }
    form.reset();
    await loadCompanyManualRecords();
    showToast("手工记录已提交", "管理员复核前不会纳入公司间往来计算", "success");
  });

  let manualWithdrawRow = null;
  $("#manualRecordRows")?.addEventListener("click", (event) => {
    const btn = event.target.closest("button[data-action='withdraw']");
    if (!btn) return;
    const tr = btn.closest("tr");
    manualWithdrawRow = tr;
    const cells = tr.cells;
    $("#wd-date").textContent = cells[0]?.textContent.trim() || "—";
    $("#wd-peer").textContent = cells[2]?.textContent.trim() || "—";
    $("#wd-amount").textContent = cells[4]?.textContent.trim() || "—";
    $("#wd-summary").textContent = cells[5]?.textContent.trim() || "—";
    openModal("withdraw-modal");
  });
  $("#wd-confirm")?.addEventListener("click", () => {
    closeModal("withdraw-modal");
    showToast("已提交记录不可在公司端撤回", "请联系管理员退回后重新登记", "warn");
  });

  // ── 转账往来（方案 A）──
  initTransfers();

  // ── 往来确认 + 工作台权威待办 ──
  initReconcile();
  (async () => {
    await loadCompanyWorkspace();
    await loadCompanyCoverageGaps();
    await renderReconcileMatchStack();
  })();

  // ── 通知 ──
  initNotifications();

  // ── 银行账户 ──
  $("#openAccountDialog")?.addEventListener("click", () => openModal("accountDialog"));
  $("#account-tbody")?.addEventListener("click", (event) => {
    const btn = event.target.closest("[data-account-view]");
    if (!btn) return;
    const tr = btn.closest("tr");
    const account = (state.accounts || []).find((a) => String(a.id) === String(tr?.dataset.accountId));
    if (!account) return;
    openAccountDetail(account);
  });
  $("#accountForm")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const form = event.currentTarget;
    const data = new FormData(form);
    const response = await fetch("/api/company/accounts", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        bank_name: String(data.get("bank") || "").trim(),
        account_type: String(data.get("type") || ""),
        account_number: String(data.get("accountNumber") || ""),
        start_date: String(data.get("startDate") || ""),
      }),
    }).catch(() => null);
    if (response?.status === 401 || response?.status === 403) {
      window.location.href = "index.html";
      return;
    }
    const result = await response?.json().catch(() => ({}));
    if (!response || !response.ok) {
      showToast("账户登记失败", result?.message || "请稍后重试", "danger");
      return;
    }
    closeModal("accountDialog");
    form.reset();
    await loadCompanyAccounts();
    showToast("银行账户已提交登记", "复核通过前不能上传流水，也不参与账户识别和覆盖计算", "success");
  });
}

if (portal === "entry") {
  initEntry();
} else {
  initAuthGuard().then((allowed) => {
    if (!allowed) return;
    initShell();
    initFlowTools();
    if (portal === "admin") {
      initPairQueries();
      initAdmin();
    } else {
      initCompany();
    }
  });
}
