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: "结账与期初", reminders: "提醒管理" }
  : { workspace: "工作台", upload: "流水导入", manual: "手工记录", flows: "流水管理", reconcile: "往来确认", accounts: "银行账户", notifications: "通知" };

const storageKeys = {
  manual: "ledger-demo-manual-records",
};

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

const state = {
  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-heading > *",
    ".metric-card",
    ".period-ribbon",
    ".company-alert",
    ".admin-dashboard-grid > *",
    ".company-dashboard-grid > *",
    ".company-ledger-panel",
    ".query-band",
    ".filter-bar",
    ".filter-grid",
    ".pair-report",
    ".panel",
    ".work-progress",
    ".reconcile-summary",
    ".account-directory > article",
  ];
  const elements = [...new Set(selectors.flatMap((selector) => [...view.querySelectorAll(selector)]))]
    .filter((element) => !element.closest(".panel") || element.matches(".panel"));

  elements.forEach((element, index) => {
    element.getAnimations().forEach((animation) => animation.cancel());
    // metric-card 的 3D 倾斜与悬停倾斜由 CSS 控制，入场动画只做淡入，
    // 否则 fill:both 的 translateY(0) 会覆盖 CSS 的 transform，导致倾斜失效。
    const keyframes = element.classList.contains("metric-card")
      ? [{ opacity: 0 }, { opacity: 1 }]
      : [
          { 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 });

  $$(".nav-item", $("#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" },
    );
  });

  $$(".company-ledger").forEach((ledger) => {
    ledger.addEventListener("toggle", () => {
      if (!ledger.open) return;
      $(".ledger-breakdown", ledger)?.animate(
        [{ opacity: 0, transform: "translateY(-8px)" }, { opacity: 1, transform: "translateY(0)" }],
        { duration: 260, easing: "cubic-bezier(.22,1,.36,1)" },
      );
    });
  });
}

function readStoredRecords(key) {
  try {
    const value = JSON.parse(localStorage.getItem(key) || "[]");
    return Array.isArray(value) ? value : [];
  } catch {
    return [];
  }
}

function writeStoredRecords(key, records) {
  try {
    localStorage.setItem(key, JSON.stringify(records));
    return true;
  } catch {
    showToast("本机演示数据保存失败", "请检查浏览器是否允许本地存储");
    return false;
  }
}

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 accountTail(masked) {
  return String(masked || "").replace(/^\*+/, "");
}

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

function showToast(title, detail = "") {
  const region = $("#toastRegion");
  if (!region) return;
  const toast = document.createElement("div");
  toast.className = "toast";
  const heading = document.createElement("strong");
  heading.textContent = title;
  toast.append(heading);
  if (detail) {
    const description = document.createElement("small");
    description.textContent = detail;
    toast.append(description);
  }
  region.append(toast);
  window.setTimeout(() => toast.remove(), 3400);
}

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));
  $$(".nav-item[data-view]").forEach((item) => {
    const active = item.dataset.view === view;
    item.classList.toggle("is-active", active);
    if (active) item.setAttribute("aria-current", "page");
    else item.removeAttribute("aria-current");
  });
  const title = $("#currentViewName");
  if (title) 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" });
}

const detailContent = {
  "gap-a": { tag: ["danger", "高风险"], title: "A公司 · 工行账户断档", desc: "工商银行 9481 缺少 07.01—07.21 流水，已影响 7 月结账。", fields: [["公司", "A公司"], ["账户", "工商银行 · 9481"], ["缺口期间", "2026.07.01—07.21 · 21 天"], ["影响", "7 月结账 · 账户覆盖 · 双边匹配"], ["当前状态", "已逾期 2 天"]], tip: "建议先向 A公司出纳发送补传提醒，补齐后在审核中心复核覆盖区间。", action: ["去审核中心处理", "audit"] },
  "match-bd": { tag: ["warning", "中风险"], title: "B公司 ↔ D公司 · 单边待匹配", desc: "D公司侧流水已到，B公司侧尚未确认，合计 312.00 万元。", fields: [["本方", "B公司"], ["对方", "D公司"], ["笔数 / 金额", "6 笔 · 312.00 万元"], ["候选情况", "金额与日期存在 2 个候选"], ["当前状态", "今日新增"]], tip: "建议按账号优先核对候选流水，金额与日期相同者先确认。", action: ["去审核中心匹配", "audit"] },
  "calib-f": { tag: ["warning", "中风险"], title: "F公司 · 起算区间待校准", desc: "01.01—01.16 无银行流水覆盖，公司已提交无业务说明。", fields: [["公司", "F公司"], ["账户", "农业银行 · 3650"], ["无覆盖期间", "2026.01.01—01.16"], ["现有依据", "公司已提交无业务说明"], ["当前状态", "待公司确认"]], tip: "无业务说明属于审计证据，复核通过后该区间标记为已校准，不生成银行流水。", action: ["去审核中心复核", "audit"] },
  "task-upload": { tag: ["danger", "最紧急"], title: "补传工商银行流水", desc: "账户尾号 9481 缺少 07.01—07.21 流水，已逾期 2 天。", fields: [["账户", "工商银行 · 9481"], ["缺少期间", "2026.07.01—07.21 · 21 天"], ["影响", "7 月账户覆盖与双边匹配"], ["截止", "08.05 集团结账日前"]], tip: "从工商银行网银导出 7 月流水后直接上传，系统会自动识别表头并重新计算匹配。", action: ["去上传流水", "upload"] },
  "task-match": { tag: ["warning", "待确认"], title: "确认 1 笔单边流水", desc: "07.18 转出 280.00 万元，系统找到 2 个对方候选。", fields: [["对方", "B公司"], ["日期 / 金额", "07.18 · 280.00 万元"], ["候选", "工商银行 9481（推荐）· 建设银行 2046"], ["核对点", "摘要与账号是否一致"]], tip: "系统推荐账号一致的候选，请核对回单后再确认。", action: ["去往来确认", "reconcile"] },
  "task-subject": { tag: ["warning", "待确认"], title: "确认往来科目", desc: "06.27 转出 600.00 万元，规则无法区分应收与其他应收。", fields: [["对方", "C公司"], ["日期 / 金额", "06.27 · 600.00 万元"], ["待确认", "应收 或 其他应收"], ["摘要", "资金调拨"]], tip: "科目只按确定性规则建议，拿不准时选“其他应收”并在说明里注明依据。", action: ["去确认科目", "reconcile"] },
  "task-notice": { tag: ["neutral", "提醒"], title: "阅读总账提醒", desc: "管理员要求 08.08 前完成 7 月银行流水确认。", fields: [["来自", "系统管理员 · 今天 09:30"], ["处理期限", "2026.08.08"], ["关联事项", "断档补传 · 2 项待确认往来"]], tip: "完成补传和两项确认后，再提交公司确认即可。", action: ["查看通知", "notifications"] },
  "acct-citic": { tag: ["success", "连续"], title: "中信银行 · 5316", desc: "基本户 · 起算日以来流水全部连续。", fields: [["账户类型", "基本户"], ["启用日期", "2026.01.01"], ["流水覆盖", "01.01—07.31 · 7 个月连续"], ["最近导入", "今天 09:42"]], tip: "该账户无需处理，8 月流水到期后正常上传即可。", action: ["查看银行账户", "accounts"] },
  "acct-abc": { tag: ["success", "连续"], title: "农业银行 · 3650", desc: "一般户 · 起算日以来流水全部连续。", fields: [["账户类型", "一般户"], ["启用日期", "2026.01.01"], ["流水覆盖", "01.01—07.31 · 7 个月连续"], ["最近导入", "08.01 08:01"]], tip: "该账户无需处理，8 月流水到期后正常上传即可。", action: ["查看银行账户", "accounts"] },
  "acct-icbc": { tag: ["danger", "断档"], title: "工商银行 · 9481", desc: "一般户 · 缺少 07.01—07.21 流水，已逾期 2 天。", fields: [["账户类型", "一般户"], ["缺口期间", "2026.07.01—07.21 · 21 天"], ["影响", "7 月结账与双边匹配"], ["最近导入", "07.31 16:18（6 月批次）"]], tip: "这是最紧急的一项：补齐后系统会自动重算覆盖与匹配。", action: ["去补传流水", "upload"] },
  "acct-ccb": { tag: ["success", "连续"], title: "建设银行 · 0845", desc: "一般户 · 起算日以来流水全部连续。", fields: [["账户类型", "一般户"], ["启用日期", "2026.01.01"], ["流水覆盖", "01.01—07.31 · 7 个月连续"], ["最近导入", "08.01 10:03"]], tip: "该账户无需处理，8 月流水到期后正常上传即可。", action: ["查看银行账户", "accounts"] },
};

function initDetailDrawer() {
  const triggers = $$("[data-detail]");
  if (!triggers.length) return;
  const drawer = document.createElement("aside");
  drawer.className = "detail-drawer";
  drawer.id = "detailDrawer";
  drawer.setAttribute("aria-label", "事项详情");
  drawer.innerHTML = `<header><div><span class="status" id="detailTag"></span><h2 id="detailTitle"></h2><p class="detail-desc" id="detailDesc"></p></div><button type="button" class="icon-button" data-close-detail aria-label="关闭详情" title="关闭详情"><svg><use href="icons.svg#x"/></svg></button></header><dl class="detail-fields" id="detailFields"></dl><div class="detail-tip" id="detailTip"></div><footer><button type="button" class="button secondary" data-close-detail>关闭</button><button type="button" class="button primary" id="detailAction"></button></footer>`;
  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 = `status ${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.map(([label, value]) => {
      const row = document.createElement("div");
      const dt = document.createElement("dt"); dt.textContent = label;
      const dd = document.createElement("dd"); dd.textContent = value;
      row.append(dt, dd);
      return row;
    }));
    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-brand"), $(".entry-statement"), ...$$(".entry-facts > div"), form].filter(Boolean).forEach((element, index) => {
      element.animate(
        [{ opacity: 0, transform: "translateY(16px)" }, { opacity: 1, transform: "translateY(0)" }],
        { duration: 520, delay: index * 65, easing: "cubic-bezier(.22,1,.36,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;

  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;
    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("两次输入的新密码不一致。");
        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 || "修改密码失败，请稍后重试。");
        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 || "登录服务暂时不可用，请稍后重试。");
      return;
    }
    if (result.must_change_password) {
      pendingRole = role;
      changeSection.hidden = false;
      action.textContent = "设置新密码并进入";
      $('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) {
  // The company portal always shows the session-bound company, never a
  // hard-coded one.
  if (portal !== "company" || !me?.company_name) return;
  const context = $(".company-context");
  if (context) {
    const mark = $("span", context);
    if (mark) mark.textContent = me.company_name.slice(0, 1);
    const name = $("strong", context);
    if (name) name.textContent = me.company_name;
  }
}

function initShell() {
  $$(".page-heading").forEach((heading) => {
    if (heading.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>';
    heading.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 ? "关闭导航" : "打开导航");
    });
  });

  $$(".nav-item").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", () => showView(button.dataset.view)));
  $$("[data-view-link]").forEach((button) => button.addEventListener("click", () => showView(button.dataset.viewLink)));
  $$('a.nav-item[href="index.html"]').forEach((link) => link.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);
  });
  $(".nav-item[data-view].is-active")?.setAttribute("aria-current", "page");

  $("#globalSearch")?.addEventListener("input", (event) => {
    const view = $(`.app-view[data-page="${state.currentView}"]`);
    const query = event.target.value.trim().toLowerCase();
    $$(".data-table tbody tr, .company-ledger, .notification-list article, .account-directory article", view).forEach((item) => {
      item.hidden = query ? !item.textContent.toLowerCase().includes(query) : false;
    });
  });

  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 pairData(from, to, endDate) {
  const companies = ["A公司", "B公司", "C公司", "D公司", "E公司", "F公司"];
  const fromIndex = companies.indexOf(from) + 1;
  const toIndex = companies.indexOf(to) + 1;
  const low = Math.min(fromIndex, toIndex);
  const high = Math.max(fromIndex, toIndex);
  const seed = low * 13 + high * 7;
  const canonical = {
    receivable: 280 + seed * 18,
    otherReceivable: 120 + seed * 9,
    payable: 160 + ((seed * 11) % 720),
    otherPayable: 80 + ((seed * 5) % 360),
  };
  const reversed = fromIndex > toIndex;
  const receivable = reversed ? canonical.payable : canonical.receivable;
  const otherReceivable = reversed ? canonical.otherPayable : canonical.otherReceivable;
  const payable = reversed ? canonical.receivable : canonical.payable;
  const otherPayable = reversed ? canonical.otherReceivable : canonical.otherPayable;
  const debit = receivable + otherReceivable;
  const credit = payable + otherPayable;
  const opening = ((fromIndex + toIndex) % 3) * 60 * (reversed ? -1 : 1);
  const final = opening + debit - credit;
  const end = new Date(`${endDate}T12:00:00`);
  const dateBefore = (days) => {
    const date = new Date(end);
    date.setDate(date.getDate() - days);
    return date.toISOString().slice(0, 10);
  };
  return {
    opening, debit, credit, final,
    totals: { 应收: receivable, 其他应收: otherReceivable, 应付: payable, 其他应付: otherPayable },
    rows: [
      [dateBefore(2), "转出", "应收", "中信 · 5316", `${to} · 9481`, "往来款", "双边匹配", receivable],
      [dateBefore(9), "转出", "其他应收", "建行 · 0845", `${to} · 2046`, "资金调拨", "双边匹配", otherReceivable],
      [dateBefore(17), "转入", "应付", "中信 · 5316", `${to} · 9481`, "归还往来款", "双边匹配", payable],
      [dateBefore(25), "转入", "其他应付", "农行 · 3650", `${to} · 6120`, "临时往来", "单边待核", otherPayable],
    ],
  };
}

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.options].some((option) => option.value === from)) fromSelect.value = from;
    if ([...toSelect.options].some((option) => option.value === to)) toSelect.value = to;
    const endInput = $('[name="end"]', form);
    if (endInput) endInput.value = endDate;
  });
  if (!$("#pairReport")) return;
  const data = pairData(from, to, endDate);
  const format = (value) => value.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  $("#pairPeriod").textContent = `统计口径 2026.01.01—${endDate}`;
  $("#pairOpening").textContent = format(data.opening);
  $("#pairDebit").textContent = format(data.debit);
  $("#pairCredit").textContent = format(data.credit);
  $("#pairFinal").innerHTML = `${data.final >= 0 ? "应收" : "应付"} ${format(Math.abs(data.final))}<small>万元</small>`;
  $("#pairReviewStatus").textContent = "含 1 笔待审核";
  const quickResult = $(".quick-result");
  if (quickResult) {
    $("span", quickResult).textContent = `${from}对${to}`;
    $("strong", quickResult).innerHTML = `${data.final >= 0 ? "应收" : "应付"} ${format(Math.abs(data.final))}<small>万元</small>`;
    $("p", quickResult).textContent = `${data.rows.length - 1} 笔已确认 · 1 笔待审核`;
  }
  $('[data-subject-total="all"]').textContent = `${data.rows.length} 笔`;
  Object.entries(data.totals).forEach(([subject, value]) => { $(`[data-subject-total="${subject}"]`).textContent = format(value); });
  $("#pairTransactions tbody").innerHTML = data.rows.map(([date, direction, subject, ownAccount, counterparty, summary, match, amount]) => `<tr data-subject="${subject}"><td>${date}</td><td>${direction}</td><td>${subject}</td><td>${ownAccount}</td><td>${counterparty}</td><td>${summary}</td><td><span class="status ${match === "双边匹配" ? "success" : "warning"}">${match}</span></td><td class="number">${format(amount)}</td></tr>`).join("");
}

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";
      if (from === to) {
        showToast("请选择两个不同的公司", "同公司账户调拨不进入公司间往来查询");
        return;
      }
      setPair(from, to, endDate);
      showView("pair");
      showToast("查询结果已更新", `${from} 与 ${to} · 截至 ${endDate}`);
    });
  });

  $$("[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("is-active", active);
      item.setAttribute("aria-pressed", String(active));
    });
    $$("#pairTransactions tbody tr").forEach((row) => { row.hidden = subject !== "all" && row.dataset.subject !== subject; });
  }));
}

function renderCompanyManualRecords() {
  const tbody = $("#manualRecordRows");
  if (!tbody) return;
  $$('[data-stored-record]', tbody).forEach((row) => row.remove());
  const records = readStoredRecords(storageKeys.manual).filter((record) => record.company === "A公司");
  [...records].reverse().forEach((record) => {
    const row = document.createElement("tr");
    row.dataset.storedRecord = record.id;
    const identity = document.createElement("td");
    const id = document.createElement("strong"); id.textContent = record.id;
    const date = document.createElement("small"); date.textContent = record.transactionDate;
    identity.append(id, date);
    const direction = document.createElement("td");
    const directionName = document.createElement("strong"); directionName.textContent = record.direction;
    const subject = document.createElement("small"); subject.textContent = record.subject;
    direction.append(directionName, subject);
    const source = document.createElement("td"); source.textContent = record.sourceAccount;
    const counterparty = document.createElement("td");
    const counterpartyName = document.createElement("strong"); counterpartyName.textContent = record.counterparty;
    const counterpartyType = document.createElement("small"); counterpartyType.textContent = record.counterpartyType;
    counterparty.append(counterpartyName, counterpartyType);
    const summary = document.createElement("td"); summary.textContent = record.summary;
    const amount = document.createElement("td"); amount.className = "number"; amount.textContent = formatCurrency(record.amount);
    const statusCell = document.createElement("td");
    const status = recordStatus(record.status);
    const badge = document.createElement("span"); badge.className = `status ${status.className}`; badge.textContent = status.label;
    statusCell.append(badge);
    row.append(identity, direction, source, counterparty, summary, amount, statusCell);
    tbody.append(row);
  });
  const pending = records.filter((record) => record.status === "待总账复核").length + 1;
  if ($("#manualPendingStatus")) $("#manualPendingStatus").textContent = `${pending} 笔待总账复核`;
}

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 renderCompanyAccounts(accounts) {
  const directory = $("#accountDirectory");
  if (directory) {
    directory.replaceChildren(...accounts.map((account) => {
      const article = document.createElement("article");
      const header = document.createElement("header");
      const mark = document.createElement("span"); mark.className = "bank-mark"; mark.textContent = account.bank_name.slice(0, 1);
      const identity = document.createElement("div");
      const name = document.createElement("strong"); name.textContent = account.bank_name;
      const meta = document.createElement("small"); meta.textContent = `${account.account_type} · 尾号 ${accountTail(account.account_number_masked)}`;
      identity.append(name, meta);
      const status = recordStatus(accountStatusLabel(account.status));
      const badge = document.createElement("em"); badge.className = `status ${status.className}`; badge.textContent = status.label;
      header.append(mark, identity, badge);
      const details = document.createElement("dl");
      const rows = [
        ["申请启用", account.effective_from || "待审核确定"],
        ["流水覆盖", account.usable ? "尚未上传" : "不参与计算"],
        ["提交时间", String(account.created_at || "").slice(0, 10) || "—"],
      ];
      if (account.status === "returned" && account.review_reason) rows.push(["退回原因", account.review_reason]);
      if (account.status === "disabled" && account.effective_to) rows.push(["停用日期", account.effective_to]);
      rows.forEach(([term, value]) => {
        const wrapper = document.createElement("div");
        const dt = document.createElement("dt"); dt.textContent = term;
        const dd = document.createElement("dd"); dd.textContent = value;
        wrapper.append(dt, dd); details.append(wrapper);
      });
      article.append(header, details);
      return article;
    }));
  }
  fillAccountSelects(accounts);
}

async function loadCompanyAccounts() {
  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) return;
  const result = await response.json().catch(() => null);
  if (result?.accounts) renderCompanyAccounts(result.accounts);
}

function appendAdminReviewRow(record, kind) {
  const tbody = $("#auditRows");
  if (!tbody) return;
  const isAccount = kind === "account";
  // Account rows come from the server (full number visible only in this
  // authorized admin view); manual records are still browser-local demo data.
  const statusLabel = isAccount ? accountStatusLabel(record.status) : record.status;
  const row = document.createElement("tr");
  row.dataset.storedReview = record.id;
  row.dataset.recordId = record.id;
  row.dataset.recordKind = kind;
  if (isAccount) row.dataset.accountId = record.id;
  row.dataset.auditType = isAccount ? "账户" : "手工";
  row.dataset.company = isAccount ? record.company_name : record.company;
  row.dataset.evidence = isAccount
    ? "公司提交资料、开户行、账号、账户类型与启用日期"
    : "公司手工记录、关联银行流水号、证明附件与提交说明";
  if (statusLabel !== "待复核" && statusLabel !== "待总账复核") row.dataset.resolved = "true";

  const riskCell = document.createElement("td");
  const risk = document.createElement("span"); risk.className = "task-level warning"; risk.textContent = "中"; riskCell.append(risk);
  const identityCell = document.createElement("td");
  const identity = document.createElement("strong");
  const detail = document.createElement("small");
  const typeCell = document.createElement("td");
  const periodCell = document.createElement("td");
  const impactCell = document.createElement("td");
  if (isAccount) {
    identity.textContent = `${record.company_name} · ${record.bank_name} ${String(record.account_number).slice(-4)}`;
    detail.textContent = `${record.account_type} · 完整账号 ${record.account_number}`;
    typeCell.textContent = "账户登记";
    periodCell.textContent = record.effective_from || "待审核确定";
    impactCell.textContent = "账户识别与流水上传";
  } else {
    identity.textContent = `${record.company} · ${record.id}`;
    detail.textContent = `${record.direction} ${formatCurrency(record.amount)} 元 · ${record.counterparty} · ${record.subject}`;
    typeCell.textContent = "手工记录";
    periodCell.textContent = record.transactionDate;
    impactCell.textContent = `待确认往来 ${(Number(record.amount) / 10000).toFixed(2)} 万元`;
  }
  identityCell.append(identity, detail);
  const statusCell = document.createElement("td");
  const status = recordStatus(statusLabel);
  const badge = document.createElement("span"); badge.className = `status ${status.className}`; badge.textContent = status.label; statusCell.append(badge);
  const actionCell = document.createElement("td");
  const action = document.createElement("button"); action.className = "text-button"; action.dataset.auditAction = "";
  if (isAccount) row.dataset.accountStatus = record.status;
  action.textContent = row.dataset.resolved
    ? (isAccount && record.status === "active" ? "管理" : "查看记录")
    : "复核";
  if (row.dataset.resolved) {
    const decisionLabels = { active: "复核通过并启用账户", returned: "退回公司修改", disabled: "停用并驳回" };
    const decisionLabel = isAccount ? decisionLabels[record.status] || statusLabel : record.decision || record.status;
    const reasonText = (isAccount ? record.review_reason : record.reviewReason) || "已留痕";
    action.dataset.record = `${decisionLabel} · ${reasonText}`;
    action.dataset.decision = decisionLabel;
    action.dataset.reason = reasonText;
    action.dataset.processedAt = (isAccount ? record.reviewed_at : record.reviewedAt) || "时间未记录";
  }
  actionCell.append(action);
  row.append(riskCell, identityCell, typeCell, periodCell, impactCell, statusCell, actionCell);
  tbody.append(row);
}

async function renderAdminAccountReviews() {
  const tbody = $("#auditRows");
  if (!tbody) return;
  $$('[data-stored-review][data-record-kind="account"]', tbody).forEach((row) => row.remove());
  const response = await fetch("/api/admin/accounts").catch(() => null);
  if (!response?.ok) return;
  const result = await response.json().catch(() => null);
  (result?.accounts || []).forEach((account) => appendAdminReviewRow(account, "account"));
  updateAuditCounts();
}

function renderStoredAdminReviews() {
  if (!$("#auditRows")) return;
  $$('[data-stored-review]', $("#auditRows")).forEach((row) => row.remove());
  readStoredRecords(storageKeys.manual).forEach((record) => appendAdminReviewRow(record, "manual"));
  renderAdminAccountReviews();
}

function updateStoredReview(kind, id, status, decision, reviewReason, reviewedAt) {
  if (kind !== "manual" || !id) return;
  const records = readStoredRecords(storageKeys.manual);
  const record = records.find((item) => item.id === id);
  if (!record) return;
  Object.assign(record, { status, decision, reviewReason, reviewedAt });
  writeStoredRecords(storageKeys.manual, records);
}

function updateAuditCounts() {
  const rows = $$("#auditRows tr");
  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;
    button.textContent = `${button.dataset.label} ${count}`;
  });
  const badge = $('.nav-item[data-view="audit"] b');
  if (badge) badge.textContent = unresolved.length;
}

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;
  tbody.replaceChildren(...companies.map((company) => {
    const row = document.createElement("tr");
    const nameCell = document.createElement("td");
    const name = document.createElement("strong"); name.textContent = company.name;
    const code = document.createElement("small"); code.textContent = `COMP-${String(company.id).padStart(3, "0")}`;
    nameCell.append(name, code);
    const credit = document.createElement("td"); credit.textContent = company.credit_code || "待补充";
    const accounts = document.createElement("td"); accounts.textContent = `${company.account_count ?? 0} 个`;
    const usernames = document.createElement("td"); 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="status ${badge.className}">${badge.label}</span>`;
    const actionCell = document.createElement("td");
    actionCell.innerHTML = `<button class="text-button" data-toast="已打开 ${company.name} 主档">管理</button>`;
    row.append(nameCell, credit, accounts, usernames, cashier, statusCell, actionCell);
    return row;
  }));
}

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) {
  // 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 });
  setSelectOptions($('#reminderForm [name="company"]'), names, { keepFirst: true });
  setSelectOptions($('#openingDialog [name="from"]'), names);
  setSelectOptions($('#openingDialog [name="to"]'), names);
}

async function loadAdminCompanies() {
  const response = await fetch("/api/admin/companies").catch(() => null);
  if (!response?.ok) return;
  const result = await response.json().catch(() => null);
  const companies = result?.companies || [];
  renderAdminCompanyTable(companies);
  fillCompanySelects(companies.map((company) => company.name));
}

function initAdmin() {
  renderStoredAdminReviews();
  updateAuditCounts();
  loadAdminCompanies();
  const companySearch = $('[data-filter-target="companyLedgers"]');
  companySearch?.addEventListener("input", () => {
    const query = companySearch.value.trim().toLowerCase();
    $$("#companyLedgers .company-ledger").forEach((item) => { item.hidden = query ? !item.textContent.toLowerCase().includes(query) : false; });
  });

  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("is-active", active);
      item.setAttribute("aria-pressed", String(active));
    });
    filterAuditRows();
  }));
  $("#auditCompany")?.addEventListener("change", filterAuditRows);

  const auditDialog = $("#auditDialog");
  $$('[data-close-audit]').forEach((button) => button.addEventListener("click", () => auditDialog.close()));
  // Delegated: account review rows arrive asynchronously from the API.
  $("#auditRows")?.addEventListener("click", (event) => {
    const button = event.target.closest("[data-audit-action]");
    if (!button) return;
    const row = button.closest("tr");
    state.auditRow = row;
    const cells = $$('td', row);
    const form = $("#auditForm");
    const decision = $('[name="decision"]', form);
    const reason = $('[name="reason"]', form);
    const submit = $('button[type="submit"]', form);
    form.reset();
    const isActiveAccount = row.dataset.recordKind === "account" && row.dataset.accountStatus === "active";
    const decisions = row.dataset.recordKind === "account"
      ? (isActiveAccount ? ["停用并驳回"] : ["复核通过并启用账户", "退回公司修改", "停用并驳回"])
      : ["确认并纳入计算", "退回公司补充材料", "转为异常待后续处理"];
    decision.replaceChildren(new Option("请选择", ""), ...decisions.map((item) => new Option(item, item)));
    decision.disabled = false;
    reason.disabled = false;
    submit.hidden = false;
    $("#auditDialogTitle").textContent = `${cells[2].innerText.trim()} · ${cells[1].querySelector("strong").textContent}`;
    $("#auditDialogMeta").textContent = `${cells[3].innerText.trim()} · 影响 ${cells[4].innerText.trim()}`;
    const evidence = $("#auditEvidence");
    evidence.replaceChildren();
    const heading = document.createElement("strong"); heading.textContent = cells[1].querySelector("strong").textContent;
    const detail = document.createElement("small"); detail.textContent = cells[1].querySelector("small").textContent;
    const source = document.createElement("small"); source.textContent = `证据：${row.dataset.evidence || "银行原始行、导入批次、账户覆盖区间与公司说明"}`;
    evidence.append(heading, detail, source);
    if (button.dataset.record && !isActiveAccount) {
      decision.value = button.dataset.decision;
      reason.value = button.dataset.reason;
      decision.disabled = true;
      reason.disabled = true;
      submit.hidden = true;
      $("#auditDialogMeta").textContent = `已处理 · ${button.dataset.processedAt} · 系统管理员`;
      const record = document.createElement("small");
      record.textContent = `处理记录：${button.dataset.record}`;
      evidence.append(record);
    }
    auditDialog.showModal();
  });
  $("#auditForm")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    const row = state.auditRow;
    const decision = String(data.get("decision"));
    const reason = String(data.get("reason"));
    const approved = decision.includes("通过") || decision.includes("确认并纳入");
    const returned = decision.includes("退回");
    let storedStatus;
    let reviewedAccount = null;
    if (row.dataset.recordKind === "account" && row.dataset.accountId) {
      // Server-side review: the account only becomes usable after this succeeds.
      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) {
        showToast("审核结果提交失败", result?.message || "请稍后重试");
        return;
      }
      reviewedAccount = result.account;
      storedStatus = accountStatusLabel(result.account?.status);
    } else {
      storedStatus = approved
        ? (row.dataset.recordKind === "account" ? "已启用" : "已确认")
        : (returned ? "已退回" : "异常待处理");
      updateStoredReview(row.dataset.recordKind, row.dataset.recordId, storedStatus, decision, reason, new Date().toLocaleString("zh-CN", { hour12: false }));
    }
    const status = recordStatus(storedStatus);
    const statusCell = row.children[5];
    statusCell.innerHTML = `<span class="status ${status.className}">${status.label}</span>`;
    row.dataset.resolved = "true";
    const action = $("[data-audit-action]", row);
    if (reviewedAccount) {
      row.dataset.accountStatus = reviewedAccount.status;
      action.textContent = reviewedAccount.status === "active" ? "管理" : "查看记录";
    } else {
      action.textContent = "查看记录";
    }
    action.dataset.record = `${decision} · ${data.get("reason")}`;
    action.dataset.decision = decision;
    action.dataset.reason = data.get("reason");
    action.dataset.processedAt = new Date().toLocaleString("zh-CN", { hour12: false });
    updateAuditCounts();
    auditDialog.close();
    event.currentTarget.reset();
    showToast("审核结果已记录", approved ? "已通过的数据取得相应使用或核算资格" : "当前记录不参与自动核算");
  });

  const dialog = $("#companyDialog");
  $("#openCompanyDialog")?.addEventListener("click", () => dialog.showModal());
  $("#companyForm")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    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 || "请稍后重试");
      return;
    }
    const accountCreated = Boolean(result.username);
    dialog.close();
    event.currentTarget.reset();
    await loadAdminCompanies();
    showToast(
      accountCreated ? "公司与账号已创建" : "公司已创建",
      accountCreated ? `账号 ${result.username} 的初始密码已生成（仅此一次显示）：${result.initial_password}，首次登录必须修改` : "可稍后在账号管理中创建公司账号",
    );
  });

  $("#systemSettings")?.addEventListener("submit", (event) => {
    event.preventDefault();
    showToast("系统计算口径已保存", "正式系统将记录修改前后值与操作人");
  });
  $("#runClosingCheck")?.addEventListener("click", () => {
    const unresolved = $$(".audit-table tbody tr").filter((row) => row.dataset.resolved !== "true" && !$(".status.success", row)).length;
    if (unresolved) {
      showToast("结账检查未通过", `仍有 ${unresolved} 项审核事项，已打开审核中心`);
      showView("audit");
    } else {
      $$("#closingPanel .is-blocked").forEach((item) => {
        item.classList.remove("is-blocked");
        $("use", item).setAttribute("href", "icons.svg#circle-check");
        $("small", item).textContent = "检查已通过";
      });
      $("#closingDescription").textContent = "2026 年 7 月 · 全部前置检查已通过";
      $("#closingStatus").className = "status success";
      $("#closingStatus").textContent = "可结账";
      $("#executeClosing").disabled = false;
      $("#executeClosing").removeAttribute("title");
      showToast("结账检查通过", "执行结账按钮已解锁");
    }
  });
  const closingDialog = $("#closingDialog");
  $("#executeClosing")?.addEventListener("click", () => closingDialog.showModal());
  $$('[data-close-closing]').forEach((button) => button.addEventListener("click", () => closingDialog.close()));
  $("#closingForm")?.addEventListener("submit", (event) => {
    event.preventDefault();
    closingDialog.close();
    $("#closingDescription").textContent = "2026 年 7 月 · 已完成集团结账";
    $("#closingStatus").className = "status success";
    $("#closingStatus").textContent = "已结账";
    $("#closingHistory").textContent = `${new Date().toLocaleString("zh-CN", { hour12: false })} · 系统管理员执行 2026 年 7 月结账 · 已写入审计记录`;
    $("#runClosingCheck").disabled = true;
    $("#executeClosing").disabled = true;
    $("#executeClosing").textContent = "7 月已结账";
    showToast("2026 年 7 月已完成结账", "本期结果已锁定，后续补录将进入重开流程");
  });
  const openingDialog = $("#openingDialog");
  $("#openOpeningDialog")?.addEventListener("click", () => openingDialog.showModal());
  $$('[data-close-opening]').forEach((button) => button.addEventListener("click", () => openingDialog.close()));
  $("#openingForm")?.addEventListener("submit", (event) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    if (data.get("from") === data.get("to")) {
      showToast("本方与对方不能相同", "同公司账户余额不属于公司间期初");
      return;
    }
    const row = document.createElement("tr");
    [data.get("from"), data.get("to"), data.get("subject"), data.get("direction")].forEach((value) => {
      const cell = document.createElement("td"); cell.textContent = value; row.append(cell);
    });
    const amount = document.createElement("td"); amount.className = "number"; amount.textContent = Number(data.get("amount")).toLocaleString("zh-CN", {minimumFractionDigits:2}); row.append(amount);
    const date = document.createElement("td"); date.textContent = data.get("effectiveDate"); row.append(date);
    const status = document.createElement("td"); status.innerHTML = '<span class="status warning">待复核</span>'; row.append(status);
    $("#openingRows").append(row);
    openingDialog.close();
    event.currentTarget.reset();
    showToast("期初余额已提交复核", "正式系统将保留录入依据与操作人");
  });
  $("#reminderForm")?.addEventListener("submit", (event) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    const article = document.createElement("article");
    article.innerHTML = '<span class="notification-icon warning"><svg><use href="icons.svg#bell"/></svg></span>';
    const content = document.createElement("span");
    const heading = document.createElement("strong");
    heading.textContent = `${data.get("company")} · ${data.get("message")}`;
    const meta = document.createElement("small");
    meta.textContent = `手动提醒 · 截止 ${data.get("dueDate")} · 刚刚`;
    content.append(heading, meta);
    const status = document.createElement("em");
    status.className = "status warning";
    status.textContent = "未读";
    article.append(content, status);
    $("#adminReminderList").prepend(article);
    event.currentTarget.reset();
    showToast("提醒已发送", "对方将在公司业务端收到站内通知");
  });
}

function visibleRows(table) {
  return $$('tbody tr', table).filter((row) => !row.hidden);
}

function filterFlows() {
  const table = $("#flowTable");
  if (!table) return;
  const company = $("#flowCompany")?.value || "全部公司";
  const bank = $("#flowBank")?.value || "全部银行";
  const account = $("#flowAccount")?.value || "全部账户";
  const startDate = $("#flowStart")?.value || "0000-01-01";
  const endDate = $("#flowEnd")?.value || "9999-12-31";
  const keyword = $("#flowKeyword")?.value.trim().toLowerCase() || "";
  if (startDate > endDate) {
    showToast("日期范围无效", "开始日期不能晚于结束日期");
    return;
  }
  let count = 0;
  $$("tbody tr", table).forEach((row) => {
    const rowDate = $("td", row).textContent.trim().replaceAll(".", "-");
    const matchesCompany = company === "全部公司" || row.dataset.company === company;
    const matchesBank = bank === "全部银行" || row.dataset.bank === bank;
    const matchesAccount = account === "全部账户" || row.textContent.includes(account);
    const matchesDate = rowDate >= startDate && rowDate <= endDate;
    const matchesKeyword = !keyword || row.textContent.toLowerCase().includes(keyword);
    row.hidden = !(matchesCompany && matchesBank && matchesAccount && matchesDate && matchesKeyword);
    if (!row.hidden) count += 1;
  });
  $("#flowCount").textContent = count;
  showToast("查询完成", `当前显示 ${count} 笔流水`);
}

function exportFlows() {
  const table = $("#flowTable");
  const rows = visibleRows(table);
  const headers = ["交易日期", "公司", "银行及账号", "收付方向", "对方户名及账号", "摘要", "银行流水号", "归集状态", "金额", "导入批次", "源行定位"];
  const records = rows.map((row, index) => {
    const cells = $$('td', row).map((cell) => cell.innerText.replace(/\n/g, " ").trim());
    return [cells[0], portal === "company" ? "A公司" : cells[1].split(" ")[0], portal === "company" ? cells[1] : cells[1], ...cells.slice(2), `IMP-DEMO-${String(index + 1).padStart(3, "0")}`, `Sheet1!R${index + 8}`];
  });
  const csv = [headers, ...records].map((record) => record.map((value) => `"${String(value ?? "").replace(/"/g, '""')}"`).join(",")).join("\r\n");
  const link = document.createElement("a");
  link.href = URL.createObjectURL(new Blob(["\ufeff", csv], { type: "text/csv;charset=utf-8" }));
  link.download = `${portal === "admin" ? "集团" : "A公司"}银行流水_202607.csv`;
  link.click();
  URL.revokeObjectURL(link.href);
  showToast("导出已生成", `共 ${rows.length} 笔，已保留银行标识与源行定位`);
}

function initFlowTools() {
  $("#applyFlowFilters")?.addEventListener("click", filterFlows);
  $("#exportFlows")?.addEventListener("click", exportFlows);
}

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 文件");
    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("is-exception", !parsed);
  $("use", panel).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";
  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("article");
  item.className = "sheet-item";
  if (sheet.review_status === "pending") item.classList.add("is-pending");
  const meta = sheetStatusMeta(sheet);
  const head = document.createElement("div");
  head.className = "sheet-item-head";
  const name = document.createElement("strong");
  name.textContent = sheet.sheet_name;
  const badge = document.createElement("em");
  badge.className = `status ${meta.className}`;
  badge.textContent = meta.label;
  head.append(name, badge);
  const details = document.createElement("p");
  details.className = "sheet-item-meta";
  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}`;
  }
  const body = document.createElement("div");
  body.append(head, details);

  const actions = document.createElement("div");
  actions.className = "sheet-item-actions";
  if (sheet.review_status === "pending") {
    if (sheet.outcome === "parsed") {
      const confirmButton = document.createElement("button");
      confirmButton.type = "button";
      confirmButton.className = "text-button";
      confirmButton.textContent = "确认";
      confirmButton.addEventListener("click", () => sheetReviewAction(batchId, sheet.sheet_name, "confirm"));
      actions.append(confirmButton);
    }
    const ignoreButton = document.createElement("button");
    ignoreButton.type = "button";
    ignoreButton.className = "text-button";
    ignoreButton.textContent = "忽略";
    ignoreButton.addEventListener("click", () => sheetReviewAction(batchId, sheet.sheet_name, "ignore"));
    actions.append(ignoreButton);
  }
  item.append(body, actions);
  return item;
}

async function sheetReviewAction(batchId, sheetName, decision) {
  const payload = { sheets: [sheetName] };
  if (decision === "ignore") {
    const reason = (window.prompt("请填写忽略原因（必填）：", "") || "").trim();
    if (!reason) {
      showToast("忽略未提交", "必须填写忽略原因");
      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 || "请稍后重试");
    return;
  }
  showToast(decision === "confirm" ? "工作表已确认" : "工作表已忽略", `${sheetName}`);
  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) {
    $("#uploadDialog").close();
    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 || "请稍后重试");
    return;
  }
  button.disabled = false;
  if (Array.isArray(outcome.sheets)) renderSheetList(outcome.sheets, batchId);
  await loadImportBatches();
  $("#uploadDialog").close();
  showView("upload");
  showToast("流水已确认", `${outcome.updated.length} 个工作表已确认；未确认的工作表不参与计算`);
}

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

function renderBatchRow(batch) {
  const row = document.createElement("tr");
  const idCell = document.createElement("td");
  const id = document.createElement("strong");
  id.textContent = `IMP-${String(batch.id).padStart(6, "0")}`;
  const file = document.createElement("small");
  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.textContent = batch.period_start && batch.period_end
    ? `${batch.period_start}—${batch.period_end}`
    : "—";

  const count = document.createElement("td");
  count.className = "number";
  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="status ${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} 个忽略`);
  parseState.textContent = statusParts.length ? statusParts.join("、") : "解析成功";

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

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

async function loadImportBatches() {
  const tbody = $("#importRows");
  if (!tbody) return;
  const response = await fetch("/api/batches").catch(() => null);
  if (response?.status === 401 || response?.status === 403) {
    window.location.href = "index.html";
    return;
  }
  const result = await response?.json().catch(() => ({}));
  const batches = Array.isArray(result?.batches) ? result.batches : [];
  if (batches.length) tbody.replaceChildren(...batches.map(renderBatchRow));
}

function initCompany() {
  renderCompanyManualRecords();
  loadCompanyAccounts();
  loadImportBatches();
  const uploadDialog = $("#uploadDialog");
  $$('[data-open-upload]').forEach((button) => button.addEventListener("click", () => uploadDialog.showModal()));
  $$('[data-close-upload]').forEach((button) => button.addEventListener("click", () => uploadDialog.close()));
  uploadDialog?.addEventListener("close", resetUpload);
  $("#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("is-dragging"); }));
    ["dragleave", "drop"].forEach((type) => dropzone.addEventListener(type, (event) => { event.preventDefault(); dropzone.classList.remove("is-dragging"); }));
    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) {
        $("#uploadDialog").close();
        showView("upload");
        await loadImportBatches();
      } else {
        submitImportException();
      }
      return;
    }
    $("#parseButton").disabled = true;
    $("#parseButton span").textContent = "正在识别表头...";
    await parseFile();
  });

  function finishReview(button, disposition) {
    const item = button.closest("article");
    const type = item.dataset.reviewType;
    const selectedMatch = $('input[name="matchCandidate"]:checked', item)?.value;
    const selectedSubject = $("select", item)?.value;
    const detail = type === "match" ? (selectedMatch || "转为匹配异常") : selectedSubject;
    const history = $("#reviewHistory");
    const record = document.createElement("p");
    record.textContent = `${new Date().toLocaleString("zh-CN", { hour12: false })} · 牛女士 · ${disposition} · ${detail}`;
    history.append(record);
    history.hidden = false;
    item.remove();
    if (type === "match") $("#matchPendingCount").textContent = "0 笔";
    else $("#subjectPendingCount").textContent = "0 笔";
    const remaining = $$("#reviewList > article").length;
    const badge = $('.nav-item[data-view="reconcile"] b');
    if (badge) badge.textContent = remaining;
    $(`.cashier-tasks [data-task-type="${type}"]`)?.remove();
    const workspaceRemaining = $$(".cashier-tasks > article").length;
    $("#workspacePendingStatus").textContent = `${workspaceRemaining} 项待处理`;
    const workspaceBadge = $('.nav-item[data-view="workspace"] b');
    if (workspaceBadge) workspaceBadge.textContent = workspaceRemaining;
    showToast("处理结果已记录", "待办状态、操作人、时间和依据已同步更新");
  }
  $$('[data-resolve]').forEach((button) => button.addEventListener("click", () => finishReview(button, "确认")));
  $$('[data-reject]').forEach((button) => button.addEventListener("click", () => finishReview(button, "转异常")));
  $("#markAllRead")?.addEventListener("click", () => {
    $$("#companyNotifications .is-unread").forEach((item) => {
      item.classList.remove("is-unread");
      const status = $(".status", item);
      status.className = "status neutral";
      status.textContent = "已读";
    });
    showToast("通知已全部标为已读");
  });

  $("#manualEntryForm")?.addEventListener("submit", (event) => {
    event.preventDefault();
    const form = event.currentTarget;
    const data = new FormData(form);
    const evidence = data.get("evidence");
    if (evidence instanceof File && evidence.size > 20 * 1024 * 1024) {
      showToast("证明附件超过限制", "请选择不超过 20 MB 的文件");
      return;
    }
    const records = readStoredRecords(storageKeys.manual);
    const record = {
      id: `MR-${Date.now().toString().slice(-10)}`,
      company: "A公司",
      transactionDate: String(data.get("transactionDate")),
      direction: String(data.get("direction")),
      amount: Number(data.get("amount")),
      sourceAccount: String(data.get("sourceAccount")),
      counterpartyType: String(data.get("counterpartyType")),
      counterparty: String(data.get("counterparty")).trim(),
      counterpartyAccount: String(data.get("counterpartyAccount") || "").trim(),
      subject: String(data.get("subject")),
      summary: String(data.get("summary")).trim(),
      remark: String(data.get("remark")).trim(),
      bankReference: String(data.get("bankReference") || "").trim(),
      evidenceName: evidence instanceof File ? evidence.name : "",
      status: "待总账复核",
      createdAt: new Date().toLocaleString("zh-CN", { hour12: false }),
    };
    records.push(record);
    if (!writeStoredRecords(storageKeys.manual, records)) return;
    renderCompanyManualRecords();
    form.reset();
    showToast("手工记录已提交", "总账复核前不会纳入公司间往来计算");
  });

  const accountDialog = $("#accountDialog");
  $("#openAccountDialog")?.addEventListener("click", () => accountDialog.showModal());
  $$('[data-close-account]').forEach((button) => button.addEventListener("click", () => accountDialog.close()));
  $("#accountForm")?.addEventListener("submit", async (event) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    // The server binds the account to the session company and normalizes the
    // number; duplicates come back as 409.
    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 || "请稍后重试");
      return;
    }
    accountDialog.close();
    event.currentTarget.reset();
    await loadCompanyAccounts();
    showToast("银行账户已提交登记", "复核通过前不能上传流水，也不参与账户识别和覆盖计算");
  });
}

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