(() => {
  "use strict";

  const METRIC_ORDER = [
    "rainfall_24h",
    "basin_area",
    "concentration_time",
    "underlying_surface",
    "peak_flow",
    "peak_time",
    "water_level",
    "rain_type",
    "section_count",
  ];
  const METRIC_FALLBACKS = {
    rainfall_24h: { value: "—", unit: "mm", status: "待计算" },
    basin_area: { value: "—", unit: "km²", status: "待录入" },
    concentration_time: { value: "—", unit: "h", status: "待计算" },
    underlying_surface: { value: "—", unit: "", status: "待选取" },
    peak_flow: { value: "—", unit: "m³/s", status: "待计算" },
    peak_time: { value: "—", unit: "h", status: "待计算" },
    water_level: { value: "—", unit: "m", status: "待计算" },
    rain_type: { value: "—", unit: "", status: "待计算" },
    section_count: { value: "待接入", unit: "", status: "" },
  };
  const MODULE_COPY = {
    fld: { label: "流域水文", title: "流域水文与设计洪水", target: "modules/home-1.html", pages: 5 },
    irr: { label: "灌溉输水", title: "灌溉泵站与渠道输水", target: "modules/home-2.html", pages: 5 },
    riv: { label: "河道堤防", title: "河道堤防与防洪影响", target: "modules/home-3.html", pages: 6 },
    wsl: { label: "水位断面", title: "水位水面线与断面复核", target: "modules/home-4.html", pages: 3 },
    rsr: { label: "水库大坝", title: "水库蓄水与调洪", target: "modules/home-5.html", pages: 2 },
    hyd: { label: "水电机电", title: "水电站压力管道与机电", target: "modules/home-6.html", pages: 2 },
    hst: { label: "水工建筑物", title: "水工建筑物与桥涵泄流", target: "modules/home-7.html", pages: 3 },
  };
  const STATIC_SEARCH_INDEX = [
    ["1-01", "流域参数与设计暴雨", "设计暴雨 暴雨 频率 雨量", "modules/1-01.html"],
    ["1-04", "瞬时单位线法", "单位线 净雨 产汇流 过程线", "modules/1-04.html"],
    ["1-05", "推理公式法", "推理公式 汇流 洪峰", "modules/1-05.html"],
    ["1-11", "水文面积比拟法", "面积比拟 参证站 n值 线性回归", "modules/1-11.html"],
    ["1-12", "双对数内插法", "双对数 内插 参证站 面积流量", "modules/1-12.html"],
    ["1-13", "水文频率适线计算", "频率适线 P-III 经验频率", "modules/1-13.html"],
    ["4-01", "水面线推求", "水位 水面线 能量方程 标准步法", "modules/4-01.html"],
    ["3-15", "阻水比计算", "阻水 过水面积 桥孔", "modules/3-15.html"],
    ["3-16", "桥前壅水计算", "壅水 回水 桥前", "modules/3-16.html"],
    ["3-13", "冲刷计算", "一般冲刷 局部冲刷", "modules/3-13.html"],
    ["3-17", "净空校核", "净空 梁底 安全超高", "modules/3-17.html"],
    ["3-12", "基础埋深安全", "埋深 冲刷线 基础", "modules/3-12.html"],
    ["3-14", "壅水曲线与上游回水影响", "回水影响 壅水曲线", "modules/3-14.html"],
    ["5-01", "水库调洪演算", "调洪 水库 过程线", "modules/5-01.html"],
    ["7-01", "水闸水力计算", "水闸 泄流", "modules/7-01.html"],
  ];

  let searchIndex = STATIC_SEARCH_INDEX.map(([id, name, keywords, url]) => ({ id, name, keywords, url }));
  let toastTimer = 0;
  let loginPollTimer = 0;
  let currentAuth = null;
  let currentVersion = null;
  document.addEventListener("contextmenu", (event) => event.preventDefault());
  document.addEventListener("keydown", (event) => {
    const key = event.key.toLowerCase();
    if (event.key === "F12" || (event.ctrlKey && event.shiftKey && ["i", "j", "c"].includes(key))) {
      event.preventDefault();
    }
    if ((event.ctrlKey || event.metaKey) && key === "k") {
      event.preventDefault();
      const input = document.getElementById("program-search");
      if (input) input.focus();
    }
  });

  function updateClock(value) {
    const el = document.getElementById("model-clock");
    if (!el) return;
    const text = value || new Date().toLocaleTimeString("zh-CN", { hour12: false });
    el.textContent = `更新于 ${text}`;
  }

  function api() {
    return window.pywebview && window.pywebview.api;
  }

  async function callBridge(method, ...args) {
    if (!api() || typeof api()[method] !== "function") return null;
    try {
      return await api()[method](...args);
    } catch (error) {
      console.warn(`bridge ${method} failed`, error);
      return { ok: false, message: String(error) };
    }
  }

  function isWechatPlaceholder(value) {
    return /^wx_[0-9a-f]{16}$/i.test(String(value || "").trim());
  }

  function hasBoundPhone(auth) {
    const phone = String(auth?.bound_phone || auth?.phone || "").trim();
    return Boolean(phone && !isWechatPlaceholder(phone));
  }

  function boundPhoneText(auth) {
    if (!hasBoundPhone(auth)) return "未绑定手机号";
    return String(auth.bound_phone || auth.phone).trim();
  }

  function loggedInNeedsPayment(auth) {
    return Boolean(auth && auth.authorized === false && (auth.needs_payment || auth.action === "pay" || auth.logged_in));
  }

  function bindPhoneFormHtml(auth) {
    if (hasBoundPhone(auth)) return "";
    return `
      <form class="profile-bind-form" data-bind-phone-form>
        <h4>绑定手机号并同步会员</h4>
        <p>微信扫码登录后，绑定已开通会员的手机号，即可把该手机号下的会员同步到当前微信账号。</p>
        <div class="profile-bind-row">
          <label class="profile-bind-field">
            <span>手机号</span>
            <input type="tel" inputmode="numeric" maxlength="11" placeholder="请输入 11 位手机号" data-bind-phone-input>
          </label>
          <button type="button" data-bind-phone-code>获取验证码</button>
        </div>
        <div class="profile-bind-row">
          <label class="profile-bind-field">
            <span>验证码</span>
            <input type="text" inputmode="numeric" maxlength="8" placeholder="短信验证码" data-bind-phone-code-input>
          </label>
          <button type="button" class="primary" data-bind-phone-confirm>确认绑定</button>
        </div>
        <p class="profile-bind-message" data-bind-phone-message></p>
      </form>
    `;
  }

  function setBindPhoneMessage(dialog, message, state = "") {
    const el = dialog?.querySelector("[data-bind-phone-message]");
    if (!el) return;
    el.textContent = message || "";
    if (state) el.dataset.state = state;
    else delete el.dataset.state;
  }

  async function handleRequestBindPhoneCode(dialog) {
    const button = dialog?.querySelector("[data-bind-phone-code]");
    const phone = dialog?.querySelector("[data-bind-phone-input]")?.value || "";
    if (button) button.disabled = true;
    setBindPhoneMessage(dialog, "正在发送验证码...");
    const result = await callBridge("request_bind_phone_code", phone);
    if (!result || result.ok === false) {
      setBindPhoneMessage(dialog, result?.message || "验证码发送失败，请稍后重试。", "error");
      if (button) button.disabled = false;
      return;
    }
    const debugText = result.debug_code ? ` 测试验证码：${result.debug_code}` : "";
    setBindPhoneMessage(dialog, `${result.message || "验证码已发送。"}${debugText}`, "ok");
    if (!button) return;
    let seconds = 60;
    button.textContent = `${seconds}s 后重发`;
    const timer = window.setInterval(() => {
      seconds -= 1;
      if (seconds <= 0) {
        window.clearInterval(timer);
        button.disabled = false;
        button.textContent = "获取验证码";
        return;
      }
      button.textContent = `${seconds}s 后重发`;
    }, 1000);
  }

  async function handleConfirmBindPhone(dialog) {
    const button = dialog?.querySelector("[data-bind-phone-confirm]");
    const phone = dialog?.querySelector("[data-bind-phone-input]")?.value || "";
    const code = dialog?.querySelector("[data-bind-phone-code-input]")?.value || "";
    if (button) button.disabled = true;
    setBindPhoneMessage(dialog, "正在绑定手机号并同步会员...");
    const result = await callBridge("bind_account_phone", phone, code);
    if (!result || result.ok === false) {
      setBindPhoneMessage(dialog, result?.message || "手机号绑定失败，请检查验证码。", "error");
      if (button) button.disabled = false;
      return;
    }
    if (result.auth) updateAuthSummary(result.auth);
    setBindPhoneMessage(dialog, result.message || "手机号绑定成功，会员状态已同步。", "ok");
    toast(result.message || "手机号绑定成功。");
    if (loggedInNeedsPayment(result.auth)) {
      window.setTimeout(() => {
        document.querySelector(".dialog-backdrop")?.remove();
        showPaymentModal(result.auth?.message || "手机号绑定成功，请开通会员后使用。");
      }, 600);
      return;
    }
    window.setTimeout(() => document.querySelector(".dialog-backdrop")?.remove(), 900);
  }

  function paymentDisplayAccount(auth) {
    return auth?.display_account || auth?.phone || "微信账号";
  }

  function isExpiredMembership(auth) {
    const expireAt = String(auth?.expire_at || "").trim();
    if (!expireAt) return false;
    const parsed = Date.parse(expireAt);
    return Number.isFinite(parsed) && parsed <= Date.now();
  }

  function paymentPromptMessage(auth, fallback) {
    const message = String(fallback || "").trim();
    const plan = String(auth?.plan || auth?.plan_name || auth?.license?.plan || auth?.license_plan || "");
    if (loggedInNeedsPayment(auth) && isExpiredMembership(auth) && /月度|月卡|month/i.test(plan)) {
      return "月度体验会员已到期，继续使用请选择年度会员并完成微信支付。支付成功后软件会自动刷新授权。";
    }
    if (message) return message;
    if (loggedInNeedsPayment(auth)) {
      return "微信扫码登录已完成，当前账号未开通会员或会员已过期。请选择套餐并扫码支付开通。";
    }
    return "当前账号未完成在线授权，请微信扫码登录并开通会员后使用。";
  }

  function loggedOutAuthState(message) {
    return {
      authorized: false,
      logged_in: false,
      needs_payment: false,
      action: "login",
      display_account: "未登录",
      message: message || "已退出登录，请重新微信扫码登录。",
    };
  }

  function isUsableAuthPayload(auth) {
    if (!auth) return false;
    return [
      "authorized",
      "logged_in",
      "needs_payment",
      "action",
      "display_account",
      "phone",
      "bound_phone",
      "wechat_openid",
    ].some((key) => Object.prototype.hasOwnProperty.call(auth, key));
  }

  async function resolveWechatAuthPayload(payload) {
    if (isUsableAuthPayload(payload?.auth)) return payload.auth;
    const heartbeat = await callBridge("auth_heartbeat");
    if (isUsableAuthPayload(heartbeat)) return heartbeat;
    if (isUsableAuthPayload(currentAuth) && (currentAuth.logged_in || currentAuth.authorized)) return currentAuth;
    return null;
  }

  async function routeAfterWechatLogin(payload, defaultMessage) {
    const effectiveAuth = await resolveWechatAuthPayload(payload);
    if (effectiveAuth) updateAuthSummary(effectiveAuth);
    if (loggedInNeedsPayment(effectiveAuth) || payload?.requires_membership) {
      const fallbackMessage = payload?.message || effectiveAuth?.message || (
        String(defaultMessage || "").includes("开通") ? defaultMessage : "微信扫码登录成功，当前账号需开通会员后使用。"
      );
      const paymentMessage = paymentPromptMessage(effectiveAuth, fallbackMessage);
      showPaymentModal(paymentMessage);
      return true;
    }
    if (effectiveAuth?.authorized === true) {
      document.querySelector(".membership-modal")?.remove();
      toast(defaultMessage || payload?.message || "微信扫码登录成功。");
      return true;
    }
    return false;
  }

  function truthyFlag(value) {
    return value === true || value === 1 || value === "1" || String(value || "").toLowerCase() === "true";
  }

  function monthlyUnavailableReason(rules) {
    if (truthyFlag(rules?.monthly_pending)) return "该账号已有未完成的月度会员订单，请完成原订单或等待订单过期。";
    if (truthyFlag(rules?.monthly_used)) return "该账号已使用过月度会员，继续使用请选择年度会员。";
    return rules?.monthly_rule || "月度会员每个账号只能订阅一次。";
  }

  function normalizeMembershipPlans(payload) {
    const rules = payload?.rules || {};
    const rawPlans = Array.isArray(payload?.plans) ? payload.plans : [];
    const byId = new Map();
    rawPlans.forEach((plan) => {
      const id = String(plan?.id || plan?.plan_id || "").trim();
      if (id) byId.set(id, { ...plan, id });
    });
    if (!byId.has("month")) {
      byId.set("month", {
        id: "month",
        name: "月度会员",
        amount_cents: 12800,
        amount_yuan: "128.00",
        days: 30,
        max_devices: 1,
      });
    }
    if (!byId.has("year")) {
      byId.set("year", {
        id: "year",
        name: "年度会员",
        amount_cents: 128800,
        amount_yuan: "1288.00",
        days: 365,
        max_devices: 1,
      });
    }
    const monthlyAvailable = !truthyFlag(rules.monthly_used)
      && !truthyFlag(rules.monthly_pending)
      && rules.monthly_available !== false
      && rules.monthly_available !== "0";
    const month = byId.get("month");
    if (month) {
      month.name = month.name || "月度会员";
      month.available = month.available !== false && monthlyAvailable;
      month.purchase_note = month.purchase_note || "每个账号仅可订阅一次。";
      if (!month.available) month.disabled_reason = month.disabled_reason || monthlyUnavailableReason(rules);
    }
    const year = byId.get("year");
    if (year) {
      year.name = year.name || "年度会员";
      year.available = year.available !== false;
      year.purchase_note = year.purchase_note || "适合长期使用，月度体验后续续费请选择年度会员。";
    }
    return ["month", "year"].map((id) => byId.get(id)).filter(Boolean);
  }

  function showFatalError(message, detail) {
    document.querySelector(".fatal-error")?.remove();
    const panel = document.createElement("section");
    panel.className = "fatal-error";
    panel.setAttribute("role", "alert");
    panel.innerHTML = `
      <div>
        <strong>界面加载异常</strong>
        <p>${escapeHtml(message || "当前电脑的界面渲染失败，请点击重新加载；如果仍失败，请把启动日志发给管理员。")}</p>
        ${detail ? `<pre>${escapeHtml(detail).slice(0, 1200)}</pre>` : ""}
        <menu>
          <button type="button" data-reload-ui>重新加载</button>
          <button type="button" class="secondary" data-close-error>关闭提示</button>
        </menu>
      </div>
    `;
    panel.querySelector("[data-reload-ui]")?.addEventListener("click", () => window.location.reload());
    panel.querySelector("[data-close-error]")?.addEventListener("click", () => panel.remove());
    document.body.appendChild(panel);
  }

  window.addEventListener("error", (event) => {
    showFatalError("界面脚本执行失败。", `${event.message || ""}\n${event.filename || ""}:${event.lineno || ""}`);
  });

  window.addEventListener("unhandledrejection", (event) => {
    showFatalError("界面异步加载失败。", event.reason ? String(event.reason) : "");
  });

  function toast(message) {
    const el = document.getElementById("toast");
    if (!el) return;
    el.textContent = message;
    el.classList.add("show");
    window.clearTimeout(toastTimer);
    toastTimer = window.setTimeout(() => el.classList.remove("show"), 2400);
  }

  function showDialog(title, bodyHtml) {
    document.querySelector(".dialog-backdrop")?.remove();
    const dialog = document.createElement("div");
    dialog.className = "dialog-backdrop";
    dialog.innerHTML = `
      <section class="dialog-card" role="dialog" aria-modal="true" aria-label="${escapeHtml(title)}">
        <h3>${escapeHtml(title)}</h3>
        ${bodyHtml}
        <menu><button type="button" data-dialog-close aria-label="关闭弹窗">关闭</button></menu>
      </section>
    `;
    dialog.querySelector("[data-dialog-close]").addEventListener("click", () => dialog.remove());
    dialog.addEventListener("click", (event) => {
      if (event.target === dialog) dialog.remove();
    });
    document.body.appendChild(dialog);
    return dialog;
  }

  async function logoutAndRestart(event) {
    if (event) {
      event.preventDefault();
      event.stopPropagation();
    }
    try{await window.Workspace?.flush();}catch(error){toast("工程保存失败，未退出账号："+error.message);return;}
    window.clearTimeout(loginPollTimer);
    document.querySelector(".membership-modal")?.remove();
    setAccountMenuOpen(false);
    const result = await callBridge("logout_auth", false);
    if (result && result.ok === false) {
      toast(result.message || "退出登录失败，请关闭软件后重试。");
      return;
    }
    updateAuthSummary(loggedOutAuthState(result?.message));
    toast("已退出登录，请重新微信扫码登录。");
    showWechatLoginModal("本机登录状态已清除，正在打开微信扫码登录页面。", true);
  }

  async function startAccountMenuLogin(event) {
    if (event) {
      event.preventDefault();
      event.stopPropagation();
    }
    window.clearTimeout(loginPollTimer);
    document.querySelector(".membership-modal")?.remove();
    setAccountMenuOpen(false);
    const result = await callBridge("logout_auth", false);
    if (result && result.ok === false) {
      toast(result.message || "清除本机登录状态失败，请稍后重试。");
      return;
    }
    updateAuthSummary(loggedOutAuthState("请使用微信扫码登录。"));
    showWechatLoginModal("正在打开微信扫码登录页面。登录成功后，未开通会员的账号会自动进入支付开通流程。", true);
  }

  function showWechatLoginModal(message, autoStart = false) {
    document.querySelector(".membership-modal")?.remove();
    window.clearTimeout(loginPollTimer);
    const modal = document.createElement("div");
    modal.className = "membership-modal";
    modal.innerHTML = `
      <div class="membership-card" role="dialog" aria-modal="true" aria-label="微信扫码登录">
        <strong>微信扫码登录</strong>
        <p>${escapeHtml(message || "请使用微信扫码登录。登录后系统会自动校验会员授权。")}</p>
        <div class="login-status" data-login-status>点击下方按钮后，会自动打开微信扫码登录页面。</div>
        <menu>
          <button type="button" data-wechat-login>微信扫码登录</button>
          <button type="button" class="secondary" data-membership-close>稍后处理</button>
        </menu>
      </div>
    `;
    const statusEl = modal.querySelector("[data-login-status]");
    modal.querySelector("[data-wechat-login]")?.addEventListener("click", () => startWechatLogin(statusEl));
    modal.querySelector("[data-membership-close]")?.addEventListener("click", () => {
      window.clearTimeout(loginPollTimer);
      modal.remove();
    });
    document.body.appendChild(modal);
    if (autoStart) {
      window.setTimeout(() => startWechatLogin(statusEl), 0);
    }
  }

  async function startWechatLogin(statusEl) {
    const button = document.querySelector("[data-wechat-login]");
    if (button) button.disabled = true;
    if (statusEl) statusEl.textContent = "正在创建微信扫码登录请求...";
    const payload = await callBridge("start_wechat_login");
    if (!payload || payload.ok === false) {
      if (statusEl) statusEl.textContent = payload?.message || "创建微信登录请求失败，请稍后重试。";
      if (button) button.disabled = false;
      return;
    }
    if (!payload.state) {
      if (statusEl) statusEl.textContent = "服务器未返回登录状态码，请稍后重试。";
      if (button) button.disabled = false;
      return;
    }
    if (payload.auth_url) {
      const opened = await callBridge("open_external_url", payload.auth_url);
      if (!opened || opened.ok === false) {
        window.open(payload.auth_url, "_blank", "noopener,noreferrer");
      }
    }
    if (statusEl) statusEl.textContent = "已打开微信扫码页面，请在手机微信中确认登录。";
    pollWechatLogin(payload.state, statusEl, Math.max(20, Math.ceil(Number(payload.expires_in || 600) / 3)));
  }

  async function pollWechatLogin(state, statusEl, attemptsLeft) {
    window.clearTimeout(loginPollTimer);
    const payload = await callBridge("get_wechat_login_status", state);
    if (!payload || payload.ok === false) {
      if (statusEl) statusEl.textContent = payload?.message || "查询登录状态失败，请重新扫码。";
      const button = document.querySelector("[data-wechat-login]");
      if (button) button.disabled = false;
      return;
    }
    if (payload.status === "authenticated") {
      const routed = await routeAfterWechatLogin(payload, "微信扫码登录成功。");
      if (!routed) {
        document.querySelector(".membership-modal")?.remove();
        toast("微信扫码登录成功。");
      }
      return;
    }
    if (payload.status === "device_conflict" || payload.requires_device_replace) {
      showDeviceConflictPrompt(state, statusEl, payload);
      return;
    }
    if (payload.requires_membership) {
      const routed = await routeAfterWechatLogin(payload, payload.message || "微信扫码登录成功，当前账号需开通会员后使用。");
      if (routed) return;
      if (statusEl) statusEl.textContent = payload.message || "账号尚未开通会员，请扫码支付开通后再使用。";
      const button = document.querySelector("[data-wechat-login]");
      if (button) button.disabled = false;
      return;
    }
    if (attemptsLeft <= 0) {
      if (statusEl) statusEl.textContent = "扫码登录已超时，请重新发起。";
      const button = document.querySelector("[data-wechat-login]");
      if (button) button.disabled = false;
      return;
    }
    if (statusEl) statusEl.textContent = payload.message || "等待微信扫码确认...";
    loginPollTimer = window.setTimeout(() => pollWechatLogin(state, statusEl, attemptsLeft - 1), 3000);
  }

  function showDeviceConflictPrompt(state, statusEl, payload) {
    window.clearTimeout(loginPollTimer);
    const conflict = payload.device_conflict || {};
    const candidate = conflict.replace_candidate || {};
    const used = conflict.active_device_count ?? conflict.devices?.length ?? "—";
    const limit = conflict.max_devices ?? "—";
    const canReplace = conflict.can_replace !== false;
    const usedMonthly = conflict.monthly_replace_used ?? 0;
    const monthlyLimit = conflict.monthly_replace_limit ?? "—";
    const oldDevice = candidate.device_name || "最早登录的旧设备";
    const lastSeen = candidate.last_seen_at || "无记录";
    if (statusEl) {
      statusEl.innerHTML = `
        <div class="device-conflict-panel">
          <strong>当前账号设备席位已满</strong>
          <p>${escapeHtml(payload.message || "请确认是否替换旧设备。本机确认后，旧设备会自动退出，本机完成登录。")}</p>
          <dl>
            <div><dt>设备席位</dt><dd>${escapeHtml(String(used))}/${escapeHtml(String(limit))}</dd></div>
            <div><dt>将替换设备</dt><dd>${escapeHtml(oldDevice)}</dd></div>
            <div><dt>旧设备最近使用</dt><dd>${escapeHtml(lastSeen)}</dd></div>
            <div><dt>本月自助替换</dt><dd>${escapeHtml(String(usedMonthly))}/${escapeHtml(String(monthlyLimit))}</dd></div>
          </dl>
          <div class="device-conflict-actions">
            <button type="button" data-confirm-device-replace ${canReplace ? "" : "disabled"}>替换旧设备并登录</button>
            <button type="button" class="secondary" data-restart-wechat-scan>重新扫码</button>
          </div>
        </div>
      `;
      statusEl.querySelector("[data-confirm-device-replace]")?.addEventListener("click", () => {
        confirmWechatDeviceReplace(state, statusEl);
      });
      statusEl.querySelector("[data-restart-wechat-scan]")?.addEventListener("click", () => {
        showWechatLoginModal("请重新打开微信扫码登录页面。");
      });
    }
    const button = document.querySelector("[data-wechat-login]");
    if (button) button.disabled = true;
  }

  async function confirmWechatDeviceReplace(state, statusEl) {
    const confirmButton = statusEl?.querySelector("[data-confirm-device-replace]");
    if (confirmButton) confirmButton.disabled = true;
    const result = await callBridge("confirm_wechat_device_replace", state);
    if (!result || result.ok === false || result.status !== "authenticated") {
      if (statusEl) {
        const messageEl = statusEl.querySelector(".device-conflict-panel p");
        if (messageEl) messageEl.textContent = result?.message || "设备替换失败，请重新扫码或联系管理员。";
      }
      if (confirmButton) confirmButton.disabled = false;
      return;
    }
    const routed = await routeAfterWechatLogin(result, result.message || "已替换旧设备并完成微信登录。");
    if (!routed) toast(result.message || "已替换旧设备并完成微信登录。");
  }

  function showMembershipModal(message) {
    if (loggedInNeedsPayment(currentAuth)) {
      showPaymentModal(message);
      return;
    }
    if (document.querySelector(".membership-modal")) return;
    const modal = document.createElement("div");
    modal.className = "membership-modal";
    modal.innerHTML = `
      <div class="membership-card" role="dialog" aria-modal="true" aria-label="会员授权">
        <strong>授权失效，需要微信扫码</strong>
        <p>${escapeHtml(message || "当前账号未完成在线授权，请微信扫码登录并开通会员后使用。")}</p>
        <menu>
          <button type="button" data-wechat-login-start>微信扫码登录</button>
          <button type="button" class="secondary" data-membership-close>稍后处理</button>
        </menu>
      </div>
    `;
    modal.querySelector("[data-wechat-login-start]")?.addEventListener("click", logoutAndRestart);
    modal.querySelector("[data-membership-close]")?.addEventListener("click", () => modal.remove());
    document.body.appendChild(modal);
  }

  function showPaymentModal(message) {
    document.querySelector(".membership-modal")?.remove();
    const promptMessage = paymentPromptMessage(currentAuth, message);
    const modal = document.createElement("div");
    modal.className = "membership-modal";
    modal.innerHTML = `
      <div class="membership-card payment-card" role="dialog" aria-modal="true" aria-label="开通会员">
        <strong>开通会员后使用</strong>
        <p>${escapeHtml(promptMessage)}</p>
        <div class="payment-account-box">
          <span>当前账号</span>
          <b>${escapeHtml(paymentDisplayAccount(currentAuth))}</b>
        </div>
        <div class="plan-grid" data-plan-list>
          <div class="payment-status">正在读取会员套餐...</div>
        </div>
        <div class="payment-status" data-payment-status>支付完成后软件会自动刷新授权状态。</div>
        <menu>
          <button type="button" class="secondary" data-membership-close>稍后处理</button>
        </menu>
      </div>
    `;
    modal.querySelector("[data-membership-close]")?.addEventListener("click", () => modal.remove());
    document.body.appendChild(modal);
    loadMembershipPlans(modal);
  }

  async function loadMembershipPlans(modal) {
    const list = modal.querySelector("[data-plan-list]");
    const statusEl = modal.querySelector("[data-payment-status]");
    const payload = await callBridge("get_membership_plans");
    if (!payload || payload.ok === false) {
      if (list) list.innerHTML = `<div class="payment-status error">${escapeHtml(payload?.message || "读取会员套餐失败，请稍后重试。")}</div>`;
      return;
    }
    const plans = normalizeMembershipPlans(payload);
    if (!plans.length) {
      if (list) list.innerHTML = `<div class="payment-status error">当前没有可购买套餐，请联系管理员检查支付配置。</div>`;
      return;
    }
    if (list) {
      list.innerHTML = plans.map((plan) => {
        const rawPlanId = String(plan.id || plan.plan_id || "");
        const planId = escapeHtml(rawPlanId);
        const amount = Number(plan.amount_yuan ?? (Number(plan.amount_cents || 0) / 100)).toFixed(2);
        const days = plan.days ? `${escapeHtml(plan.days)} 天` : "按套餐规则";
        const maxDevices = plan.max_devices ? `${escapeHtml(plan.max_devices)} 台设备` : "设备按规则";
        const available = plan.available !== false && plan.disabled !== true;
        const cardClass = available ? "plan-card" : "plan-card disabled";
        const buttonText = rawPlanId === "month" ? "微信支付开通月度会员" : "微信支付开通年度会员";
        const disabledReason = escapeHtml(plan.disabled_reason || "该套餐当前不可购买。");
        const note = escapeHtml(plan.purchase_note || "");
        return `
          <article class="${cardClass}">
            <span class="plan-name">${escapeHtml(plan.name || "会员套餐")}</span>
            <span class="plan-price">￥${amount}</span>
            <small>${days} · ${maxDevices}</small>
            ${note ? `<em class="plan-note">${note}</em>` : ""}
            ${available ? "" : `<small class="plan-disabled">${disabledReason}</small>`}
            <button type="button" data-plan-id="${planId}" ${available ? "" : "disabled aria-disabled=\"true\""}>${available ? buttonText : "不可购买"}</button>
          </article>
        `;
      }).join("");
      list.querySelectorAll("[data-plan-id]:not([disabled])").forEach((button) => {
        button.addEventListener("click", () => createPaymentOrder(button.dataset.planId, statusEl));
      });
    }
  }

  async function createPaymentOrder(planId, statusEl) {
    if (!planId) {
      if (statusEl) statusEl.textContent = "套餐编号缺失，请刷新后重试。";
      return;
    }
    if (statusEl) statusEl.textContent = "正在创建微信支付订单...";
    const payload = await callBridge("create_payment_order", planId, "wechat");
    if (!payload || payload.ok === false) {
      if (statusEl) statusEl.textContent = payload?.message || "创建支付订单失败，请稍后重试。";
      return;
    }
    const payUrl = payload.pay_page_url || payload.code_url || payload.url;
    if (payUrl) {
      const opened = await callBridge("open_external_url", payUrl);
      if (!opened || opened.ok === false) {
        window.open(payUrl, "_blank", "noopener,noreferrer");
      }
      if (statusEl) statusEl.textContent = "已打开微信支付页面，请完成扫码支付。";
    } else if (statusEl) {
      statusEl.textContent = "订单已创建，但服务器未返回支付页面地址，请联系管理员。";
    }
    if (payload.order_no) pollPaymentOrder(payload.order_no, statusEl, 120);
  }

  async function pollPaymentOrder(orderNo, statusEl, attemptsLeft) {
    if (attemptsLeft <= 0) {
      if (statusEl) statusEl.textContent = "支付状态确认超时。如已付款，请点击账号面板里的“扫码登录”重新校验授权。";
      return;
    }
    const payload = await callBridge("get_payment_order", orderNo);
    if (!payload || payload.ok === false) {
      if (statusEl) statusEl.textContent = payload?.message || "查询支付状态失败，稍后会继续重试。";
      window.setTimeout(() => pollPaymentOrder(orderNo, statusEl, attemptsLeft - 1), 3000);
      return;
    }
    const status = String(payload.status || "").toLowerCase();
    if (status === "paid") {
      const auth = payload.auth || await callBridge("auth_heartbeat");
      updateAuthSummary(auth);
      document.querySelector(".membership-modal")?.remove();
      toast("会员开通成功，授权已刷新。");
      return;
    }
    if (["expired", "closed", "cancelled", "canceled", "refunded"].includes(status)) {
      if (statusEl) statusEl.textContent = `订单状态：${payload.status || "已关闭"}，请重新创建支付订单。`;
      return;
    }
    if (statusEl) statusEl.textContent = "等待支付完成，软件会自动刷新授权...";
    window.setTimeout(() => pollPaymentOrder(orderNo, statusEl, attemptsLeft - 1), 3000);
  }

  function updateAuthSummary(auth) {
    const account = document.querySelector(".account");
    const label = document.querySelector("[data-auth-label]");
    const accountId = document.querySelector("[data-auth-account]");
    const plan = document.querySelector("[data-auth-plan]");
    if (!account || !label || !plan || !auth) return;
    currentAuth = auth;
    const unauthorized = auth.authorized === false;
    const needsPayment = loggedInNeedsPayment(auth);
    account.classList.toggle("auth-invalid", unauthorized);
    label.textContent = unauthorized ? (needsPayment ? "会员未开通" : "授权失效") : "在线授权";
    const displayAccount = auth.display_account || auth.phone || (unauthorized ? "未登录" : "微信已登录");
    if (accountId) {
      accountId.textContent = `账号：${displayAccount}`;
      accountId.title = auth.phone ? `当前登录账号：${auth.phone}` : String(displayAccount);
    }
    if (unauthorized) {
      plan.textContent = needsPayment ? "开通会员" : "请微信扫码登录";
      updateAccountDetailPanel();
      return;
    }
    const currentPlan = auth.plan || "终身会员";
    plan.textContent = auth.expire_at ? `${currentPlan} · ${auth.expire_at}` : `${currentPlan} · 永久授权`;
    updateAccountDetailPanel();
  }

  function setText(selector, value) {
    const element = document.querySelector(selector);
    if (element) element.textContent = value;
  }

  function accountPlanText(auth) {
    if (!auth) return "未开通";
    if (auth.authorized === false) return loggedInNeedsPayment(auth) ? "未开通 · 待支付" : "未登录";
    const currentPlan = auth.plan || "终身会员";
    return auth.expire_at ? `${currentPlan} · 到期 ${auth.expire_at}` : `${currentPlan} · 永久授权`;
  }

  function updateAccountDetailPanel() {
    const auth = currentAuth || {};
    const version = currentVersion || {};
    const unauthorized = auth.authorized === false;
    const accountText = auth.display_account || auth.phone || (unauthorized ? "未登录" : "微信已登录");
    const wechatText = auth.nickname || (auth.unionid ? `UnionID · ${String(auth.unionid).slice(-6)}` : "") || (auth.openid ? `OpenID · ${String(auth.openid).slice(-6)}` : "") || "未获取";
    const statusText = unauthorized ? (auth.message || "授权失效") : "在线授权正常";
    setText("[data-account-detail-title]", accountText);
    setText("[data-account-detail-subtitle]", statusText);
    setText("[data-account-detail-account]", accountText);
    setText("[data-account-detail-phone]", boundPhoneText(auth));
    setText("[data-account-detail-wechat]", wechatText);
    setText("[data-account-detail-status]", statusText);
    setText("[data-account-detail-plan]", accountPlanText(auth));
    setText("[data-account-detail-device]", auth.device_name || "当前设备");
    setText("[data-account-detail-version]", version.version ? `v${version.version}` : "本地预览");
  }

  function setAccountMenuOpen(open) {
    const menu = document.querySelector("[data-account-menu]");
    const button = document.querySelector('[data-action="profile"]');
    if (!menu || !button) return;
    menu.hidden = !open;
    button.setAttribute("aria-expanded", open ? "true" : "false");
  }

  async function refreshAccountMenu() {
    const [version, auth] = await Promise.all([
      callBridge("get_version"),
      callBridge("auth_heartbeat"),
    ]);
    if (version?.version) currentVersion = version;
    updateAuthSummary(auth || { authorized: false, message: "授权心跳失败" });
    updateAccountDetailPanel();
  }

  function wireAccountMenu() {
    const button = document.querySelector('[data-action="profile"]');
    const menu = document.querySelector("[data-account-menu]");
    if (!button || !menu) return;
    button.addEventListener("click", async (event) => {
      event.preventDefault();
      event.stopImmediatePropagation();
      const nextOpen = menu.hidden;
      if (nextOpen) await refreshAccountMenu();
      setAccountMenuOpen(nextOpen);
    }, true);
    menu.querySelector("[data-auth-login-menu]")?.addEventListener("click", startAccountMenuLogin);
    menu.querySelector("[data-auth-switch-account]")?.addEventListener("click", logoutAndRestart);
    document.addEventListener("click", (event) => {
      if (!event.target.closest("[data-account-shell]")) setAccountMenuOpen(false);
    });
    document.addEventListener("keydown", (event) => {
      if (event.key === "Escape") setAccountMenuOpen(false);
    });
  }

  function escapeHtml(value) {
    return String(value ?? "")
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;");
  }

  function wireTabs() {
    const tabs = Array.from(document.querySelectorAll(".model-tabs button"));
    const views = Array.from(document.querySelectorAll(".visual-stage .view"));
    tabs.forEach((tab) => {
      tab.addEventListener("click", () => {
        const viewName = tab.dataset.view;
        tabs.forEach((item) => {
          const active = item === tab;
          item.classList.toggle("active", active);
          item.setAttribute("aria-selected", active ? "true" : "false");
        });
        views.forEach((view) => view.classList.toggle("active", view.classList.contains(`view-${viewName}`)));
      });
    });
  }

  function drawGrid() {
    const grid = document.querySelector(".view-flow .grid");
    if (!grid) return;
    const lines = [];
    for (let x = 72; x <= 562; x += 70) lines.push(`<line x1="${x}" y1="52" x2="${x}" y2="252"/>`);
    for (let y = 62; y <= 242; y += 45) lines.push(`<line x1="72" y1="${y}" x2="562" y2="${y}"/>`);
    grid.innerHTML = lines.join("");
  }

  function updateMetricCards(data) {
    if (!data || data.ok === false) data = {};
    METRIC_ORDER.forEach((key) => {
      const strong = document.querySelector(`[data-metric="${key}"]`);
      const em = document.querySelector(`[data-unit="${key}"]`);
      const metric = data[key] || METRIC_FALLBACKS[key];
      if (!metric) return;
      strong.textContent = metric.value || "—";
      const unit = metric.unit ? ` ${metric.unit}` : "";
      if (em) em.textContent = `${metric.status || ""}${unit}`.trim() || "待计算";
    });
    if (data.updated_at) updateClock(data.updated_at);
  }

  function updateModuleSummary(payload) {
    if (!payload || payload.ok === false) return;
    const modules = payload.modules || [];
    modules.forEach((module) => {
      const key = module.key;
      const card = document.querySelector(`.module-card[data-module="${CSS.escape(key)}"]`);
      const copy = MODULE_COPY[key];
      if (!card || !copy) return;
      card.querySelector(".count").textContent = `${module.program_count ?? "—"} 项程序 · ${copy.pages} 页`;
      card.dataset.done = module.status_done ?? "";
    });

    const summary = payload.summary || {};
    const more = document.querySelector(".more-card p");
    if (more) {
      const total = summary.total ?? "—";
      const done = summary.done ?? "—";
      more.textContent = `查看 ${total} 项程序清单、${done} 项已接入状态和依赖链。`;
    }
  }

  async function buildSearchIndex() {
    const modulesPayload = await callBridge("get_modules");
    updateModuleSummary(modulesPayload);
    const allPrograms = await callBridge("list_programs", "all");
    if (!allPrograms || allPrograms.ok === false || !Array.isArray(allPrograms.programs)) return;
    searchIndex = allPrograms.programs.map((program) => {
      const id = String(program.program_id || "");
      const moduleKey = String(program.module_key || "");
      const firstTarget = MODULE_COPY[moduleKey]?.target || "modules/1-01.html";
      const lower = id.toLowerCase();
      const explicit = `modules/${lower}.html`;
      return {
        id,
        name: String(program.name || id),
        keywords: [program.module, program.source, ...(program.inputs || []), ...(program.outputs || [])].join(" "),
        url: moduleKey ? explicit : firstTarget,
      };
    });
  }

  function runSearch() {
    const input = document.getElementById("program-search");
    const query = (input?.value || "").trim().toLowerCase();
    if (!query) {
      toast("请输入程序名、公式或规范关键词。");
      return;
    }
    const matched = searchIndex.find((item) => {
      const haystack = `${item.id} ${item.name} ${item.keywords}`.toLowerCase();
      return haystack.includes(query);
    });
    if (!matched) {
      toast("未找到匹配程序，请换一个关键词。");
      return;
    }
    window.location.href = matched.url;
  }

  function wireActions() {
    document.querySelectorAll("[data-target]").forEach((button) => {
      button.addEventListener("click", () => {
        window.location.href = button.dataset.target;
      });
    });

    document.querySelectorAll(".module-card footer button").forEach((button) => {
      button.addEventListener("click", (event) => {
        const card = event.currentTarget.closest(".module-card");
        if (!card?.dataset.target) return;
        window.location.href = card.dataset.target;
      });
    });

    document.querySelectorAll("[data-tdt-layer]").forEach((button) => {
      button.addEventListener("click", () => setTiandituLayer(button.dataset.tdtLayer));
    });

    document.querySelector('[data-action="file-menu"]')?.addEventListener("click", () => {
      showDialog("文件", "<p>工程打开、新建和保存能力正在接入工程数据模型。当前版本请先从各计算入口录入参数并导出计算书。</p>");
    });

    document.querySelector('[data-action="docs"]')?.addEventListener("click", () => {
      showDialog("资料库", `
        <p>常用资料入口：</p>
        <ul>
          <li>瞬时单位线法：附表 10、附表 12、甘肃省暴雨洪水图集提取资料</li>
          <li>推理公式法：产流入渗、汇流参数、设计洪水过程线资料</li>
          <li>壅水、冲刷、曼宁公式、水库调洪及水利大算盘资料</li>
        </ul>
        <p>资料文件随安装包发布，位于软件目录的“资料”文件夹。</p>
      `);
    });

    document.querySelector('[data-action="help"]')?.addEventListener("click", () => {
      showDialog("帮助", "<p>按工程任务选择模块，进入程序页后按输入区提示录入参数。计算完成后可导出结果表和计算书。未接入的工程管理功能会显示“接入中”，不会出现无反馈按钮。</p>");
    });

    document.querySelector('[data-action="project"]')?.addEventListener("click", () => {
      toast("项目管理接入中，当前请从计算入口进入。");
    });

    document.querySelector('[data-action="profile"]')?.addEventListener("click", async () => {
      const version = await callBridge("get_version");
      const auth = await callBridge("auth_heartbeat");
      updateAuthSummary(auth);
      const versionText = version?.version ? `当前版本：${escapeHtml(version.version)}` : "当前版本：本地预览";
      const authText = auth?.authorized === false ? escapeHtml(auth.message || "未授权") : "在线授权正常";
      const accountText = auth?.display_account || auth?.phone || (auth?.authorized === false ? "未登录" : "微信已登录");
      const phoneText = escapeHtml(boundPhoneText(auth));
      const deviceText = auth?.device_name ? escapeHtml(auth.device_name) : "当前设备";
      const action = `<menu><button type="button" data-profile-membership>会员与续费</button><button type="button" data-profile-auth-switch>退出/切换账号</button></menu>`;
      const dialog = showDialog("个人中心", `
        <dl class="profile-list">
          <div><dt>登录账号</dt><dd>${escapeHtml(accountText)}</dd></div>
          <div><dt>绑定手机</dt><dd>${phoneText}</dd></div>
          <div><dt>授权状态</dt><dd>${authText}</dd></div>
          <div><dt>会员套餐</dt><dd>${escapeHtml(accountPlanText(auth))}</dd></div>
          <div><dt>当前设备</dt><dd>${deviceText}</dd></div>
          <div><dt>软件版本</dt><dd>${versionText}</dd></div>
        </dl>
        ${hasBoundPhone(auth) ? "" : "<p class=\"profile-warning\">当前微信账号尚未绑定手机号。绑定后可在不同电脑上更清楚地识别账号与会员状态。</p>"}
        ${bindPhoneFormHtml(auth)}
        ${action}
      `);
      dialog.querySelector("[data-profile-auth-switch]")?.addEventListener("click", logoutAndRestart);
      dialog.querySelector("[data-profile-membership]")?.addEventListener("click", () => {dialog.remove();showPaymentModal();});
      dialog.querySelector("[data-bind-phone-code]")?.addEventListener("click", () => handleRequestBindPhoneCode(dialog));
      dialog.querySelector("[data-bind-phone-confirm]")?.addEventListener("click", () => handleConfirmBindPhone(dialog));
    });

    document.querySelector(".more-card")?.addEventListener("click", () => {
      const rows = Object.entries(MODULE_COPY)
        .map(([key, item]) => {
          const card = document.querySelector(`.module-card[data-module="${key}"]`);
          const count = card?.querySelector(".count")?.textContent || "—";
          return `<li><strong>${escapeHtml(item.title)}</strong>：${escapeHtml(count)}</li>`;
        })
        .join("");
      showDialog("程序清单", `<ul>${rows}</ul><p>完整程序目录由注册表统一管理，已接入程序可直接从模块卡进入。</p>`);
    });

    const search = document.getElementById("program-search");
    search?.addEventListener("keydown", (event) => {
      if (event.key === "Enter") runSearch();
    });
  }

  let bridgeInitialized = false;

  async function updateStatusbarVersion() {
    const el = document.getElementById("app-version");
    if (!el) return;
    const version = await callBridge("get_version");
    if (version?.version) {
      currentVersion = version;
      el.textContent = `v${version.version}`;
    }
  }

  async function initBridgeData() {
    if (bridgeInitialized) return;
    bridgeInitialized = true;
    await updateStatusbarVersion();
    if (!window.WORKSPACE_MODE) {
      const dashboard = await callBridge("get_dashboard_data");
      updateMetricCards(dashboard);
      await buildSearchIndex();
    }

    const auth = await callBridge("auth_heartbeat");
    updateAuthSummary(auth);
    if (auth && auth.authorized === false) {
      showMembershipModal(paymentPromptMessage(auth, auth.message));
    }
  }

  document.addEventListener("DOMContentLoaded", () => {
    if (!window.WORKSPACE_MODE) {
      updateClock();
      window.setInterval(() => updateClock(), 1000);
      wireTabs();
      drawGrid();
    }
    wireActions();
    wireAccountMenu();

    let attempts = 0;
    const timer = window.setInterval(() => {
      attempts += 1;
      if (api()) {
        window.clearInterval(timer);
        initBridgeData();
      } else if (attempts > 25) {
        window.clearInterval(timer);
        updateMetricCards({ ok: true });
      }
    }, 120);
  });

  window.addEventListener("pywebviewready", initBridgeData, { once: true });
})();
