/* ============================================================
   바른셈 — 공유 UI 프리미티브
   ============================================================ */
const { useState, useEffect, useRef, useMemo } = React;
const KRW = (n, opt) => window.BARUNSEM.krw(n, opt);

/* Material Symbols 아이콘 */
// 아이콘은 장식이다 — 기본적으로 낭독 대상에서 뺀다(E8). 의미가 있으면 부모가 aria-label을 준다.
function Ms({ name, style, className, ...rest }) {
  return <span className={"ms material-symbols-rounded" + (className ? " " + className : "")} style={style} aria-hidden="true" {...rest}>{name}</span>;
}

/* 상태 정의 — 검수 워크플로(§8) */
const STATUS_META = {
  draft:       { label: "작성 중",   pill: "pill-gray",   icon: "edit_note" },
  submitted:   { label: "검수 대기", pill: "pill-info",   icon: "inbox" },
  in_review:   { label: "검수 중",   pill: "pill-warn",   icon: "fact_check" },
  locked:      { label: "잠금",      pill: "pill-indigo", icon: "lock" },
  calculated:  { label: "계산 완료", pill: "pill-teal",   icon: "calculate" },
  pending_approval: { label: "승인 대기", pill: "pill-warn", icon: "approval" },
  confirmed:   { label: "확정",      pill: "pill-ok",     icon: "verified" },
  distributed: { label: "발송 완료", pill: "pill-ok",     icon: "send" },
};

function StatusPill({ status, size }) {
  const m = STATUS_META[status] || STATUS_META.draft;
  return (
    <span className={"pill " + m.pill} style={size === "lg" ? { height: 27, fontSize: 12.5, padding: "0 12px" } : null}>
      <Ms name={m.icon} />{m.label}
    </span>
  );
}

/* 고객사 아바타 */
function ClientAvatar({ client, size = 36 }) {
  return (
    <div style={{
      width: size, height: size, borderRadius: Math.round(size * 0.28),
      background: `color-mix(in oklab, ${client.color} 16%, white)`,
      color: `color-mix(in oklab, ${client.color} 78%, black)`,
      display: "grid", placeItems: "center", fontWeight: 800,
      fontSize: size * 0.42, flex: "none", letterSpacing: "-0.03em",
      border: `1px solid color-mix(in oklab, ${client.color} 28%, white)`,
    }}>{client.short[0]}</div>
  );
}

/* 급여체계 — enum SSOT (색상은 여기서만 관리, 한 곣에서 파생) ─────────
   조화를 위해 L·C는 고정, hue만 변주 (oklch). 시각적 구분 확보. */
const SCHEME_META = {
  "월급제":     { hue: 264 },
  "시급제":     { hue: 186 },
  "일급제":     { hue: 146 },
  "연봉제":     { hue: 305 },
  "네트제":     { hue: 22 },
  "포괄임금제":  { hue: 58 },
};
function schemeStyle(scheme) {
  const m = SCHEME_META[scheme];
  if (!m) return null;
  return {
    background: `oklch(0.963 0.035 ${m.hue})`,
    color: `oklch(0.46 0.115 ${m.hue})`,
    borderColor: `oklch(0.90 0.055 ${m.hue})`,
  };
}
/* 급여체계 태그 */
function SchemeTag({ scheme, dot }) {
  const s = schemeStyle(scheme);
  return (
    <span className="tag" style={s || undefined}>
      {dot && <span style={{ width: 5, height: 5, borderRadius: 99, background: "currentColor", flex: "none", marginRight: 1 }} />}
      {scheme}
    </span>
  );
}

/* 가로 스텝퍼 */
function Stepper({ flow, currentKey }) {
  const ci = flow.findIndex((f) => f.key === currentKey);
  return (
    <div className="stepper">
      {flow.map((f, i) => {
        const cls = i < ci ? "done" : i === ci ? "current" : "future";
        return (
          <div className={"step " + cls} key={f.key}>
            <div className="step-node">{i < ci ? <Ms name="check" style={{ fontSize: 15 }} /> : i + 1}</div>
            <div className="step-txt">{f.label}</div>
            {i < flow.length - 1 && <div className="step-line" />}
          </div>
        );
      })}
    </div>
  );
}

/* 세로 타임라인 */
function Timeline({ flow, currentKey, history }) {
  const ci = flow.findIndex((f) => f.key === currentKey);
  return (
    <div className="timeline">
      {flow.map((f, i) => {
        const cls = i < ci ? "done" : i === ci ? "current" : "future";
        const h = history && history[f.key];
        return (
          <div className={"tl-item " + cls} key={f.key}>
            <div className="tl-rail">
              <div className="tl-node">{i < ci ? <Ms name="check" style={{ fontSize: 13 }} /> : i === ci ? <span style={{ width: 7, height: 7, borderRadius: 9, background: "var(--accent)" }} /> : ""}</div>
              {i < flow.length - 1 && <div className="tl-bar" />}
            </div>
            <div className="tl-body">
              <div className="tl-title">{f.label}</div>
              <div className="tl-meta">{h || f.desc}</div>
            </div>
          </div>
        );
      })}
    </div>
  );
}

/* 칸반 보드 — 7단계 가로 레인, 고객사 카드를 단계 칸에 배치 */
function Board({ clients, flow, statusOf, activeId, onSelect, onMove, canMove }) {
  return (
    <div className="kb">
      {flow.map((f, i) => {
        const here = clients.filter((c) => statusOf(c.id) === f.key);
        return (
          <div className="kb-lane" key={f.key}>
            <div className="kb-lane-hd">
              <span className="kb-step-no">{i + 1}</span>
              <span className="kb-lane-title">{f.label}</span>
              {here.length > 0 && <span className="kb-count">{here.length}</span>}
            </div>
            <div className="kb-lane-body">
              {here.map((c) => {
                const active = c.id === activeId;
                return (
                  <div className={"kb-card" + (active ? " active" : "")} key={c.id} onClick={() => onSelect && onSelect(c.id)}>
                    <div className="kb-card-top">
                      <ClientAvatar client={c} size={28} />
                      <div style={{ minWidth: 0, flex: 1 }}>
                        <div className="kb-card-name">{c.short}</div>
                        <div className="kb-card-sub">{c.headcount}명</div>
                      </div>
                    </div>
                    {onMove && (() => {
                      // C1 — 게이트가 막으면 버튼도 막고 '왜'를 툴팁으로 말한다.
                      const prevG = i > 0 && canMove ? canMove(c.id, flow[i - 1].key) : { ok: i > 0 };
                      const nextG = i < flow.length - 1 && canMove ? canMove(c.id, flow[i + 1].key) : { ok: i < flow.length - 1 };
                      return (
                      <div className="kb-card-nav">
                        <button className="kb-nav-btn" disabled={i === 0 || !prevG.ok} aria-label={`${c.short} 이전 단계로`} title={i === 0 ? "이전 단계 없음" : prevG.ok ? "이전 단계로" : prevG.reason}
                          onClick={(e) => { e.stopPropagation(); onMove(c.id, flow[i - 1].key, -1); }}><Ms name="chevron_left" style={{ fontSize: 16 }} /></button>
                        <span className="kb-card-stat">{STATUS_META[f.key]?.label}</span>
                        <button className="kb-nav-btn" disabled={i === flow.length - 1 || !nextG.ok} aria-label={`${c.short} 다음 단계로`} title={i === flow.length - 1 ? "마지막 단계" : nextG.ok ? "다음 단계로" : nextG.reason}
                          onClick={(e) => { e.stopPropagation(); onMove(c.id, flow[i + 1].key, 1); }}><Ms name="chevron_right" style={{ fontSize: 16 }} /></button>
                      </div>
                      );
                    })()}
                  </div>
                );
              })}
              {here.length === 0 && <div className="kb-empty" />}
            </div>
          </div>
        );
      })}
    </div>
  );
}

/* diff 배지 */
function Delta({ now, prev, unit }) {
  if (prev === undefined || prev === null) return <span className="delta delta-new">NEW</span>;
  // 시간 델타는 0.1h 단위로 떨어진다 — 부동소수점 잔차(15.199999999999996)를 화면에 흘리지 않는다.
  const d = Math.round((now - prev) * 10) / 10;
  if (d === 0) return <span className="muted" style={{ fontSize: 11.5 }}>—</span>;
  const up = d > 0;
  return <span className={"delta " + (up ? "delta-up" : "delta-down")}>{up ? "▲" : "▼"} {Math.abs(d)}{unit || ""}</span>;
}

/* 모달 */
/* 모달 — Esc 닫기 + 초기 포커스 + 포커스 트랩 (E8).
   트랩이 없으면 Tab이 모달 밖 배경 컨트롤로 새어 나가 키보드 사용자가 길을 잃는다. */
const FOCUSABLE = 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])';
function Modal({ title, children, onClose, footer, width }) {
  const boxRef = useRef(null);
  const titleId = useRef("mt-" + Math.random().toString(36).slice(2, 7)).current;
  useEffect(() => {
    const prev = document.activeElement;
    const box = boxRef.current;
    const items = () => Array.from(box ? box.querySelectorAll(FOCUSABLE) : []).filter((el) => el.offsetParent !== null);
    const first = items()[0];
    (first || box).focus({ preventScroll: true });
    const h = (e) => {
      if (e.key === "Escape") return onClose();
      if (e.key !== "Tab") return;
      const list = items(); if (!list.length) return;
      const a = list[0], z = list[list.length - 1];
      if (e.shiftKey && document.activeElement === a) { e.preventDefault(); z.focus(); }
      else if (!e.shiftKey && document.activeElement === z) { e.preventDefault(); a.focus(); }
      else if (box && !box.contains(document.activeElement)) { e.preventDefault(); a.focus(); }
    };
    window.addEventListener("keydown", h);
    return () => { window.removeEventListener("keydown", h); if (prev && prev.focus) prev.focus({ preventScroll: true }); };
  }, []);
  return (
    <div className="modal-scrim" onMouseDown={onClose}>
      <div className="modal" ref={boxRef} role="dialog" aria-modal="true" aria-labelledby={titleId} tabIndex={-1}
        style={width ? { maxWidth: width } : null} onMouseDown={(e) => e.stopPropagation()}>
        <div className="modal-hd"><h3 id={titleId} style={{ fontSize: 17, fontWeight: 700, letterSpacing: "-0.02em" }}>{title}</h3></div>
        <div className="modal-bd">{children}</div>
        {footer && <div className="modal-ft">{footer}</div>}
      </div>
    </div>
  );
}

/* 게이트 안내 (W3-2) — "왜 지금은 못 쓰는지 + 다음에 뭘 하면 되는지".
   화면을 통째로 흐리게만 하면 사용자는 고장으로 읽는다(E2). */
function GateNotice({ icon, title, body, actionLabel, onAction, tone }) {
  const c = tone === "ok" ? "ok" : "accent";
  return (
    <div className="card card-pad" style={{ display: "flex", gap: 14, alignItems: "center", marginBottom: 16,
      background: `var(--${c}-soft)`, border: `1px solid var(--${c}-line)` }}>
      <Ms name={icon || "info"} style={{ fontSize: 24, color: `var(--${c})`, flex: "none" }} />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontWeight: 650, fontSize: 13.5, color: `var(--${c}-ink)` }}>{title}</div>
        <div style={{ fontSize: 12.5, color: `var(--${c}-ink)`, opacity: .88, marginTop: 3, lineHeight: 1.6 }}>{body}</div>
      </div>
      {onAction && <button className="btn btn-primary btn-sm" style={{ flex: "none" }} onClick={onAction}>{actionLabel}<Ms name="arrow_forward" /></button>}
    </div>
  );
}

/* 카드 헤더 */
function CardHead({ icon, title, sub, right }) {
  return (
    <div className="card-hd">
      {icon && <Ms name={icon} />}
      <div className="card-hd-main">
        <h3>{title}</h3>
        {sub && <span className="card-hd-sub">{sub}</span>}
      </div>
      {right && <div className="card-hd-right">{right}</div>}
    </div>
  );
}

/* 빈 상태 */
function Empty({ icon, title, desc }) {
  return (
    <div className="empty">
      <Ms name={icon || "inbox"} />
      <div style={{ fontWeight: 650, color: "var(--ink-2)", marginTop: 10, fontSize: 15 }}>{title}</div>
      {desc && <div style={{ marginTop: 4 }}>{desc}</div>}
    </div>
  );
}

/* ── §01 검수 findings (자동 점검 신호) ─────────────────── */
const SEV = {
  error: { dot: "var(--danger)", soft: "var(--danger-soft)", line: "var(--danger-line)", ink: "var(--danger)", icon: "error", label: "확정 전 확인 필요" },
  warn:  { dot: "var(--warn)",   soft: "var(--warn-soft)",   line: "var(--warn-line)",   ink: "color-mix(in oklab, var(--warn) 72%, black)", icon: "warning", label: "확인 권장" },
  info:  { dot: "var(--ink-4)",  soft: "var(--bg-sunken)",   line: "var(--line-2)",      ink: "var(--ink-3)", icon: "info", label: "참고" },
};
const SEV_RANK = { error: 3, warn: 2, info: 1 };
function topSeverity(items) {
  let s = null, r = 0;
  (items || []).forEach((f) => { if (SEV_RANK[f.severity] > r) { r = SEV_RANK[f.severity]; s = f.severity; } });
  return s;
}

/* 리스크 카운트 배지 (대시보드 카드·검수 요약) */
function RiskBadge({ errors, warns, infos, size }) {
  const e = errors || 0, w = warns || 0, n = infos || 0;
  const h = size === "sm" ? 18 : 20;
  if (!e && !w && !n) return <span className="tag tag-ok" style={{ height: h }}><Ms name="check_circle" style={{ fontSize: 12 }} />이상 없음</span>;
  const chip = (count, sev) => count > 0 && (
    <span key={sev} style={{ display: "inline-flex", alignItems: "center", gap: 3, height: h, padding: "0 7px", borderRadius: 6, fontSize: 11, fontWeight: 700, fontVariantNumeric: "tabular-nums", background: SEV[sev].soft, color: SEV[sev].ink, border: "1px solid " + SEV[sev].line }}>
      <span style={{ width: 6, height: 6, borderRadius: "50%", background: SEV[sev].dot }} />{count}
    </span>
  );
  return <span style={{ display: "inline-flex", gap: 5, alignItems: "center" }}>{chip(e, "error")}{chip(w, "warn")}{chip(n, "info")}</span>;
}

/* 근로자 행 옆 신호 점 */
function FindingDot({ items }) {
  const sev = topSeverity(items);
  if (!sev) return null;
  const n = (items || []).length;
  return (
    <span title={(items || []).map((f) => "· " + f.message).join("\n")} style={{ display: "inline-flex", alignItems: "center", gap: 3, marginLeft: 6, verticalAlign: "middle" }}>
      <span style={{ width: 8, height: 8, borderRadius: "50%", background: SEV[sev].dot, boxShadow: "0 0 0 3px " + SEV[sev].soft }} />
      {n > 1 && <span style={{ fontSize: 10.5, fontWeight: 700, color: SEV[sev].ink }}>{n}</span>}
    </span>
  );
}

/* findings 항목 (검수 화면 패널) */
function FindingItem({ f, onDismiss, canDismiss }) {
  const s = SEV[f.severity] || SEV.info;
  return (
    <div style={{ display: "flex", gap: 11, padding: "11px 2px", borderBottom: "1px solid var(--line)" }}>
      <Ms name={s.icon} style={{ fontSize: 19, color: s.dot, flex: "none", marginTop: 1 }} />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: "flex", gap: 7, alignItems: "baseline", flexWrap: "wrap" }}>
          {f.empName && <b style={{ fontSize: 13 }}>{f.empName}</b>}
          <span style={{ fontSize: 13, color: "var(--ink)" }}>{f.message}</span>
          <span style={{ fontSize: 10, fontWeight: 700, letterSpacing: ".02em", color: s.ink, background: s.soft, border: "1px solid " + s.line, padding: "0 6px", borderRadius: 5, height: 17, display: "inline-flex", alignItems: "center", fontVariantNumeric: "tabular-nums" }}>{f.code}</span>
        </div>
        {f.hint && <div className="muted" style={{ fontSize: 11.5, marginTop: 2 }}>{f.hint}</div>}
      </div>
      {canDismiss && f.empId && (
        <button className="btn btn-ghost btn-sm" style={{ flex: "none", height: 28 }} title="이 경고를 끕니다 (사유·기간 기록 — 만료되면 다시 뜹니다)" onClick={() => onDismiss(f)}>
          <Ms name="notifications_off" style={{ fontSize: 15 }} />끄기
        </button>
      )}
    </div>
  );
}

Object.assign(window, {
  Ms, KRW, STATUS_META, StatusPill, ClientAvatar, SchemeTag, SCHEME_META, schemeStyle,
  Stepper, Timeline, Board, Delta, Modal, CardHead, Empty,
  SEV, topSeverity, RiskBadge, FindingDot, FindingItem,
  useState, useEffect, useRef, useMemo,
});

/* 인쇄용 문서 창 — 브라우저 기본 "PDF로 저장"을 그대로 쓴다(F4).
   PDF 라이브러리를 얹지 않는 이유: 증명서 1종을 위해 400KB를 로드할 이유가 없다.
   ponytail: 서명·직인 이미지가 필요해지면 그때 라이브러리를 검토한다. */
function printDocument(title, bodyHtml) {
  const w = window.open("", "_blank", "width=760,height=900");
  if (!w) return false;
  w.document.write(`<!doctype html><html lang="ko"><head><meta charset="utf-8"><title>${title}</title>
    <style>
      body{font-family:Pretendard,-apple-system,sans-serif;margin:0;padding:48px 56px;color:#1a1a1e;font-size:13px;line-height:1.6}
      h1{font-size:22px;letter-spacing:-.02em;text-align:center;margin:0 0 4px}
      .sub{text-align:center;color:#77777d;font-size:12px;margin-bottom:22px}
      table{width:100%;border-collapse:collapse;margin:18px 0}
      th,td{border:1px solid #d8d8de;padding:9px 12px;text-align:left}
      th{background:#f5f5f8;width:32%;font-weight:650}
      .foot{margin-top:36px;text-align:center;color:#77777d;font-size:11.5;border-top:1px solid #e5e5ea;padding-top:14px}
      @media print{body{padding:0}}
    </style></head><body>${bodyHtml}</body></html>`);
  w.document.close();
  w.focus();
  setTimeout(() => w.print(), 250);
  return true;
}
window.printDocument = printDocument;
