/* ============================================================
   바른셈 — 룰셋 거버넌스: 공용 부품 + 플랫폼 표준(admin) 관점 + 모달
   ============================================================ */

/* 출처 배지 — 표준 상속 / 법인 재정의 */
function SourceBadge({ source, kind }) {
  if (source === "firm")
    return <span className="src-badge src-firm"><Ms name="edit" style={{ fontSize: 12 }} />법인 재정의</span>;
  return <span className="src-badge src-std"><Ms name="link" style={{ fontSize: 12 }} />표준 상속</span>;
}

/* 법정/정책 구분 칩 */
function KindChip({ kind }) {
  return kind === "statutory"
    ? <span className="tag" style={{ background: "var(--bg-sunken)", color: "var(--ink-3)" }}><Ms name="gavel" style={{ fontSize: 11 }} />법정</span>
    : <span className="tag tag-accent"><Ms name="tune" style={{ fontSize: 11 }} />정책</span>;
}

/* 표준 버전 상태 pill */
function StdStatus({ status }) {
  const m = {
    active:     { t: "현재 적용", cls: "pill-ok",   ic: "check_circle" },
    incoming:   { t: "도착 · 검토 대기", cls: "pill-warn", ic: "mark_email_unread" },
    scheduled:  { t: "예약", cls: "pill-info", ic: "schedule" },
    superseded: { t: "지난 버전", cls: "pill-gray", ic: "history" },
  }[status] || { t: status, cls: "pill-gray", ic: "circle" };
  return <span className={"pill " + m.cls}><Ms name={m.ic} />{m.t}</span>;
}

/* 표준 버전 이력 타임라인 (불변 · 감사연결) */
function StdTimeline({ versions, currentRef, pending, onView, onRuns, runsForStd, adopted, compact, statusOf, aggRunsForStd }) {
  // 최신이 위로
  const ordered = [...versions].reverse();
  return (
    <div className="rs-vtl">
      {ordered.map((v, i) => {
        const vs = statusOf ? statusOf(v) : v.status;   // ★ 파생 우선, 없으면 하드코딩 fallback
        const isRef = v.id === currentRef;
        const agg = aggRunsForStd ? aggRunsForStd(v.id) : null;   // ★ admin 집계(실+추정)
        const rl = runsForStd ? runsForStd(v.id) : null;
        const totalRuns = agg ? agg.oursRuns + agg.otherRuns : (rl ? rl.length : v.applied.runs);
        const totalHeads = agg ? agg.oursHeads + agg.otherHeads : (rl ? rl.reduce((s, r) => s + (r.heads || 0), 0) : null);
        return (
          <div className={"rs-vnode" + (isRef ? " is-ref" : "")} key={v.id}>
            <div className="rs-vrail">
              <div className="rs-vdot" data-status={vs}>
                {vs === "active" ? <Ms name="check" style={{ fontSize: 13 }} /> :
                 vs === "incoming" ? <Ms name="priority_high" style={{ fontSize: 13 }} /> : ""}
              </div>
              {i < ordered.length - 1 && <div className="rs-vbar" />}
            </div>
            <div className="rs-vbody">
              <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                <span className="kbd num">{v.id}</span>
                <StdStatus status={vs} />
                {isRef && <span className="tag tag-accent">우리 법인 추적 중</span>}
                {adopted && adopted.includes(v.id) && !isRef && <span className="tag"><Ms name="check" style={{ fontSize: 11 }} />채택함</span>}
              </div>
              <div className="rs-vtitle">{v.label}</div>
              <div className="rs-vmeta num">유효 {v.effective} · 발행 {v.published}</div>
              <div className="rs-vmeta"><Ms name="verified" style={{ fontSize: 13, verticalAlign: "-2px", color: "var(--ok)" }} /> {v.verifiedBy} · {v.verifiedAt}</div>
              {(!compact || agg) && (
                onRuns
                  ? <button className="rs-applied rs-applied-btn" onClick={() => onRuns(v)} title="이 버전으로 박제된 급여건 보기">
                      <Ms name="receipt_long" style={{ fontSize: 14 }} />
                      이 버전으로 처리된 급여 <b>{totalRuns}건</b>
                      {totalHeads != null && <span className="muted"> · 누적 {totalHeads}명</span>}
                      <Ms name="chevron_right" style={{ fontSize: 15, marginLeft: "auto" }} />
                    </button>
                  : <div className="rs-applied">
                      <Ms name="receipt_long" style={{ fontSize: 14 }} />
                      이 버전으로 처리된 급여 <b>{totalRuns}건</b>
                      {totalHeads != null && <span className="muted"> · 누적 {totalHeads}명</span>}
                    </div>
              )}
              {agg && (
                <div className="muted" style={{ fontSize: 10.5, marginTop: 2 }}>
                  우리 법인 {agg.oursRuns}건 · 타 법인 추정 {agg.otherRuns}건 ({agg.otherFirms}곳)
                </div>
              )}
              {onView && (
                <button className="btn btn-ghost btn-sm" style={{ marginTop: 6, marginLeft: -8 }} onClick={() => onView(v)}>
                  <Ms name="visibility" style={{ fontSize: 15 }} />스냅샷 보기 (읽기전용)
                </button>
              )}
            </div>
          </div>
        );
      })}
      <div className="rs-vfoot"><Ms name="lock" style={{ fontSize: 14 }} />버전은 삭제되지 않습니다 — 갱신은 항상 새 버전 발행</div>
    </div>
  );
}

/* 변경 항목 diff 리스트 */
function DiffList({ diffs }) {
  if (!diffs.length) return <div className="muted" style={{ fontSize: 13, padding: "8px 0" }}>변경된 항목이 없습니다.</div>;
  return (
    <div className="rs-diff">
      {diffs.map((d) => (
        <div className="rs-diff-row" key={d.key}>
          <div className="rs-diff-name"><KindChip kind={d.kind} /><span style={{ fontWeight: 600 }}>{d.label}</span></div>
          <div className="rs-diff-vals num">
            <span className="rs-from">{d.from}</span>
            <Ms name="east" style={{ fontSize: 16, color: "var(--ink-4)" }} />
            <span className="rs-to">{d.to}</span>
          </div>
        </div>
      ))}
    </div>
  );
}

/* 표준 채택 모달 — diff 검토 + 노무사 검증 게이트 */
function AdoptModal({ fromId, to, diffs, onClose, onAdopt }) {
  const toId = to ? to.id : "";
  const [ack, setAck] = useState(false);
  if (!to) return null;
  return (
    <Modal title="표준 버전 채택" width={620} onClose={onClose}
      footer={<>
        <button className="btn btn-default" onClick={onClose}>나중에</button>
        <button className="btn btn-primary" disabled={!ack} onClick={() => onAdopt(toId)}>
          <Ms name="verified" />검토 완료 · {to.id} 채택
        </button>
      </>}>
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 14 }}>
        <span className="kbd num">{fromId}</span><Ms name="east" style={{ color: "var(--ink-4)" }} /><span className="kbd num">{to.id}</span>
        <span className="page-desc" style={{ margin: 0, fontSize: 13 }}>· 유효일 {to.effective}부터</span>
      </div>
      <div style={{ fontWeight: 650, fontSize: 13, marginBottom: 8 }}>변경 항목 {diffs.length}건</div>
      <DiffList diffs={diffs} />
      <div className="card card-pad" style={{ marginTop: 14, background: "var(--warn-soft)", border: "1px solid var(--warn-line)", display: "flex", gap: 11, padding: "12px 14px" }}>
        <Ms name="balance" style={{ color: "color-mix(in oklab, var(--warn) 70%, black)", fontSize: 19, flex: "none" }} />
        <label style={{ fontSize: 12.5, color: "color-mix(in oklab, var(--warn) 55%, black)", lineHeight: 1.55, display: "flex", gap: 9, cursor: "pointer" }}>
          <input type="checkbox" checked={ack} onChange={(e) => setAck(e.target.checked)} style={{ marginTop: 2, flex: "none" }} />
          <span>위 변경을 <b>노무사가 검토·확인</b>했습니다. 채택 시 <b>유효일 이후 신규 급여</b>부터 새 버전이 적용되며, <b>이미 확정된 과거 급여는 당시 버전으로 그대로 보존</b>됩니다.</span>
        </label>
      </div>
    </Modal>
  );
}

/* 항목 재정의(오버레이) 모달 */
const UNIT_SUFFIX = { pct: "%", sur: "%", won: "원", hours: "시간" };
const UNIT_HINT = {
  pct: "백분율을 숫자로 입력 (예: 4.75 → 4.75%)",
  sur: "가산율(추가율)을 숫자로 입력 (예: 50 → +50%)",
  won: "원 단위 정수로 입력 (콤마 가능)",
  hours: "시간 수를 숫자로 입력",
};

function OverrideModal({ item, onClose, onSave, onRemove }) {
  const { editStr, parseEdit, slotDisplay } = window.BARUNSEM;
  const isEnum = item.unit === "enum";
  const isText = item.unit === "text";
  const numeric = !isEnum && !isText;
  // 재정의 중이면 현재 적용 slot, 아니면 빈 값(표준 상속) — 단위별 편집 문자열로 변환
  const startSlot = item.source === "firm" ? item.slot : null;
  const [raw, setRaw] = useState(editStr(item, startSlot));
  const [note, setNote] = useState(item.source === "firm" ? (item.note || "") : "");
  const statutory = item.kind === "statutory";
  const suffix = UNIT_SUFFIX[item.unit];

  const slot = parseEdit(item, raw);                          // 입력 → 정규화 slot (실패 시 null)
  const preview = slot ? slotDisplay(item, slot) : null;       // 표시 문자열은 숫자/키에서 파생
  const invalid = numeric && raw.trim() !== "" && !slot;        // 입력했는데 파싱 실패 = 잘못된 형식
  const stdPlaceholder = numeric ? editStr(item, item.stdSlot) : item.stdValue;

  return (
    <Modal title={`항목 재정의 — ${item.label}`} width={540} onClose={onClose}
      footer={<>
        {item.source === "firm" && <button className="btn btn-danger" style={{ marginRight: "auto" }} onClick={() => onRemove(item.key)}><Ms name="undo" />표준값으로 되돌리기</button>}
        <button className="btn btn-default" onClick={onClose}>취소</button>
        <button className="btn btn-primary" disabled={!slot} onClick={() => onSave(item.key, slot, note.trim())}><Ms name="check" />재정의 저장</button>
      </>}>
      <div className="dl" style={{ marginBottom: 14 }}>
        <dt>구분</dt><dd style={{ textAlign: "left" }}><KindChip kind={item.kind} /></dd>
        <dt>표준값</dt><dd className="num">{item.stdValue}</dd>
        <dt>근거</dt><dd style={{ textAlign: "left", fontWeight: 500 }} className="muted">{item.legal}</dd>
      </div>
      {statutory && (
        <div className="card card-pad" style={{ background: "var(--danger-soft)", border: "1px solid var(--danger-line)", display: "flex", gap: 11, padding: "12px 14px", marginBottom: 14 }}>
          <Ms name="warning" style={{ color: "var(--danger)", fontSize: 19, flex: "none" }} />
          <div style={{ fontSize: 12.5, color: "color-mix(in oklab, var(--danger) 55%, black)", lineHeight: 1.55 }}>
            <b>법정 수치입니다.</b> 표준값은 공식 고시 기준이며, 법인이 임의로 바꾸면 신고금액 불일치 위험이 있습니다. 특별한 사유가 없으면 <b>표준 상속</b>을 권장합니다.
          </div>
        </div>
      )}
      <div className="field" style={{ marginBottom: invalid || (numeric && preview) ? 6 : 12 }}>
        <label>법인 적용값 {numeric && <span className="muted" style={{ fontWeight: 400 }}>· {UNIT_HINT[item.unit]}</span>}</label>
        {isEnum ? (
          <select className="select" value={raw} onChange={(e) => setRaw(e.target.value)}>
            <option value="">— 표준 상속 ({item.stdValue})</option>
            {(item.options || []).map((o) => <option key={o.key} value={o.key}>{o.label}</option>)}
          </select>
        ) : isText ? (
          <input className="input" value={raw} onChange={(e) => setRaw(e.target.value)} placeholder={stdPlaceholder} />
        ) : (
          <div className="input-affix">
            <input className="input num" inputMode="decimal" value={raw} onChange={(e) => setRaw(e.target.value)} placeholder={stdPlaceholder}
              style={invalid ? { borderColor: "var(--danger)" } : null} />
            <span className="suffix">{suffix}</span>
          </div>
        )}
      </div>
      {/* 숫자 → 표시 파생을 명시: 입력한 숫자가 실제로 어떻게 적용되는지 즉시 보여줌 */}
      {numeric && !invalid && preview && (
        <div style={{ display: "flex", alignItems: "center", gap: 7, fontSize: 12, color: "var(--accent-ink)", margin: "0 0 12px 2px" }}>
          <Ms name="calculate" style={{ fontSize: 15, color: "var(--accent)" }} />계산 적용값 <b className="num">{preview}</b>
          {item.unit === "pct" && <span className="muted">· 내부 저장 {slot.n}</span>}
          {item.unit === "sur" && <span className="muted">· 배수 ×{(1 + slot.n).toFixed(2)}</span>}
        </div>
      )}
      {invalid && (
        <div style={{ display: "flex", alignItems: "center", gap: 7, fontSize: 12, color: "var(--danger)", margin: "0 0 12px 2px" }}>
          <Ms name="error" style={{ fontSize: 15 }} />숫자로 입력해야 합니다 — 단위({suffix})는 자동으로 붙습니다.
        </div>
      )}
      <div className="field">
        <label>재정의 사유 <span className="muted" style={{ fontWeight: 400 }}>(감사기록에 남습니다)</span></label>
        <input className="input" value={note} onChange={(e) => setNote(e.target.value)} placeholder="예: 법인 디폴트 정책 — 노무사 개별검토 강제" />
      </div>
    </Modal>
  );
}

/* 스냅샷(읽기전용) 모달 */
function SnapshotModal({ version, items, status, onClose }) {
  const list = items || window.BARUNSEM.RULESET_GOV.RS_ITEMS;
  return (
    <Modal title={`${version.id} · 스냅샷 (읽기전용)`} width={680} onClose={onClose}
      footer={<button className="btn btn-default" onClick={onClose}>닫기</button>}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 12, flexWrap: "wrap" }}>
        <StdStatus status={status || version.status} /><span className="page-desc num" style={{ margin: 0, fontSize: 13 }}>유효 {version.effective} · 발행 {version.published}</span>
        <span className="tag"><Ms name="lock" style={{ fontSize: 12 }} />불변</span>
      </div>
      <table className="tbl tbl-cards">
        <thead><tr><th>항목</th><th style={{ width: 64 }}>구분</th><th className="r">값</th></tr></thead>
        <tbody>
          {list.map((it) => (
            <tr key={it.key}>
              <td className="tc-name" style={{ fontWeight: 550 }}>{it.label}</td>
              <td data-label="구분"><KindChip kind={it.kind} /></td>
              <td className="r num" data-label="값" style={{ fontWeight: 650 }}>{window.BARUNSEM.slotDisplay(it, version.items[it.key])}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </Modal>
  );
}

/* ── 플랫폼 표준 admin 관점 ─────────────────────────── */
function AdminRuleset({ ctx, onView, onRuns }) {
  const G = ctx.rsGov;
  const versions = ctx.stdVersions;
  const ss = ctx.stdStatusOf;
  const active = versions.find((v) => ss(v) === "active") || versions.find((v) => v.status === "active") || versions[versions.length - 1];
  const incoming = [...versions].reverse().find((v) => ss(v) === "incoming") || [...versions].reverse().find((v) => v.status === "incoming") || versions[versions.length - 1];
  const tenants = ctx.tenants;
  const adopted = tenants.filter((t) => t.state === "adopted");
  const pending = tenants.filter((t) => t.state === "pending");
  const canEdit = ctx.canManageTenants;   // W2-5 — 법인 owner 권한이 아니라 플랫폼 운영자 권한

  return (
    <div className="l-split" style={{ "--rail": "360px" }}>
      <div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
        {/* admin 설명 */}
        <div className="card card-pad" style={{ display: "flex", gap: 13, background: "var(--surface-2)" }}>
          <Ms name="admin_panel_settings" style={{ color: "var(--ink-3)", fontSize: 22, flex: "none" }} />
          <div style={{ fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.6 }}>
            <b>플랫폼 표준룰셋</b>은 바른셈이 유지·검증해 <b>전체 노무법인에 배포</b>합니다. 법령·요율·세액표가 바뀌면 여기서 <b>새 버전을 발행</b>하고, 각 법인은 알림을 받아 <b>검토 후 채택</b>합니다 — 법인마다 요율을 다시 입력할 필요가 없습니다.
          </div>
        </div>

        {/* 활성 + 도착 버전 카드 */}
        <div className="grid-2">
          {[active, incoming].map((v) => (
            <div key={v.id} className="card card-pad" style={ss(v) === "incoming" ? { border: "1px solid var(--warn-line)" } : null}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }}>
                <span className="kbd num">{v.id}</span><StdStatus status={ss(v)} />
              </div>
              <div style={{ fontWeight: 700, fontSize: 15, letterSpacing: "-0.01em" }}>{v.label}</div>
              <div className="muted num" style={{ fontSize: 12, marginTop: 3 }}>유효 {v.effective} · 발행 {v.published}</div>
              <div style={{ marginTop: 10, display: "flex", flexDirection: "column", gap: 4 }}>
                {v.changelog.map((c, i) => (
                  <div key={i} style={{ fontSize: 12, color: "var(--ink-2)", display: "flex", gap: 7, lineHeight: 1.5 }}>
                    <Ms name="arrow_right" style={{ fontSize: 15, color: "var(--ink-4)", flex: "none" }} />{c}
                  </div>
                ))}
              </div>
              <button className="btn btn-default btn-sm" style={{ marginTop: 12 }} onClick={() => onView(v)}><Ms name="visibility" />스냅샷 보기</button>
            </div>
          ))}
        </div>

        {/* 멀티테넌시 — 채택 현황 */}
        <div className="card">
          <CardHead icon="domain" title="노무법인 채택 현황" sub={`${tenants.length}개 법인 · ${incoming.id} 배포`}
            right={<span className="tag tag-warn">{pending.length}개 법인 검토 대기</span>} />
          <table className="tbl tbl-cards">
            <thead><tr><th>노무법인</th><th>적용 방식</th><th className="r">고객사</th><th>추적 버전</th><th style={{ textAlign: "center", width: 132 }}>{incoming.id} 채택</th></tr></thead>
            <tbody>
              {tenants.map((t) => {
                const isOurs = t.name === G.FIRM.name;
                return (
                <tr key={t.name}>
                  <td className="tc-name" style={{ fontWeight: 650 }}>{t.name}{isOurs && <span className="tag tag-accent" style={{ marginLeft: 6, height: 18, fontSize: 10 }}>우리 법인</span>}</td>
                  <td data-label="적용 방식">{t.adoption === "subscribed"
                    ? <span className="tag"><Ms name="link" style={{ fontSize: 11 }} />표준 구독</span>
                    : <span className="tag tag-accent"><Ms name="tune" style={{ fontSize: 11 }} />부분 재정의</span>}</td>
                  <td className="r num" data-label="고객사">{t.clients}</td>
                  <td data-label="추적 버전"><span className="kbd num">{t.ref}</span></td>
                  <td className="tc-action" data-label={incoming.id + " 채택"} style={{ textAlign: "center" }}>
                    {(() => {
                      const isAdopted = t.ref === incoming.id;
                      return (
                        <button className="btn btn-sm" disabled={!canEdit}
                          title={canEdit ? "데모: 이 법인이 " + incoming.id + "을 채택/되돌리기" : "플랫폼 운영자 모드에서만 조작할 수 있습니다"}
                          onClick={() => canEdit && ctx.toggleTenant(t.name)}
                          style={isAdopted
                            ? { background: "var(--ok-soft)", color: "color-mix(in oklab, var(--ok) 82%, black)", borderColor: "var(--ok-line)" }
                            : { background: "var(--warn-soft)", color: "color-mix(in oklab, var(--warn) 55%, black)", borderColor: "var(--warn-line)" }}>
                          {isAdopted
                            ? <><Ms name="check" style={{ fontSize: 14 }} />채택 완료</>
                            : <><Ms name="schedule" style={{ fontSize: 14 }} />대기 · 채택(데모)</>}
                        </button>
                      );
                    })()}
                  </td>
                </tr>
              );})}
            </tbody>
            <tfoot><tr className="tbl-foot"><td className="tc-name" colSpan="2">{incoming.id} 채택 {tenants.filter((t) => t.ref === incoming.id).length} · 대기 {tenants.filter((t) => t.ref !== incoming.id).length}</td><td className="r num" data-label="고객사 합계">{tenants.reduce((s, t) => s + t.clients, 0)}</td><td colSpan="2"></td></tr></tfoot>
          </table>
        </div>

        <div className="card card-pad" style={{ display: "flex", gap: 13, background: "var(--accent-soft)", border: "1px solid var(--accent-line)" }}>
          <Ms name="info" style={{ color: "var(--accent)", fontSize: 20, flex: "none" }} />
          <div style={{ fontSize: 12.5, color: "var(--accent-ink)", lineHeight: 1.6 }}>
            표준은 <b>법인에게 강제되지 않습니다</b>. 도착 알림 후 각 법인의 노무사가 <b>직접 채택</b>해야 적용됩니다(책임 소재 일치). 미채택 법인은 기존 버전으로 계속 운영되며, 과거 급여는 영향받지 않습니다.
          </div>
        </div>
      </div>

      {/* 우측: 표준 버전 이력 + 발행 */}
      <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
        <button className="btn btn-primary btn-lg" disabled={!canEdit} title={canEdit ? "" : "플랫폼 운영자만 표준을 발행할 수 있습니다"} onClick={() => canEdit && onView("publish")}><Ms name="add" />새 표준 버전 발행</button>
        <div className="card card-pad">
          <div style={{ fontSize: 12, fontWeight: 700, color: "var(--ink-3)", letterSpacing: ".03em", marginBottom: 12 }}>표준 버전 이력 · 불변</div>
          <StdTimeline versions={versions} currentRef={null} pending={null} onView={onView} onRuns={onRuns} runsForStd={ctx.runsForStd} aggRunsForStd={ctx.adminRunsForStd} statusOf={ctx.stdStatusOf} compact />
        </div>
      </div>
    </div>
  );
}

/* 버전 → 급여건 역추적 모달 (스냅샷 역색인) */
function AppliedRunsModal({ version, runs, status, agg, onClose, onGo }) {
  const heads = runs.reduce((s, r) => s + (r.heads || 0), 0);
  const hasEst = agg && agg.otherFirms > 0;
  return (
    <Modal title={`${version.id} · 이 버전으로 처리된 급여건`} width={640} onClose={onClose}
      footer={<button className="btn btn-default" onClick={onClose}>닫기</button>}>
      <div style={{ display: "flex", gap: 8, alignItems: "center", marginBottom: 14, flexWrap: "wrap" }}>
        <StdStatus status={status || version.status} />
        <span className="tag"><Ms name="receipt_long" style={{ fontSize: 12 }} />우리 법인 실 {runs.length}건</span>
        {hasEst && <span className="tag tag-warn"><Ms name="domain" style={{ fontSize: 12 }} />타 법인 추정 {agg.otherRuns}건</span>}
        <span className="tag"><Ms name="groups" style={{ fontSize: 12 }} />우리 누적 {heads}명</span>
        <span className="page-desc" style={{ margin: 0, fontSize: 12.5 }}>· 스냅샷 역색인 (불변)</span>
      </div>
      <div style={{ fontSize: 11.5, fontWeight: 700, color: "var(--ink-3)", letterSpacing: ".02em", marginBottom: 6 }}>우리 법인 실데이터</div>
      {runs.length === 0 ? (
        <div className="muted" style={{ fontSize: 13, padding: "20px 0", textAlign: "center" }}>아직 이 버전으로 박제된 급여건이 없습니다.</div>
      ) : (
        <table className="tbl tbl-cards">
          <thead><tr><th>고객사</th><th>귀속월</th><th className="r">인원</th><th>박제 시각</th><th style={{ width: 64, textAlign: "center" }}>이동</th></tr></thead>
          <tbody>
            {runs.map((r, i) => (
              <tr key={i}>
                <td className="tc-name" style={{ fontWeight: 650 }}>{r.clientName}{r.live && <span className="tag tag-accent" style={{ marginLeft: 6, height: 18, fontSize: 10 }}>현재</span>}</td>
                <td className="num" data-label="귀속월">{r.ym}</td>
                <td className="r num" data-label="인원">{r.heads}명</td>
                <td className="muted num" data-label="박제 시각" style={{ fontSize: 12 }}>{r.at}</td>
                <td className="tc-action" data-label="이동" style={{ textAlign: "center" }}>
                  {r.clientId ? <button className="iconbtn" aria-label="급여대장으로 이동" title="급여대장으로 이동" onClick={() => onGo(r.clientId)} style={{ width: 30, height: 30, margin: "0 auto" }}><Ms name="arrow_forward" style={{ fontSize: 16 }} /></button> : <span className="muted">—</span>}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
      {hasEst && (
        <div className="card card-pad" style={{ marginTop: 12, display: "flex", gap: 11, background: "var(--surface-2)", alignItems: "center" }}>
          <Ms name="domain" style={{ color: "var(--ink-3)", fontSize: 20, flex: "none" }} />
          <div style={{ fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.5 }}>
            <b>타 법인 추정 집계</b> — {agg.otherFirms}개 법인 · 약 {agg.otherRuns}건 · 누적 약 {agg.otherHeads}명.
            <span className="muted"> (고객사 수 기반 추정치 · 실데이터 아님)</span>
          </div>
        </div>
      )}
      <div className="rs-vfoot" style={{ marginTop: 10 }}><Ms name="lock" style={{ fontSize: 13 }} />각 급여건은 산정 시점 이 버전으로 박제 — 재계산해도 동일하게 재현됩니다</div>
    </Modal>
  );
}

/* 새 표준 버전 발행 모달 (admin) — 복제 → 항목 편집 → 검증 → 발행 (append-only) */
function PublishModal({ ctx, onClose }) {
  const G = ctx.rsGov;
  const { editStr, parseEdit, slotRaw, slotDisplay } = window.BARUNSEM;
  const versions = ctx.stdVersions;
  const base = versions.find((v) => v.status === "active") || versions[versions.length - 1];
  const editable = G.RS_ITEMS.filter((it) => it.kind === "statutory");
  const itemOf = (k) => editable.find((e) => e.key === k);
  const [vals, setVals] = useState(() => Object.fromEntries(editable.map((it) => [it.key, editStr(it, base.items[it.key])])));
  const [eff, setEff] = useState("2027-01-01");
  const [verifier, setVerifier] = useState("표준검증위 · 김노무 노무사");
  const [ack, setAck] = useState(false);
  const [step, setStep] = useState("edit"); // edit | verify
  const set = (k, v) => setVals((s) => ({ ...s, [k]: v }));
  // 변경 = 새 slot 원시값이 base와 다른 항목 (유효 입력만) / 오류 = 숫자 아닌 입력
  const changed = editable.filter((it) => { const ns = parseEdit(it, vals[it.key]); return ns && slotRaw(ns) !== slotRaw(base.items[it.key]); });
  const invalidKeys = editable.filter((it) => (vals[it.key] || "").trim() !== "" && !parseEdit(it, vals[it.key]));

  const publish = () => {
    const yr = eff.slice(0, 4);
    const sameYr = versions.filter((v) => v.id.indexOf("std-" + yr) === 0);
    const id = "std-" + yr + "." + (sameYr.length + 1);
    const items = {};
    G.RS_ITEMS.forEach((it) => {
      const ed = itemOf(it.key);
      const ns = ed ? parseEdit(it, vals[it.key]) : null;
      items[it.key] = ns ? { ...ns } : { ...(base.items[it.key] || {}) };  // 미편집 항목은 표준 그대로 상속
    });
    const ver = {
      id, label: "표준 " + yr + " · 발행본", effective: eff, published: "2026-06-07",
      status: "incoming", verifiedBy: verifier, verifiedAt: "2026-06-07",
      items, applied: { runs: 0, span: "발행 직후 — 적용 급여 없음" },
      changelog: changed.length ? changed.map((it) => `${it.label} → ${slotDisplay(it, parseEdit(it, vals[it.key]))}`) : ["항목 변경 없음 (재확인 발행)"],
    };
    ctx.publishStandard(ver);
    onClose();
  };

  return (
    <Modal title="새 표준 버전 발행" width={640} onClose={onClose}
      footer={step === "edit"
        ? <><button className="btn btn-default" onClick={onClose}>취소</button><button className="btn btn-primary" disabled={invalidKeys.length > 0} onClick={() => setStep("verify")}><Ms name="arrow_forward" />검증 단계로</button></>
        : <><button className="btn btn-default" onClick={() => setStep("edit")}><Ms name="arrow_back" />항목 편집</button><button className="btn btn-primary" disabled={!ack || !verifier.trim()} onClick={publish}><Ms name="send" />발행 ({changed.length}건 변경)</button></>}>
      <div className="card card-pad" style={{ background: "var(--surface-2)", display: "flex", gap: 11, marginBottom: 14, padding: "11px 14px" }}>
        <Ms name="content_copy" style={{ color: "var(--ink-3)", fontSize: 18, flex: "none" }} />
        <div style={{ fontSize: 12.5, color: "var(--ink-2)", lineHeight: 1.55 }}>
          활성 버전 <span className="kbd num">{base.id}</span>을 <b>복제</b>해 새 버전을 만듭니다. 기존 버전은 수정되지 않으며, 새 effective_date로 <b>append-only 추가</b>됩니다.
        </div>
      </div>

      {step === "edit" ? (
        <>
          <div style={{ display: "flex", gap: 12, marginBottom: 12 }}>
            <div className="field" style={{ flex: 1 }}>
              <label>유효일 (effective_date)</label>
              <input className="input num" value={eff} onChange={(e) => setEff(e.target.value)} placeholder="2027-01-01" />
            </div>
          </div>
          <div style={{ fontSize: 12, fontWeight: 700, color: "var(--ink-3)", marginBottom: 8 }}>법정 수치 편집 <span className="muted" style={{ fontWeight: 400 }}>· 바뀐 항목만 changelog에 기록</span></div>
          <table className="tbl tbl-cards">
            <thead><tr><th>항목</th><th style={{ width: 150 }}>현재값</th><th style={{ width: 170 }}>새 값</th></tr></thead>
            <tbody>
              {editable.map((it) => {
                const cur = slotDisplay(it, base.items[it.key]);
                const ns = parseEdit(it, vals[it.key]);
                const isCh = ns && slotRaw(ns) !== slotRaw(base.items[it.key]);
                const bad = (vals[it.key] || "").trim() !== "" && !ns;
                const sfx = UNIT_SUFFIX[it.unit];
                return (
                  <tr key={it.key} className={isCh ? "rs-row-firm" : ""}>
                    <td className="tc-name" style={{ fontWeight: 600 }}>{it.label}</td>
                    <td className="num muted" data-label="현재값">{cur}</td>
                    <td data-label="새 값">
                      <div className="input-affix">
                        <input className="input num" style={{ height: 32, ...(bad ? { borderColor: "var(--danger)" } : null) }} inputMode="decimal" value={vals[it.key]} onChange={(e) => set(it.key, e.target.value)} />
                        <span className="suffix">{sfx}</span>
                      </div>
                      {isCh && <div className="muted num" style={{ fontSize: 10.5, marginTop: 2 }}>→ {slotDisplay(it, ns)}</div>}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </>
      ) : (
        <>
          <div style={{ fontWeight: 650, fontSize: 13, marginBottom: 8 }}>변경 항목 {changed.length}건</div>
          {changed.length === 0
            ? <div className="muted" style={{ fontSize: 13, padding: "6px 0 14px" }}>변경된 항목이 없습니다 — 재확인 발행으로 기록됩니다.</div>
            : <div className="rs-diff" style={{ marginBottom: 14 }}>
                {changed.map((it) => (
                  <div className="rs-diff-row" key={it.key}>
                    <div className="rs-diff-name"><KindChip kind={it.kind} /><span style={{ fontWeight: 600 }}>{it.label}</span></div>
                    <div className="rs-diff-vals num">
                      <span className="rs-from">{slotDisplay(it, base.items[it.key])}</span>
                      <Ms name="east" style={{ fontSize: 16, color: "var(--ink-4)" }} />
                      <span className="rs-to">{slotDisplay(it, parseEdit(it, vals[it.key]))}</span>
                    </div>
                  </div>
                ))}
              </div>}
          <div className="field" style={{ marginBottom: 12 }}>
            <label>표준검증위 검증자 (verifiedBy)</label>
            <input className="input" value={verifier} onChange={(e) => setVerifier(e.target.value)} />
          </div>
          <div className="card card-pad" style={{ background: "var(--warn-soft)", border: "1px solid var(--warn-line)", display: "flex", gap: 11, padding: "12px 14px" }}>
            <Ms name="verified_user" style={{ color: "color-mix(in oklab, var(--warn) 70%, black)", fontSize: 19, flex: "none" }} />
            <label style={{ fontSize: 12.5, color: "color-mix(in oklab, var(--warn) 55%, black)", lineHeight: 1.55, display: "flex", gap: 9, cursor: "pointer" }}>
              <input type="checkbox" checked={ack} onChange={(e) => setAck(e.target.checked)} style={{ marginTop: 2, flex: "none" }} />
              <span>표준검증위가 위 수치를 <b>공식 고시·법령과 대조·검증</b>했습니다. 발행 시 구독·추적 법인에 <b>도착 알림</b>이 전송되며, 각 법인의 노무사 채택 후 적용됩니다.</span>
            </label>
          </div>
        </>
      )}
    </Modal>
  );
}

Object.assign(window, { SourceBadge, KindChip, StdStatus, StdTimeline, DiffList, AdoptModal, OverrideModal, SnapshotModal, AdminRuleset, AppliedRunsModal, PublishModal });
