// 정산 관리 — 실서버 /admin/settlement (backend V25, 2026-09-23)
//
// 회차는 **가게 단위**입니다(가게 하나 × 한 주 = 회차 하나). 화면은 주차 목록 → 그 주차의 가게별 정산
// → 회차 상세(주문·차감·이력) 순서로 들어갑니다.
//
// 운영자가 하는 일: 주차를 확정하고, 은행 이체는 손으로 한 뒤, 지급 완료를 **수동으로 표시**합니다.
// 지급이 실패하거나 계좌가 없으면 보류·지급 실패로 남깁니다. 되돌릴 수 없는 조작이라 모두 확인창을 거칩니다.
//
// 돈에 관한 값은 서버가 주는 것만 씁니다. PG 결제대행수수료는 실제 청구액이 들어온 건만 오고
// (pgFeeStatus PENDING 이면 pgFeeAmount·payoutAmount 가 둘 다 null), 그때는 화면에서 **계산하지 않고**
// "확인 중"으로 비웁니다. 예전 목 화면이 3.5% 로 계산해 보여 주던 값이 바로 이 자리입니다.

const SETTLEMENT_PAGE_SIZE = 20;

// 서버 필드 읽기 — 한 곳에 모았습니다.
const SF = {
  id:        (r) => r.cycleId,
  store:     (r) => r.storeName,
  storeId:   (r) => r.storeId,
  status:    (r) => r.status,
  sales:     (r) => r.salesAmount,
  orders:    (r) => r.orderCount,
  fee:       (r) => r.commissionAmount,
  feeVat:    (r) => r.commissionVatAmount,
  pgFee:     (r) => r.pgFeeAmount,
  pgStatus:  (r) => r.pgFeeStatus,
  deduction: (r) => r.deductionAmount,
  carryIn:   (r) => r.carryInAmount,
  carryOut:  (r) => r.carryOutAmount,
  payout:    (r) => r.payoutAmount,
  // 회차의 payout* 세 필드는 **지급 시점 스냅샷**입니다(지급할 때 어느 계좌로 보냈는지 박제, V25).
  // 지급 전(SCHEDULED·CONFIRMED·HOLD)에는 늘 null 이라, 현재 계좌를 보여 주는 자리에 쓰면 빈 칸이 됩니다.
  // 현재 계좌는 상세 응답에서 **cycle 과 형제인 `account` 객체**에 있습니다(cycle 안이 아닙니다 — dev 실측).
  // 아래 함수들은 그 account 객체를 인자로 받고, 없으면(구버전 응답) 스냅샷으로 떨어집니다.
  accountOk: (c, a) => (a ? a.registered === true : c.accountRegistered === true),
  bank:      (c, a) => (a ? a.bankCode : c.payoutBankCode),
  account:   (c, a) => (a ? a.accountMasked : c.payoutAccountMasked),
  holder:    (c, a) => (a ? a.accountHolder : c.payoutAccountHolder),
  // 계좌 변경 확인 기간 — 계좌를 열람하지 않아도 보류 사유를 알 수 있습니다.
  acctReview:      (a) => !!(a && a.changeUnderReview === true),
  acctReviewUntil: (a) => (a ? a.changeReviewUntil : null),
  // 지급 시 계좌(스냅샷) — 지급 완료된 회차에서만 값이 있습니다.
  paidAccount: (c) => c.payoutAccountMasked,
  paidAt:    (r) => r.paidOutAt,
  paidBy:    (r) => r.paidBy,
  closedAt:  (r) => r.closedAt,
};

// 금액이 아직 없을 때(PG 청구액 대기) 숫자를 지어내지 않고 이유를 보여 줍니다.
const Money = ({ value, pending = 'PG 결제대행수수료 확인 중', bold = false }) => (
  value == null
    ? <span style={{ color: 'var(--text-3)', fontSize: 12.5 }} title="서버에 아직 값이 없습니다. 화면에서 계산하지 않습니다.">{pending}</span>
    : <span style={{ fontWeight: bold ? 700 : 400 }}>{fmtKRW(value)}</span>
);
const Minus = ({ value }) => (
  !value ? <span style={{ color: 'var(--text-3)' }}>—</span> : <span>-{fmtKRW(value)}</span>
);

// 은행 이름표는 한 번만 받아 화면 전체에서 같은 이름을 씁니다. 실패하면 화면 상수로 떨어지고,
// 그때는 코드로 보일 수 있으므로 계좌 칸에 안내를 띄웁니다(조용히 다른 이름을 보여 주지 않으려고).
const useBankNames = () => {
  const [map, setMap] = React.useState(null);
  const [failed, setFailed] = React.useState(false);
  React.useEffect(() => {
    let alive = true;
    api.live.settlement.banks()
      .then((list) => { if (alive) setMap(Object.fromEntries((list || []).map((b) => [b.code, b.name]))); })
      .catch(() => { if (alive) { setMap({}); setFailed(true); } });
    return () => { alive = false; };
  }, []);
  return { map, failed };
};

const Settlements = () => {
  const [week, setWeek] = React.useState(null);      // periodStart
  return week
    ? <SettlementWeek periodStart={week} onBack={() => setWeek(null)}/>
    : <SettlementWeeks onOpen={setWeek}/>;
};

// ── 주차 목록 ────────────────────────────────────────────────────────────────
const SettlementWeeks = ({ onOpen }) => {
  const [page, setPage] = React.useState(1);
  const weeks = useLive(() => api.live.settlement.weeks({ page, size: SETTLEMENT_PAGE_SIZE }), [page]);
  const rows = (weeks.data && weeks.data.list) || [];
  const total = Number((weeks.data && weeks.data.totalCount) || 0);
  const lastPage = Math.max(1, Math.ceil(total / SETTLEMENT_PAGE_SIZE));

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1>정산 관리</h1>
          <div className="sub">월~일 매출을 다음 주에 정산합니다 · 주차를 골라 매장별 정산을 확인하세요</div>
        </div>
        <div className="actions">
          <button className="btn btn-ghost btn-sm" onClick={weeks.reload} disabled={weeks.loading}><Icon name="refresh" size={14}/>새로고침</button>
        </div>
      </div>

      <div className="table-wrap" data-loading={weeks.loading ? 'true' : 'false'}>
        <LiveBlock state={weeks} empty={rows.length === 0} emptyTitle="아직 마감된 정산 주차가 없습니다">
          <table className="table">
            <thead><tr>
              <th>정산 주차</th><th>지급 예정일</th><th>매장</th><th>매출</th>
              <th>중개수수료</th><th>차감</th><th>실지급액</th><th>상태</th>
            </tr></thead>
            <tbody>
              {rows.map((w) => (
                <tr key={w.periodStart} onClick={() => onOpen(w.periodStart)} style={{ cursor:'pointer' }}>
                  <td style={{ fontWeight:600 }}>{ymd(w.periodStart)} ~ {ymd(w.periodEnd)}</td>
                  <td>{w.payoutDate ? ymd(w.payoutDate) : '—'}</td>
                  <td>{fmtNum(w.storeCount)}곳</td>
                  <td>{fmtKRW(w.salesAmount)}</td>
                  <td><Minus value={(w.commissionAmount || 0) + (w.commissionVatAmount || 0)}/></td>
                  <td><Minus value={w.deductionAmount}/></td>
                  <td><Money value={w.payoutAmount} bold/></td>
                  <td><WeekStatus w={w}/></td>
                </tr>
              ))}
            </tbody>
          </table>
        </LiveBlock>
        <div className="table-foot">
          <span style={{ fontSize:13, color:'var(--text-3)' }}>총 {total}개 주차 · {page}/{lastPage}페이지 (최신 주차 먼저)</span>
          <div style={{ display:'flex', gap:6 }}>
            <button className="btn btn-ghost btn-sm" disabled={page <= 1 || weeks.loading} onClick={() => setPage((p) => p - 1)}><Icon name="chevLeft" size={14}/>이전</button>
            <button className="btn btn-ghost btn-sm" disabled={page >= lastPage || weeks.loading} onClick={() => setPage((p) => p + 1)}>다음<Icon name="chevRight" size={14}/></button>
          </div>
        </div>
      </div>
    </div>
  );
};

// 주차 한 줄의 상태 요약 — 서버가 준 건수만 그립니다.
const WeekStatus = ({ w }) => {
  const parts = [
    w.scheduledCount ? { k: 'SCHEDULED', n: w.scheduledCount } : null,
    w.confirmedCount ? { k: 'CONFIRMED', n: w.confirmedCount } : null,
    w.paidCount ? { k: 'PAID', n: w.paidCount } : null,
    w.holdCount ? { k: 'HOLD', n: w.holdCount } : null,
    w.failedCount ? { k: 'FAILED', n: w.failedCount } : null,
  ].filter(Boolean);
  if (parts.length === 0) return <span style={{ color:'var(--text-3)' }}>—</span>;
  return (
    <div style={{ display:'flex', gap:4, flexWrap:'wrap' }}>
      {parts.map((p) => (
        <span key={p.k} style={{ fontSize:12 }}>
          <StatusBadge map={SETTLEMENT_STATUS} value={p.k}/> {p.n}
        </span>
      ))}
    </div>
  );
};

// ── 주차 안의 매장별 정산 ────────────────────────────────────────────────────
const SettlementWeek = ({ periodStart, onBack }) => {
  const [status, setStatus] = React.useState('');
  const [page, setPage] = React.useState(1);
  const [openId, setOpenId] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [notice, setNotice] = React.useState(null);   // { tone, text }
  const query = React.useMemo(() => {
    const q = { page, size: SETTLEMENT_PAGE_SIZE };
    if (status) q.status = status;
    return q;
  }, [status, page]);
  const list = useLive(() => api.live.settlement.weekStores(periodStart, query), [periodStart, JSON.stringify(query)]);
  const rows = (list.data && list.data.list) || [];
  const total = Number((list.data && list.data.totalCount) || 0);
  const lastPage = Math.max(1, Math.ceil(total / SETTLEMENT_PAGE_SIZE));
  React.useEffect(() => { setPage(1); }, [status]);
  const scheduled = rows.filter((r) => SF.status(r) === 'SCHEDULED').length;

  const confirmWeek = async () => {
    if (!window.confirm(`${ymd(periodStart)} 주차에서 아직 확정되지 않은 정산을 한 번에 확정할까요?\n보류·지급 실패·이미 확정된 건은 건너뜁니다. 확정하면 금액이 고정됩니다.`)) return;
    setBusy(true); setNotice(null);
    try {
      const r = await api.live.settlement.confirmWeek(periodStart, '');
      const done = r && r.confirmedCount != null ? r.confirmedCount : 0;
      const skipped = (r && r.skipped) || [];
      setNotice({
        tone: 'info',
        text: `${fmtNum(done)}건 확정했습니다.` + (skipped.length
          ? ` ${skipped.length}건은 건너뛰었습니다: ${skipped.map((s) => `${s.storeName || s.storeId}(${s.reason || '사유 없음'})`).join(', ')}`
          : ''),
      });
      list.reload();
    } catch (e) { setNotice({ tone: 'danger', text: `확정하지 못했습니다: ${errorText(e)}` }); }
    finally { setBusy(false); }
  };

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <button className="btn btn-ghost btn-sm" onClick={onBack} style={{ marginBottom:8 }}><Icon name="chevLeft" size={14}/>주차 목록</button>
          <h1>{ymd(periodStart)} 주차 정산</h1>
          <div className="sub">매장별 정산입니다 · 확정 뒤 이체하고 지급 완료를 표시하세요</div>
        </div>
        <div className="actions">
          <button className="btn btn-ghost btn-sm" onClick={list.reload} disabled={list.loading}><Icon name="refresh" size={14}/>새로고침</button>
          {scheduled > 0 && (
            <button className="btn btn-primary btn-sm" onClick={confirmWeek} disabled={busy}>
              {busy ? '확정 중…' : `이 주차 확정 (${scheduled}건)`}
            </button>
          )}
        </div>
      </div>
      {notice && <div style={{ marginBottom:12 }}><Notice tone={notice.tone}>{notice.text}</Notice></div>}

      <div className="table-wrap" data-loading={list.loading ? 'true' : 'false'}>
        <Tabs items={SETTLEMENT_TABS} value={status} onChange={setStatus}/>
        <LiveBlock state={list} empty={rows.length === 0} emptyTitle="조건에 맞는 정산이 없습니다">
          <table className="table" style={{ opacity: list.loading ? 0.45 : 1 }}>
            <thead><tr>
              <th>매장</th><th>주문</th><th>매출</th><th>중개수수료</th>
              <th>PG 결제대행수수료</th><th>차감</th><th>이월</th><th>실지급액</th><th>정산계좌</th><th>상태</th>
            </tr></thead>
            <tbody>
              {rows.map((r) => (
                <tr key={SF.id(r)} onClick={() => setOpenId(SF.id(r))} style={{ cursor:'pointer' }}>
                  <td style={{ fontWeight:600 }}>{SF.store(r) || '—'}<span className="t-mono" style={{ color:'var(--text-3)', fontWeight:400 }}> #{SF.storeId(r)}</span></td>
                  <td>{fmtNum(SF.orders(r))}건</td>
                  <td>{fmtKRW(SF.sales(r))}</td>
                  <td><Minus value={(SF.fee(r) || 0) + (SF.feeVat(r) || 0)}/></td>
                  <td>{SF.pgFee(r) == null ? <span style={{ color:'var(--text-3)', fontSize:12.5 }}>확인 중</span> : <Minus value={SF.pgFee(r)}/>}</td>
                  <td><Minus value={SF.deduction(r)}/></td>
                  <td>{SF.carryIn(r) ? <Minus value={-SF.carryIn(r)}/> : <span style={{ color:'var(--text-3)' }}>—</span>}</td>
                  <td><Money value={SF.payout(r)} bold/></td>
                  {/* 목록에는 account 객체가 없고 스냅샷뿐이라 등록 여부만 보여 줍니다(지급 전에는 번호가 null). */}
                  <td>{SF.accountOk(r, null)
                    ? <span style={{ fontSize:12.5 }}>{SF.paidAccount(r) || '등록됨'}</span>
                    : <span style={{ fontSize:12.5, color:'var(--danger)' }}>미등록</span>}</td>
                  <td><StatusBadge map={SETTLEMENT_STATUS} value={SF.status(r)}/></td>
                </tr>
              ))}
            </tbody>
          </table>
        </LiveBlock>
        <div className="table-foot">
          <span style={{ fontSize:13, color:'var(--text-3)' }}>총 {total}곳 · {page}/{lastPage}페이지</span>
          <div style={{ display:'flex', gap:6 }}>
            <button className="btn btn-ghost btn-sm" disabled={page <= 1 || list.loading} onClick={() => setPage((p) => p - 1)}><Icon name="chevLeft" size={14}/>이전</button>
            <button className="btn btn-ghost btn-sm" disabled={page >= lastPage || list.loading} onClick={() => setPage((p) => p + 1)}>다음<Icon name="chevRight" size={14}/></button>
          </div>
        </div>
      </div>

      {openId != null && (
        <SettlementDrawer cycleId={openId} onClose={() => setOpenId(null)} onChanged={list.reload}/>
      )}
    </div>
  );
};

// ── 회차 상세 ────────────────────────────────────────────────────────────────
const SettlementDrawer = ({ cycleId, onClose, onChanged }) => {
  const detail = useLive(() => api.live.settlement.get(cycleId), [cycleId]);
  const banks = useBankNames();
  const items = useLive(() => api.live.settlement.items(cycleId, { page: 1, size: 100 }), [cycleId]);
  // 계좌 열람 기록(이 회차). "기록에 남는다"고만 적고 아무도 못 보면 억제력이 없어서 상세에 노출합니다.
  const accessLogs = useLive(() => api.live.settlement.accountAccessLogs({ cycleId, page: 1, size: 3 }), [cycleId]);
  // 제출 서류(통장 사본). 회차 상세가 사업자를 알려 줍니다(cycle.businessId, 2026-09-27 추가).
  // 값이 없으면 사업자가 연결되지 않은 것이라 블록이 그 사실을 그립니다 — 서류 없음과 다릅니다.
  const c = (detail.data && detail.data.cycle) || null;
  const acct = (detail.data && detail.data.account) || null;   // cycle 과 형제입니다
  const adjustments = (detail.data && detail.data.adjustments) || [];
  const histories = (detail.data && detail.data.histories) || [];
  const st = c ? SF.status(c) : null;
  const docBusinessId = (c && c.businessId) != null ? c.businessId : null;
  const docs = useMerchantDocuments(docBusinessId);
  const [account, setAccount] = React.useState(null);     // 열람한 전체 계좌(닫으면 사라집니다)
  const [askAccount, setAskAccount] = React.useState(false);
  const [accountReason, setAccountReason] = React.useState('');
  const [accountBusy, setAccountBusy] = React.useState(false);
  const [accountError, setAccountError] = React.useState('');
  const [next, setNext] = React.useState('');
  const [memo, setMemo] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState('');
  const options = (c && SETTLEMENT_NEXT[st]) || [];
  const memoRequired = next === 'HOLD' || next === 'FAILED';
  const canSubmit = !!next && (!memoRequired || memo.trim()) && !busy;

  const submit = async () => {
    if (!canSubmit) return;
    const label = (SETTLEMENT_STATUS[next] || {}).label || next;
    const msg = next === 'PAID'
      ? `이 정산을 "지급 완료"로 표시할까요?\n실제 이체가 끝난 뒤에만 표시하세요. 되돌릴 수 없습니다.`
      : `상태를 "${label}"(으)로 바꿀까요? 메모가 이력에 남습니다.`;
    if (!window.confirm(msg)) return;
    setBusy(true); setError('');
    try {
      // 확정 전용 경로(/confirm)는 "정산 예정" 회차만 받습니다. 보류·지급 실패에서 확정으로 되돌릴 때는
      // 상태 변경 경로(/status)를 써야 합니다 — 전자를 쓰면 서버가 "지금 상태에서는 바꿀 수 없습니다"로
      // 거절합니다(2026-09-23 dev 확인). 같은 버튼이라도 지금 상태에 따라 경로가 다릅니다.
      if (next === 'CONFIRMED' && st === 'SCHEDULED') await api.live.settlement.confirm(cycleId, memo.trim());
      else await api.live.settlement.setStatus(cycleId, next, memo.trim());
      setNext(''); setMemo('');
      detail.reload(); onChanged();      // 서버 값을 다시 읽어 그립니다
    } catch (e) { setError(errorText(e)); }
    finally { setBusy(false); }
  };

  // 사유를 직접 적어야 조회됩니다 — 기본값을 넣으면 아무 생각 없이 누르게 되고 기록이 무의미해집니다.
  const lookUpAccount = async () => {
    const reason = accountReason.trim();
    if (!reason) return;
    if (!window.confirm('전체 계좌번호를 조회할까요?\n누가·언제·왜 조회했는지 기록에 남습니다.')) return;
    setAccountBusy(true); setAccountError('');
    try {
      const data = await api.live.settlement.payoutAccount(cycleId, reason);
      setAccount(data); setAskAccount(false); setAccountReason('');
      accessLogs.reload();   // 방금 남은 내 열람 기록이 바로 보이게
    } catch (e) { setAccountError(errorText(e)); }
    finally { setAccountBusy(false); }
  };

  const rowsItems = (items.data && items.data.list) || [];

  return (
    <Drawer open onClose={onClose} title={c ? `${SF.store(c)} · ${ymd(c.periodStart)} 주차` : `정산 ${cycleId}`}
      footer={<>
        {options.length > 0 && (
          <button className={next === 'PAID' ? 'btn btn-primary' : 'btn btn-ghost'} disabled={!canSubmit} onClick={submit}>
            {busy ? '처리 중…' : next ? `${(SETTLEMENT_STATUS[next] || {}).label}(으)로 변경` : '상태를 고르세요'}
          </button>
        )}
        <button className="btn btn-ghost" onClick={onClose}>닫기</button>
      </>}>
      {error && <div style={{ marginBottom:12 }}><Notice tone="danger">처리하지 못했습니다: {error}</Notice></div>}
      <LiveBlock state={detail} empty={!c} emptyTitle="정산 회차를 찾을 수 없습니다">
        {c && (
          <div>
            <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:14, flexWrap:'wrap' }}>
              <StatusBadge map={SETTLEMENT_STATUS} value={st}/>
              <span style={{ fontSize:13, color:'var(--text-3)' }}>
                {ymd(c.periodStart)} ~ {ymd(c.periodEnd)} · 지급 예정 {c.payoutDate ? ymd(c.payoutDate) : '—'}
              </span>
            </div>

            <SectionTitle>정산 금액</SectionTitle>
            <dl className="spec">
              <dt>매출</dt><dd>{fmtKRW(SF.sales(c))} <span style={{ color:'var(--text-3)' }}>({fmtNum(SF.orders(c))}건)</span></dd>
              <dt>중개수수료</dt><dd>-{fmtKRW(SF.fee(c))} <span style={{ color:'var(--text-3)' }}>(부가세 -{fmtKRW(SF.feeVat(c))})</span></dd>
              <dt>PG 결제대행수수료</dt><dd>
                {SF.pgFee(c) == null
                  ? <span style={{ color:'var(--text-3)' }} title="PG사 청구액이 아직 들어오지 않았습니다. 결제수단별로 달라 화면에서 계산하지 않습니다.">확인 중</span>
                  : `-${fmtKRW(SF.pgFee(c))}`}
              </dd>
              <dt>차감</dt><dd>{SF.deduction(c) ? `-${fmtKRW(SF.deduction(c))} (${fmtNum(c.deductionCount)}건)` : '—'}</dd>
              <dt>이월(지난 주에서)</dt><dd>{SF.carryIn(c) ? `-${fmtKRW(SF.carryIn(c))}` : '—'}</dd>
              <dt>다음 주로 넘김</dt><dd>{SF.carryOut(c) ? fmtKRW(SF.carryOut(c)) : '—'}</dd>
              <dt>실지급액</dt><dd><Money value={SF.payout(c)} bold/></dd>
            </dl>
            {SF.payout(c) == null && (
              <div style={{ marginTop:8 }}><Notice>PG 결제대행수수료가 확인되기 전에는 실지급액이 정해지지 않습니다. 지급 완료 표시도 서버가 막습니다.</Notice></div>
            )}

            <SectionTitle>정산계좌</SectionTitle>
            {SF.accountOk(c, acct) ? (
              <>
                <dl className="spec">
                  <dt>계좌</dt><dd>{account
                    ? <span className="t-mono" style={{ fontWeight:600 }}>{[bankLabel(account.bankCode, banks.map), account.accountNo].filter(Boolean).join(' ')}</span>
                    : ([bankLabel(SF.bank(c, acct), banks.map), SF.account(c, acct)].filter(Boolean).join(' ') || '등록됨')}</dd>
                  <dt>예금주</dt><dd>{(account && account.accountHolder) || SF.holder(c, acct) || EMPTY_VALUE}</dd>
                  {(SF.acctReview(acct) || (account && account.changeUnderReview)) && (
                    <>
                      <dt>계좌 변경 확인</dt>
                      <dd style={{ color:'var(--danger)' }}>
                        {(() => { const until = (account && account.changeReviewUntil) || SF.acctReviewUntil(acct);
                          return until ? `${ymd(until)}까지 지급 보류` : '확인 기간 중 지급 보류'; })()}
                      </dd>
                    </>
                  )}
                </dl>
                {banks.failed && (
                  <div style={{ fontSize:12, color:'var(--text-3)', marginTop:6 }}>
                    은행 이름표를 불러오지 못했습니다. 은행 이름이 코드로 보일 수 있습니다.
                  </div>
                )}
                {accountError && <div style={{ marginTop:8 }}><Notice tone="danger">계좌를 조회하지 못했습니다: {accountError}</Notice></div>}
                {account ? (
                  <div style={{ fontSize:12, color:'var(--text-3)', marginTop:6 }}>
                    이 조회는 기록에 남았습니다. 창을 닫으면 번호는 화면에서 사라집니다.
                  </div>
                ) : !askAccount ? (
                  <div style={{ marginTop:8, display:'flex', alignItems:'center', gap:10, flexWrap:'wrap' }}>
                    <button className="btn btn-ghost btn-sm" onClick={() => { setAskAccount(true); setAccountError(''); }}>
                      <Icon name="search" size={13}/>전체 계좌번호 보기
                    </button>
                    <span style={{ fontSize:12, color:'var(--text-3)' }}>조회하면 누가·언제·왜 봤는지 기록에 남습니다</span>
                  </div>
                ) : (
                  <div style={{ marginTop:8, padding:12, border:'1px solid var(--border)', borderRadius:8 }}>
                    <label style={{ display:'block', fontSize:13, fontWeight:600, marginBottom:6 }}>조회 사유 (필수)</label>
                    <input className="input" style={{ width:'100%' }} autoFocus maxLength={200}
                      placeholder="예: 9월 3주차 이체 처리" value={accountReason}
                      onChange={(e) => setAccountReason(e.target.value)}
                      onKeyDown={(e) => { if (e.key === 'Enter') lookUpAccount(); }}/>
                    <div style={{ fontSize:12, color:'var(--text-3)', margin:'6px 0 10px' }}>
                      적으신 사유가 그대로 기록에 남습니다.
                    </div>
                    <div style={{ display:'flex', gap:6 }}>
                      <button className="btn btn-primary btn-sm" disabled={!accountReason.trim() || accountBusy} onClick={lookUpAccount}>
                        {accountBusy ? '조회 중…' : '조회'}
                      </button>
                      <button className="btn btn-ghost btn-sm" disabled={accountBusy} onClick={() => { setAskAccount(false); setAccountReason(''); setAccountError(''); }}>취소</button>
                    </div>
                  </div>
                )}
              </>
            ) : (
              <Notice tone="danger">정산계좌가 등록되지 않았습니다. 이체할 수 없으니 사장님에게 등록을 요청하세요.</Notice>
            )}

            <AccountAccessLog state={accessLogs}/>

            {/* 제출 서류 — **통장 사본만, 읽기 전용.** 이 화면에서 필요한 것은 "이체하려는 계좌가
                사본과 맞는가" 하나입니다. 사업자등록증은 입점 심사용이라 여기 늘어놓지 않고(필요한 것만),
                심사(승인·반려) 버튼도 두지 않습니다 — 정산 담당이 입점 서류를 심사하는 것은 맥락이 다릅니다.
                설계는 docs/merchant-document.md 2-2. */}
            <SectionTitle>제출 서류</SectionTitle>
            {/* showAccount={false} — 이 드로어 위쪽에 이미 '정산계좌' 블록이 있습니다.
                같은 계좌를 한 화면에 두 번 그리지 않습니다(필요한 것만). */}
            <MerchantDocuments businessId={docBusinessId} state={docs} docTypes={['BANKBOOK']}
              canReview={false} showBusiness={false} showAccount={false}/>

            <SectionTitle right={`${adjustments.length}건`}>차감 내역</SectionTitle>
            {adjustments.length === 0 ? (
              // 환불 차감은 **PG 결제대행수수료가 확인된 뒤에** 들어갑니다(backend #207 — 수수료를 모르는 동안 0원으로
              // 치고 차감하면 사장님이 실제보다 적게 받고, 그 행은 한 번만 쓰여 되돌릴 수 없습니다).
              // 그래서 수수료 확인 전의 "0건"은 "환불이 없었다"는 뜻이 아닙니다. 둘을 구분해 적습니다.
              SF.pgFee(c) == null ? (
                <div style={{ fontSize:13, color:'var(--text-3)' }}>
                  아직 차감이 반영되지 않았습니다. 환불 차감은 PG 결제대행수수료가 확인된 뒤에 들어갑니다
                  <span title="수수료를 모르는 채로 차감하면 금액이 틀어지고 되돌릴 수 없어, 서버가 수수료 확인 뒤로 미룹니다."> (?)</span>
                </div>
              ) : (
                <div style={{ fontSize:13, color:'var(--text-3)' }}>차감이 없습니다</div>
              )
            ) : (
              <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
                {adjustments.map((a, i) => (
                  <div key={a.adjustmentId || i} style={{ fontSize:13, padding:'8px 10px', border:'1px solid var(--border)', borderRadius:8 }}>
                    <div style={{ display:'flex', gap:8 }}>
                      <span style={{ fontWeight:600 }}>-{fmtKRW(a.amount)}</span>
                      {a.ordersId != null && <span className="t-mono" style={{ color:'var(--text-3)' }}>주문 {a.ordersId}</span>}
                      {a.originPeriodStart && <span style={{ color:'var(--text-3)' }}>{ymd(a.originPeriodStart)} 주차 주문</span>}
                      <span style={{ marginLeft:'auto', color:'var(--text-3)' }}>{a.regDate ? fmtDT(a.regDate) : ''}</span>
                    </div>
                    {(a.reason || a.type) && <div style={{ marginTop:4, color:'var(--text-2)' }}>{a.reason || a.type}</div>}
                  </div>
                ))}
              </div>
            )}

            <SectionTitle right={items.data ? `${fmtNum(items.data.totalCount)}건` : ''}>정산 대상 주문</SectionTitle>
            <LiveBlock state={items} empty={rowsItems.length === 0} compact emptyTitle="정산 대상 주문이 없습니다">
              <table className="table">
                <thead><tr><th>주문</th><th>판매 시각</th><th>매출</th><th>수수료</th></tr></thead>
                <tbody>
                  {rowsItems.map((it) => (
                    <tr key={it.itemId}>
                      <td className="t-mono">{it.ordersId}</td>
                      <td style={{ color:'var(--text-3)' }}>{fmtDT(it.soldAt)}</td>
                      <td>{fmtKRW(it.salesAmount)}</td>
                      <td>-{fmtKRW((it.commissionAmount || 0) + (it.commissionVatAmount || 0))}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </LiveBlock>

            <SectionTitle right={`${histories.length}건`}>처리 이력</SectionTitle>
            {histories.length === 0 ? (
              <div style={{ fontSize:13, color:'var(--text-3)' }}>이력이 없습니다</div>
            ) : (
              <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
                {[...histories].reverse().map((h, i) => (
                  <div key={i} style={{ fontSize:13, padding:'8px 10px', border:'1px solid var(--border)', borderRadius:8 }}>
                    <div style={{ display:'flex', alignItems:'center', gap:8 }}>
                      <StatusBadge map={SETTLEMENT_STATUS} value={h.status}/>
                      <span style={{ color:'var(--text-2)' }}>{h.aid ? `어드민 #${h.aid}` : '시스템'}</span>
                      <span style={{ marginLeft:'auto', color:'var(--text-3)' }}>{fmtDT(h.regDate)}</span>
                    </div>
                    {h.memo && <div style={{ marginTop:6, color:'var(--text-2)', whiteSpace:'pre-wrap' }}>{h.memo}</div>}
                  </div>
                ))}
              </div>
            )}

            {options.length > 0 ? (
              <>
                <SectionTitle>상태 변경</SectionTitle>
                <div style={{ display:'flex', gap:8, flexWrap:'wrap', marginBottom:10 }}>
                  {options.map((o) => (
                    <button key={o} type="button" className={`btn btn-sm ${next === o ? 'btn-primary' : 'btn-ghost'}`} onClick={() => setNext(o)}>
                      {(SETTLEMENT_STATUS[o] || {}).label || o}
                    </button>
                  ))}
                </div>
                <label style={{ display:'block', fontSize:13, fontWeight:600, marginBottom:6 }}>
                  메모 {memoRequired ? '(필수)' : '(선택)'}
                </label>
                <textarea className="input" style={{ width:'100%', height:72, padding:10, resize:'vertical' }}
                  value={memo} maxLength={500} onChange={(e) => setMemo(e.target.value)}
                  placeholder={memoRequired ? '왜 멈췄는지 적어 주세요. 이력에 남습니다.' : '이력에 남습니다.'}/>
              </>
            ) : (
              <div style={{ marginTop:16 }}><Notice>더 바꿀 수 있는 상태가 없습니다.</Notice></div>
            )}
          </div>
        )}
      </LiveBlock>
    </Drawer>
  );
};

// 계좌 열람 기록 — 누가·언제·왜 전체 계좌번호를 봤는지. 최근 3건만 보여 주고, 더 있으면 건수를 알립니다.
// 조회 자체는 기록되지 않으므로 상세를 열 때마다 불러도 됩니다.
const AccountAccessLog = ({ state }) => {
  const rows = (state.data && state.data.list) || [];
  const total = Number((state.data && state.data.totalCount) || 0);
  return (
    <>
      <SectionTitle right={state.data ? `${fmtNum(total)}건` : ''}>계좌 열람 기록</SectionTitle>
      {state.error ? (
        <div style={{ fontSize:13, color:'var(--text-3)' }}>열람 기록을 불러오지 못했습니다.</div>
      ) : !state.data ? (
        <div style={{ fontSize:13, color:'var(--text-3)' }}>불러오는 중…</div>
      ) : rows.length === 0 ? (
        <div style={{ fontSize:13, color:'var(--text-3)' }}>아직 이 회차의 계좌를 연 사람이 없습니다</div>
      ) : (
        <>
          <div style={{ display:'flex', flexDirection:'column', gap:6 }}>
            {rows.map((l) => (
              <div key={l.logId} style={{ fontSize:13, padding:'8px 10px', border:'1px solid var(--border)', borderRadius:8 }}>
                <div style={{ display:'flex', gap:8, alignItems:'center' }}>
                  <span style={{ fontWeight:600 }}>{l.adminName || `어드민 #${l.adminId}`}</span>
                  <span style={{ marginLeft:'auto', color:'var(--text-3)' }}>{fmtDT(l.accessedAt)}</span>
                </div>
                {l.reason && <div style={{ marginTop:4, color:'var(--text-2)', whiteSpace:'pre-wrap' }}>{l.reason}</div>}
              </div>
            ))}
          </div>
          {total > rows.length && (
            <div style={{ fontSize:12, color:'var(--text-3)', marginTop:6 }}>
              최근 {rows.length}건만 보여 줍니다. 전체 {fmtNum(total)}건.
            </div>
          )}
        </>
      )}
    </>
  );
};

Object.assign(window, { Settlements });
