/* =========================================================================
   债务明细清单 · 弹窗与表单
   ========================================================================= */
const { yuanToFen, fenToYuanNum, money, fmtDateTime, daysBetween, uid: _uid } = window.Ledger;

function nowLocalInput() {
  const d = window.Ledger.TODAY;
  const p = (n) => String(n).padStart(2, "0");
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;
}

function fileSizeLabel(bytes) {
  if (!bytes) return "0 KB";
  if (bytes < 1024 * 1024) return Math.max(1, Math.round(bytes / 1024)) + " KB";
  return (bytes / 1024 / 1024).toFixed(1) + " MB";
}

function readAttachment(file) {
  return new Promise((resolve, reject) => {
    if (!file) return resolve(null);
    if (file.size > 5 * 1024 * 1024) return reject(new Error("单个附件不能超过 5MB"));
    const reader = new FileReader();
    reader.onload = () => resolve({
      id: _uid("att"),
      name: file.name,
      size: fileSizeLabel(file.size),
      bytes: file.size,
      kind: file.type && file.type.startsWith("image/") ? "image" : "file",
      type: file.type || "application/octet-stream",
      dataUrl: reader.result,
      createdAt: new Date().toISOString(),
    });
    reader.onerror = () => reject(new Error("附件读取失败"));
    reader.readAsDataURL(file);
  });
}

/* ===================== 记一笔还款 ===================== */
function PaymentModal({ state, actions, onClose, preset = {}, edit, toast }) {
  const debtorsWithRemaining = state.debtors.filter((d) => {
    const calc = window.Ledger.debtorCalc(state, d.id);
    return calc.remaining > 0 || calc.pending > 0 || d.id === edit?.debtorId;
  });
  const editDatetime = edit ? new Date(edit.datetime) : null;
  const localEditDatetime = editDatetime ? new Date(editDatetime.getTime() - editDatetime.getTimezoneOffset() * 60000).toISOString().slice(0, 16) : "";
  const [debtorId, setDebtorId] = React.useState(edit?.debtorId || preset.debtorId || "");
  const [debtId, setDebtId] = React.useState(edit?.debtId || preset.debtId || "");
  const [amount, setAmount] = React.useState(edit ? fenToYuanNum(edit.amountFen) : "");
  const [datetime, setDatetime] = React.useState(localEditDatetime || nowLocalInput());
  const [channel, setChannel] = React.useState(edit?.channel || state.channels[0]);
  const [txnNo, setTxnNo] = React.useState(edit?.txnNo || "");
  const [account, setAccount] = React.useState(edit?.account || "");
  const [note, setNote] = React.useState(edit?.note || "");
  const [attachment, setAttachment] = React.useState(edit?.attachment || (edit?.hasAttach ? { name: edit.attachmentName || "历史凭证", size: "凭证", kind: "image" } : null));
  const [errors, setErrors] = React.useState({});
  const [saving, setSaving] = React.useState(false);
  const [confirmOver, setConfirmOver] = React.useState(false);

  const openDebts = state.debts.filter((d) => d.debtorId === debtorId && !d.archived)
    .map((d) => ({ d, c: window.Ledger.debtCalc(state, d.id) }))
    .filter((x) => x.c.remaining > 0 || x.c.amountPending || x.d.id === debtId);

  const curCalc = debtId ? window.Ledger.debtCalc(state, debtId) : null;
  const amtFen = yuanToFen(amount);
  const editableBaseRemaining = curCalc ? curCalc.remaining + (edit && edit.debtId === debtId && edit.status === "valid" ? edit.amountFen : 0) : 0;
  const afterRemaining = curCalc ? editableBaseRemaining - amtFen : 0;
  const isOver = curCalc && amtFen > editableBaseRemaining && amtFen > 0;

  // 重复流水号检测
  const dupTxn = txnNo && state.payments.some((p) => p.id !== edit?.id && p.txnNo && p.txnNo === txnNo && p.status === "valid");

  function validate() {
    const e = {};
    if (!debtorId) e.debtorId = "请选择欠款人";
    if (!debtId) e.debtId = "请选择具体债务";
    if (!amount || amtFen <= 0) e.amount = "还款金额必须大于 0";
    if (amount && !/^\d+(\.\d{1,2})?$/.test(String(amount).replace(/,/g, ""))) e.amount = "金额最多保留两位小数";
    if (!datetime) e.datetime = "请填写还款时间";
    if (!channel) e.channel = "请选择还款途径";
    setErrors(e);
    return Object.keys(e).length === 0;
  }

  function doSave() {
    if (saving) return;
    if (!validate()) return;
    if (isOver && !confirmOver) { setConfirmOver(true); return; }
    setSaving(true);
    const debtor = state.debtors.find((x) => x.id === debtorId);
    const debt = state.debts.find((x) => x.id === debtId);
    setTimeout(() => {
      const payload = { debtId, debtorId, amountFen: amtFen, datetime: new Date(datetime).toISOString(), channel, txnNo, account, note, hasAttach: !!attachment, attachment, attachmentName: attachment ? attachment.name : "" };
      if (edit) actions.updatePayment({ ...edit, ...payload });
      else actions.addPayment(payload);
      const recalc = window.Ledger.debtCalc({ ...state }, debtId);
      toast({ msg: edit ? `已更新 ${debtor.name} 的还款记录` : `已记录 ${debtor.name} 的还款 ${money(amtFen)}`, tone: "success" });
      onClose({ summary: { debtor, debt, amtFen, channel, datetime } });
    }, 450);
  }

  return (
    <Modal title={edit ? "编辑还款记录" : "记一笔还款"} desc="确认债务后快速完成记录，保存前可查看还款后剩余" onClose={() => onClose()} size="lg"
      footer={
        <>
          <div className="foot-info">
            {curCalc ? (
              <span className={"num " + (isOver ? "warn" : "")}>本次还款后剩余：<b>{money(afterRemaining)}</b></span>
            ) : <span className="muted">选择债务后显示还款后剩余</span>}
          </div>
          <div className="foot-btns">
            <Btn kind="ghost" onClick={() => onClose()}>取消</Btn>
            <Btn kind="primary" icon={saving ? null : "check"} onClick={doSave} disabled={saving}>{saving ? "保存中…" : edit ? "保存修改" : "保存还款"}</Btn>
          </div>
        </>
      }>
      <div className="form-grid">
        <Field label="欠款人" required error={errors.debtorId}>
          <select className="inp" value={debtorId} onChange={(e) => { setDebtorId(e.target.value); setDebtId(""); }}>
            <option value="">请选择欠款人</option>
            {debtorsWithRemaining.map((d) => {
              const calc = window.Ledger.debtorCalc(state, d.id);
              return <option key={d.id} value={d.id}>{d.name}（{calc.pending > 0 ? "金额待补充" : `剩余 ${money(calc.remaining)}`}）</option>;
            })}
          </select>
        </Field>
        <Field label="具体债务" required error={errors.debtId} hint={debtorId && openDebts.length === 0 ? "该欠款人暂无未结清债务" : "仅显示未结清债务"}>
          <select className="inp" value={debtId} onChange={(e) => setDebtId(e.target.value)} disabled={!debtorId}>
            <option value="">请选择债务</option>
            {openDebts.map((x) => <option key={x.d.id} value={x.d.id}>{x.d.title}（{x.c.amountPending ? "金额待补充" : `剩余 ${money(x.c.remaining)}`}）</option>)}
          </select>
        </Field>
      </div>

      {/* 金额：最大视觉权重 */}
      <div className={`amount-input ${errors.amount ? "ai-error" : ""} ${isOver ? "ai-warn" : ""}`}>
        <div className="ai-label">还款金额 <span className="req">必填</span></div>
        <div className="ai-row">
          <span className="ai-cur">¥</span>
          <input className="ai-field num" inputMode="decimal" placeholder="0.00" value={amount}
            onChange={(e) => setAmount(e.target.value.replace(/[^\d.]/g, ""))} autoFocus />
          {curCalc && !curCalc.amountPending && <button className="ai-fill" onClick={() => setAmount(fenToYuanNum(editableBaseRemaining))}>还清剩余</button>}
        </div>
        {errors.amount && <div className="field-msg err">{errors.amount}</div>}
        {isOver && <div className="over-warn"><Icon name="alert" size={15} />还款金额超出剩余待还，超出 <b className="num">{money(amtFen - editableBaseRemaining)}</b>，将标记为「存在多还款」</div>}
      </div>

      <div className="form-grid">
        <Field label="还款日期与时间" required error={errors.datetime}>
          <input type="datetime-local" className="inp" value={datetime} onChange={(e) => setDatetime(e.target.value)} />
        </Field>
        <Field label="还款途径" required error={errors.channel}>
          <div className="seg">
            {state.channels.map((c) => <button key={c} className={"seg-btn " + (channel === c ? "active" : "")} onClick={() => setChannel(c)}>{c}</button>)}
          </div>
        </Field>
      </div>
      <div className="form-grid">
        <Field label="交易流水号" hint={dupTxn ? null : "可选"} error={dupTxn ? "该流水号已存在有效记录，请确认是否重复" : null}>
          <input className="inp num" value={txnNo} onChange={(e) => setTxnNo(e.target.value)} placeholder="转账流水/订单号" />
        </Field>
        <Field label="收款账户" hint="可选">
          <input className="inp" value={account} onChange={(e) => setAccount(e.target.value)} placeholder="如 招商银行 ****6688" />
        </Field>
      </div>
      <Field label="备注" hint="可选">
        <input className="inp" value={note} onChange={(e) => setNote(e.target.value)} placeholder="补充说明" />
      </Field>
      <Field label="凭证附件" hint="转账截图、收据等，保存在当前浏览器本地（≤5MB）" error={errors.attachment}>
        <label className={"upload-zone " + (attachment ? "uploaded" : "")}>
          <Icon name={attachment ? (attachment.kind === "image" ? "image" : "file") : "upload"} size={18} />
          {attachment ? `${attachment.name} · ${attachment.size || "凭证"}` : "点击选择文件（jpg/png/pdf，≤5MB）"}
          <input type="file" accept="image/*,.pdf" style={{ display: "none" }} onChange={async (e) => {
            try {
              const att = await readAttachment(e.target.files && e.target.files[0]);
              if (att) setAttachment(att);
              setErrors((prev) => ({ ...prev, attachment: "" }));
            } catch (err) {
              setErrors((prev) => ({ ...prev, attachment: err.message || "附件读取失败" }));
            }
            e.target.value = "";
          }} />
        </label>
        {attachment && <div className="attach-actions"><button className="link" onClick={() => window.downloadLedgerAttachment(attachment)}>下载/查看</button><button className="link danger" onClick={() => setAttachment(null)}>移除凭证</button></div>}
      </Field>

      {confirmOver && (
        <div className="inline-confirm">
          <Icon name="alert" size={16} />
          <span>本次还款超过剩余待还，确认后将标记为多还款。再次点击「保存还款」即确认。</span>
        </div>
      )}
    </Modal>
  );
}

/* ===================== 新增 / 编辑 欠款人 ===================== */
function DebtorModal({ state, actions, onClose, edit, toast }) {
  const [f, setF] = React.useState(edit || { name: "", gender: "", birthYear: "", phone: "", wechat: "", idcard: "", address: "", relation: "朋友", note: "" });
  const [errors, setErrors] = React.useState({});
  const set = (k) => (e) => setF({ ...f, [k]: e.target.value });
  function save() {
    if (!f.name.trim()) { setErrors({ name: "请填写姓名或称呼" }); return; }
    actions.upsertDebtor(f);
    toast({ msg: edit ? "已更新欠款人信息" : `已新增欠款人 ${f.name}`, tone: "success" });
    onClose();
  }
  return (
    <Modal title={edit ? "编辑欠款人" : "新增欠款人"} onClose={onClose} size="md"
      footer={<><div /><div className="foot-btns"><Btn kind="ghost" onClick={onClose}>取消</Btn><Btn kind="primary" icon="check" onClick={save}>保存</Btn></div></>}>
      <Field label="姓名 / 称呼" required error={errors.name}><input className="inp" value={f.name} onChange={set("name")} placeholder="如 但静姝" autoFocus /></Field>
      <div className="form-grid">
        <Field label="性别"><select className="inp" value={f.gender || ""} onChange={set("gender")}><option value="">未填写</option><option>女</option><option>男</option><option>其他</option></select></Field>
        <Field label="出生年份"><input className="inp num" inputMode="numeric" value={f.birthYear || ""} onChange={set("birthYear")} placeholder="如 1984" /></Field>
      </div>
      <div className="form-grid">
        <Field label="手机号"><input className="inp num" value={f.phone} onChange={set("phone")} placeholder="选填" /></Field>
        <Field label="微信号"><input className="inp" value={f.wechat} onChange={set("wechat")} placeholder="选填" /></Field>
      </div>
      <div className="form-grid">
        <Field label="关系标签">
          <select className="inp" value={f.relation} onChange={set("relation")}>{state.relations.map((r) => <option key={r}>{r}</option>)}</select>
        </Field>
        <Field label="身份证号" hint="敏感字段，将自动遮罩"><input className="inp num" value={f.idcard} onChange={set("idcard")} placeholder="选填" /></Field>
      </div>
      <Field label="地址"><input className="inp" value={f.address} onChange={set("address")} placeholder="选填" /></Field>
      <Field label="备注"><input className="inp" value={f.note} onChange={set("note")} placeholder="选填" /></Field>
    </Modal>
  );
}

/* ===================== 新增 / 编辑 债务 ===================== */
function DebtModal({ state, actions, onClose, presetDebtorId, edit, toast }) {
  const init = edit || { title: "", debtorId: presetDebtorId || "", principal: "", occurDate: window.Ledger.fmtDate(window.Ledger.TODAY), dueDate: "", type: "借款", interest: false, interestNote: "", source: "", note: "" };
  const [f, setF] = React.useState(edit ? { ...edit, principal: fenToYuanNum(edit.principalFen) } : init);
  const [attachments, setAttachments] = React.useState(edit?.attachments || []);
  const [errors, setErrors] = React.useState({});
  const set = (k) => (e) => setF({ ...f, [k]: e.target.value });
  function save() {
    const e = {};
    if (!f.title.trim()) e.title = "请填写债务标题";
    if (!f.debtorId) e.debtorId = "请选择欠款人";
    if (!f.principal || yuanToFen(f.principal) <= 0) e.principal = "原始应还金额必须大于 0";
    if (!f.occurDate) e.occurDate = "请填写债务发生日期";
    setErrors(e);
    if (Object.keys(e).length) return;
    actions.upsertDebt({ ...f, principalFen: yuanToFen(f.principal), amountPending: false, attachments });
    toast({ msg: edit ? "已更新债务信息" : `已新增债务「${f.title}」`, tone: "success" });
    onClose();
  }
  return (
    <Modal title={edit ? "编辑债务" : "新增债务"} onClose={onClose} size="lg"
      footer={<><div /><div className="foot-btns"><Btn kind="ghost" onClick={onClose}>取消</Btn><Btn kind="primary" icon="check" onClick={save}>保存</Btn></div></>}>
      <Field label="债务标题" required error={errors.title}><input className="inp" value={f.title} onChange={set("title")} placeholder="如 2026年5月借款" autoFocus /></Field>
      <div className="form-grid">
        <Field label="欠款人" required error={errors.debtorId}>
          <select className="inp" value={f.debtorId} onChange={set("debtorId")} disabled={!!presetDebtorId}>
            <option value="">请选择</option>
            {state.debtors.map((d) => <option key={d.id} value={d.id}>{d.name}</option>)}
          </select>
        </Field>
        <Field label="债务类型"><select className="inp" value={f.type} onChange={set("type")}>{state.debtTypes.map((t) => <option key={t}>{t}</option>)}</select></Field>
      </div>
      <div className="form-grid">
        <Field label="原始应还金额" required error={errors.principal}>
          <div className="inp-money"><span>¥</span><input className="inp num" inputMode="decimal" value={f.principal} onChange={(e) => setF({ ...f, principal: e.target.value.replace(/[^\d.]/g, "") })} placeholder="0.00" /></div>
        </Field>
        <Field label="债务发生日期" required error={errors.occurDate}><input type="date" className="inp" value={f.occurDate} onChange={set("occurDate")} /></Field>
      </div>
      <div className="form-grid">
        <Field label="约定还款日期" hint="用于到期与逾期提醒"><input type="date" className="inp" value={f.dueDate} onChange={set("dueDate")} /></Field>
        <Field label="是否计息">
          <label className="switch-row"><input type="checkbox" checked={f.interest} onChange={(e) => setF({ ...f, interest: e.target.checked })} /><span>计息（一期仅记录说明）</span></label>
        </Field>
      </div>
      {f.interest && <Field label="利息说明"><input className="inp" value={f.interestNote} onChange={set("interestNote")} placeholder="如 按月 0.5%，仅记录不自动计算" /></Field>}
      <Field label="债务来源说明"><input className="inp" value={f.source} onChange={set("source")} placeholder="选填" /></Field>
      <Field label="备注"><input className="inp" value={f.note} onChange={set("note")} placeholder="选填" /></Field>
      <Field label="附件" hint="合同、借条、聊天截图等，保存在当前浏览器本地（单个≤5MB）" error={errors.attachments}>
        <label className={"upload-zone " + (attachments.length ? "uploaded" : "")}>
          <Icon name={attachments.length ? "checkCircle" : "upload"} size={18} />
          {attachments.length ? `已添加 ${attachments.length} 个附件，点击继续添加` : "点击选择附件"}
          <input type="file" accept="image/*,.pdf" multiple style={{ display: "none" }} onChange={async (e) => {
            try {
              const files = Array.from(e.target.files || []);
              const list = [];
              for (const file of files) list.push(await readAttachment(file));
              setAttachments([...attachments, ...list.filter(Boolean)]);
              setErrors((prev) => ({ ...prev, attachments: "" }));
            } catch (err) {
              setErrors((prev) => ({ ...prev, attachments: err.message || "附件读取失败" }));
            }
            e.target.value = "";
          }} />
        </label>
        {attachments.length > 0 && (
          <div className="attach-list compact">
            {attachments.map((a, i) => (
              <div className="attach-item" key={a.id || a.name || i}>
                <span className="attach-thumb"><Icon name={a.kind === "image" ? "image" : "file"} size={16} /></span>
                <div className="attach-info"><div className="attach-name">{a.name}</div><div className="muted xs">{a.size}</div></div>
                <button className="iconbtn-xs" onClick={() => window.downloadLedgerAttachment(a)}><Icon name="download" size={14} /></button>
                <button className="iconbtn-xs" onClick={() => setAttachments(attachments.filter((_, idx) => idx !== i))}><Icon name="x" size={14} /></button>
              </div>
            ))}
          </div>
        )}
      </Field>
    </Modal>
  );
}

/* ===================== 金额调整 ===================== */
function AdjustModal({ state, actions, onClose, debtId, toast }) {
  const calc = window.Ledger.debtCalc(state, debtId);
  const [type, setType] = React.useState("increase");
  const [amount, setAmount] = React.useState("");
  const [date, setDate] = React.useState(window.Ledger.fmtDate(window.Ledger.TODAY));
  const [reason, setReason] = React.useState("");
  const [errors, setErrors] = React.useState({});
  const amtFen = yuanToFen(amount);
  const after = type === "increase" ? calc.currentDue + amtFen : calc.currentDue - amtFen;
  function save() {
    const e = {};
    if (!amount || amtFen <= 0) e.amount = "金额必须大于 0";
    if (!reason.trim()) e.reason = "请填写调整原因";
    if (!date) e.date = "请填写日期";
    setErrors(e);
    if (Object.keys(e).length) return;
    actions.addAdjustment({ debtId, type, amountFen: amtFen, date, reason });
    toast({ msg: `已${type === "increase" ? "增加" : "减免"}应还 ${money(amtFen)}`, tone: "success" });
    onClose();
  }
  return (
    <Modal title="金额调整" desc="用于追加债务、债务减免等非还款的金额变化" onClose={onClose} size="md"
      footer={<><div className="foot-info num">调整后当前应还：<b>{money(after)}</b></div><div className="foot-btns"><Btn kind="ghost" onClick={onClose}>取消</Btn><Btn kind="primary" icon="check" onClick={save}>确认调整</Btn></div></>}>
      <Field label="调整类型" required>
        <div className="seg seg-2">
          <button className={"seg-btn " + (type === "increase" ? "active" : "")} onClick={() => setType("increase")}>增加应还</button>
          <button className={"seg-btn " + (type === "reduce" ? "active" : "")} onClick={() => setType("reduce")}>减免应还</button>
        </div>
      </Field>
      <div className="form-grid">
        <Field label="调整金额" required error={errors.amount}>
          <div className="inp-money"><span>¥</span><input className="inp num" inputMode="decimal" value={amount} onChange={(e) => setAmount(e.target.value.replace(/[^\d.]/g, ""))} placeholder="0.00" autoFocus /></div>
        </Field>
        <Field label="调整日期" required error={errors.date}><input type="date" className="inp" value={date} onChange={(e) => setDate(e.target.value)} /></Field>
      </div>
      <Field label="调整原因" required error={errors.reason}><input className="inp" value={reason} onChange={(e) => setReason(e.target.value)} placeholder="如 追加借款 / 协商减免" /></Field>
    </Modal>
  );
}

/* ===================== 作废确认 ===================== */
function VoidModal({ state, actions, onClose, payment, toast }) {
  const [reason, setReason] = React.useState("");
  const [err, setErr] = React.useState("");
  const calc = window.Ledger.debtCalc(state, payment.debtId);
  const debt = state.debts.find((d) => d.id === payment.debtId);
  function confirm() {
    if (!reason.trim()) { setErr("作废必须填写原因"); return; }
    actions.voidPayment(payment.id, reason);
    toast({ msg: "已作废该还款记录，相关汇总已重算", tone: "success" });
    onClose();
  }
  return (
    <Modal title="作废还款记录" danger onClose={onClose} size="md"
      footer={<><div /><div className="foot-btns"><Btn kind="ghost" onClick={onClose}>取消</Btn><Btn kind="primary" danger icon="trash" onClick={confirm}>确认作废</Btn></div></>}>
      <div className="danger-box">
        <Icon name="alert" size={18} />
        <div>
          <div className="db-title">此操作将影响以下汇总金额</div>
          <div className="db-list num">
            <div>债务「{debt.title}」累计已还将减少 <b>{money(payment.amountFen)}</b></div>
            <div>该债务剩余待还将由 {money(calc.remaining)} 变为 <b>{money(calc.remaining + payment.amountFen)}</b></div>
            <div className="muted">记录将保留在历史中并标注「已作废」，不参与统计。</div>
          </div>
        </div>
      </div>
      <Field label="作废原因" required error={err}><input className="inp" value={reason} onChange={(e) => setReason(e.target.value)} placeholder="如 重复记录 / 实际未到账" autoFocus /></Field>
    </Modal>
  );
}

/* ===================== 导出 ===================== */
function ExportModal({ state, onClose, scope, toast }) {
  const [sel, setSel] = React.useState(scope === "当前筛选结果"
    ? { debtors: false, debts: false, payments: true, adjustments: false }
    : { debtors: true, debts: true, payments: true, adjustments: false });
  const [busy, setBusy] = React.useState(false);
  function toggle(k) { setSel({ ...sel, [k]: !sel[k] }); }
  function run() {
    setBusy(true);
    setTimeout(() => {
      const csv = window.buildExportCSV(state, sel);
      const blob = new Blob(["\ufeff" + csv], { type: "text/csv;charset=utf-8" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url; a.download = `债务明细导出_${window.Ledger.fmtDate(window.Ledger.TODAY)}.csv`;
      a.click(); URL.revokeObjectURL(url);
      setBusy(false);
      toast({ msg: "导出文件已生成并开始下载", tone: "success" });
      onClose();
    }, 600);
  }
  const items = [
    { k: "debtors", label: "欠款人汇总", n: state.debtors.length },
    { k: "debts", label: "债务汇总", n: state.debts.length },
    { k: "payments", label: "还款明细", n: state.payments.length },
    { k: "adjustments", label: "金额调整明细", n: state.adjustments.length },
  ];
  return (
    <Modal title="导出数据" desc={scope ? `导出范围：${scope}` : "导出当前数据，格式为 CSV（可用 Excel 打开）"} onClose={onClose} size="md"
      footer={<><div className="foot-info muted">数据更新时间：{fmtDateTime(state.meta.updatedAt)}</div><div className="foot-btns"><Btn kind="ghost" onClick={onClose}>取消</Btn><Btn kind="primary" icon="download" onClick={run} disabled={busy}>{busy ? "生成中…" : "导出"}</Btn></div></>}>
      <div className="export-list">
        {items.map((it) => (
          <label className={"export-item " + (sel[it.k] ? "on" : "")} key={it.k}>
            <input type="checkbox" checked={sel[it.k]} onChange={() => toggle(it.k)} />
            <span className="ei-check"><Icon name="check" size={13} /></span>
            <span className="ei-label">{it.label}</span>
            <span className="ei-n num muted">{it.n} 条</span>
          </label>
        ))}
      </div>
    </Modal>
  );
}

/* ===================== 恢复备份确认 ===================== */
function RestoreModal({ actions, onClose, toast }) {
  const [text, setText] = React.useState("");
  const [data, setData] = React.useState(null);
  const [summary, setSummary] = React.useState(null);
  const [fileName, setFileName] = React.useState("");
  const [error, setError] = React.useState("");
  const ok = text.trim() === "恢复" && !!data;
  function chooseFile(e) {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    setFileName(file.name);
    setError("");
    const reader = new FileReader();
    reader.onload = () => {
      try {
        const parsed = JSON.parse(reader.result);
        const check = window.Ledger.validateBackup(parsed);
        if (!check.ok) throw new Error(check.errors.slice(0, 6).join("；"));
        const migrated = window.Ledger.migrateState(parsed);
        setData(migrated);
        setSummary({
          debtors: migrated.debtors.length,
          debts: migrated.debts.length,
          payments: migrated.payments.length,
          adjustments: migrated.adjustments.length,
          version: migrated.meta?.dataVersion || "旧版本",
          backupAt: migrated.meta?.backupAt || migrated.meta?.updatedAt || "",
        });
      } catch (_) {
        setData(null);
        setSummary(null);
        setError(_.message ? `备份校验失败：${_.message}` : "无法识别该备份文件，请选择网站导出的 JSON 备份");
      }
    };
    reader.readAsText(file);
  }
  function restore() {
    try {
      actions.restoreData(data);
      toast({ msg: "备份恢复成功", tone: "success" });
      onClose();
    } catch (e) {
      setError(e.message || "恢复失败");
    }
  }
  return (
    <Modal title="从备份恢复数据" danger onClose={onClose} size="md"
      footer={<><div /><div className="foot-btns"><Btn kind="ghost" onClick={onClose}>取消</Btn><Btn kind="primary" danger icon="upload" disabled={!ok} onClick={restore}>确认恢复</Btn></div></>}>
      <div className="danger-box">
        <Icon name="alert" size={18} />
        <div>
          <div className="db-title">高风险操作</div>
          <div className="db-list">恢复将以备份数据覆盖当前全部记录，覆盖后无法撤销。操作前请先下载当前数据备份。</div>
        </div>
      </div>
      <Field label="选择 JSON 备份文件" required error={error}>
        <label className={"upload-zone " + (data ? "uploaded" : "")}>
          <Icon name={data ? "checkCircle" : "upload"} size={18} />
          {data ? `已读取：${fileName}` : "点击选择网站导出的 JSON 备份"}
          <input type="file" accept=".json,application/json" onChange={chooseFile} style={{ display: "none" }} />
        </label>
      </Field>
      {summary && (
        <div className="restore-summary">
          <div className="rs-title">备份校验通过</div>
          <div className="rs-grid">
            <span>欠款人：<b>{summary.debtors}</b></span>
            <span>债务：<b>{summary.debts}</b></span>
            <span>还款：<b>{summary.payments}</b></span>
            <span>调整：<b>{summary.adjustments}</b></span>
          </div>
          <div className="muted xs">数据版本：{summary.version}{summary.backupAt ? ` · 备份时间：${fmtDateTime(summary.backupAt)}` : ""}</div>
        </div>
      )}
      <Field label={'请输入「恢复」以确认'} required><input className="inp" value={text} onChange={(e) => setText(e.target.value)} placeholder="恢复" /></Field>
    </Modal>
  );
}

/* ===================== 修改密码 ===================== */
function PasswordModal({ actions, onClose, toast }) {
  const [oldPwd, setOldPwd] = React.useState("");
  const [newPwd, setNewPwd] = React.useState("");
  const [newPwd2, setNewPwd2] = React.useState("");
  const [errors, setErrors] = React.useState({});
  function save() {
    const e = {};
    if (!oldPwd) e.oldPwd = "请输入当前密码";
    if (!newPwd || newPwd.length < 6) e.newPwd = "新密码至少 6 位";
    if (newPwd !== newPwd2) e.newPwd2 = "两次新密码不一致";
    setErrors(e);
    if (Object.keys(e).length) return;
    const res = actions.changePassword(oldPwd, newPwd);
    if (!res.ok) {
      setErrors({ oldPwd: res.message || "修改失败" });
      return;
    }
    toast({ msg: "密码已修改，下次登录请使用新密码", tone: "success" });
    onClose();
  }
  return (
    <Modal title="修改登录密码" desc="密码保存在当前浏览器本地，用于阻止普通访问；不是服务端级认证。" onClose={onClose} size="md"
      footer={<><div className="foot-info muted">建议立即备份数据，避免清除浏览器数据后丢失。</div><div className="foot-btns"><Btn kind="ghost" onClick={onClose}>取消</Btn><Btn kind="primary" icon="lock" onClick={save}>保存新密码</Btn></div></>}>
      <Field label="当前密码" required error={errors.oldPwd}><input className="inp" type="password" value={oldPwd} onChange={(e) => setOldPwd(e.target.value)} autoFocus /></Field>
      <Field label="新密码" required error={errors.newPwd}><input className="inp" type="password" value={newPwd} onChange={(e) => setNewPwd(e.target.value)} placeholder="至少 6 位" /></Field>
      <Field label="再次输入新密码" required error={errors.newPwd2}><input className="inp" type="password" value={newPwd2} onChange={(e) => setNewPwd2(e.target.value)} /></Field>
    </Modal>
  );
}

Object.assign(window, { PaymentModal, DebtorModal, DebtModal, AdjustModal, VoidModal, ExportModal, RestoreModal, PasswordModal });
