/* ============================================================
   바른셈 — 셸(shell) 표현 컴포넌트
   워크스페이스 헤더 · 역할/스코프/귀속월 전환 · 알림 · 토스트 · Tweaks.
   상태를 갖지 않고 props만 받는다 — app.jsx의 상태 레이어와 분리(E14 / W3-8).
   ============================================================ */
function WorkspaceHeader({ client, status, view, go, clients, clientIdx, gotoClient, selectClient, statuses: statusOf, period, switcherOpen, setSwitcherOpen, userOf, teamOf, user }) {
  return (
    <div className="ws">
      <div className="ws-top">
        <div className="ws-id">
          <ClientAvatar client={client} size={42} />
          <div>
            <div className="ws-name-row">
              <button className="ws-switch" onClick={() => setSwitcherOpen((v) => !v)} title="고객사 전환">
                <span className="ws-name">{client.name}</span><Ms name="unfold_more" />
              </button>
              <StatusPill status={status} size="lg" />
            </div>
            <div className="ws-meta">{period.label} 귀속 · {client.headcount}명 · 사업장관리번호 {client.managedNo}</div>
            {userOf && (() => { const team = teamOf && teamOf(client.teamId); const pa = userOf(client.primaryAssigneeId); const ba = userOf(client.backupAssigneeId); const ap = userOf(client.approverId); return (
              <div className="ws-assignees">
                {team && <span className="tag"><Ms name="groups" style={{ fontSize: 13 }} />{team.name}</span>}
                {pa && <AssigneeChip user={pa} label="담당" me={user && pa.id === user.id} />}
                {ba && <AssigneeChip user={ba} label="백업" me={user && ba.id === user.id} />}
                {ap && <AssigneeChip user={ap} label="승인" icon="approval" me={user && ap.id === user.id} />}
                {client.autoApprove && <span className="tag tag-accent" title="승인 단계 생략 — 1인 운영"><Ms name="bolt" style={{ fontSize: 12 }} />자동승인</span>}
              </div>
            ); })()}
          </div>
          {switcherOpen && (
            <>
              <div className="ws-scrim" onClick={() => setSwitcherOpen(false)} />
              <div className="ws-pop">
                <div className="ws-pop-hd">고객사 전환 · {clients.length}곳</div>
                {clients.map((c) => {
                  const cs = statusOf(c.id);
                  return (
                    <button key={c.id} className={"cli-item" + (c.id === client.id ? " active" : "")} onClick={() => selectClient(c.id)}>
                      <ClientAvatar client={c} size={26} />
                      <div className="cli-item-main">
                        <div className="cli-item-name">{c.name}</div>
                        <div className="cli-item-sub"><span className="sdot" style={{ background: STATUS_TONE[cs] }} />{STATUS_META[cs] ? STATUS_META[cs].label : cs}</div>
                      </div>
                      {c.id === client.id && <Ms name="check" style={{ fontSize: 17, color: "var(--accent)" }} />}
                    </button>
                  );
                })}
              </div>
            </>
          )}
        </div>
        <div className="ws-spacer" />
        <div className="ws-queue">
          <button className="ws-qbtn" onClick={() => gotoClient(-1)} title="이전 고객사"><Ms name="chevron_left" /></button>
          <span className="ws-qcount">{clientIdx + 1} / {clients.length}</span>
          <button className="ws-qbtn" onClick={() => gotoClient(1)} title="다음 고객사"><Ms name="chevron_right" /></button>
        </div>
      </div>
      <div className="ws-tabs">
        {CLIENT_TABS.map((tab) => {
          const needs = tab.key === "review" && ["submitted", "in_review"].includes(status);
          return (
            <button key={tab.key} className={"ws-tab" + (view === tab.key ? " on" : "")} onClick={() => go(tab.key, { clientId: client.id })}>
              <Ms name={tab.icon} />{tab.label}
              {needs && <span className="pill pill-warn">1</span>}
            </button>
          );
        })}
        <span className="ws-tab-sep" aria-hidden="true" />
        {ANNUAL_TABS.map((tab) => (
          <button key={tab.key} className={"ws-tab ws-tab-annual" + (view === tab.key ? " on" : "")} onClick={() => go(tab.key, { clientId: client.id })}
            title="연 1회 작업 — 월 마감과 별개">
            <Ms name={tab.icon} />{tab.label}
            <span className="tag" style={{ height: 16, fontSize: 9.5, padding: "0 5px", marginLeft: 4 }}>연간</span>
          </button>
        ))}
      </div>
    </div>
  );
}

/* 역할 전환 — 4역할 사용자 드롭다운 (RBAC 데모). 승인 대기 큐 배지 동반. */
function RoleToggle({ users, actorId, setActorId, ROLE_LABEL, approvalQueue, go }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  const u = users.find((x) => x.id === actorId) || users[0];
  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);
  const qn = approvalQueue ? approvalQueue.length : 0;
  return (
    <div className="pp" ref={ref} style={{ position: "relative" }}>
      <button className={"pp-trigger" + (open ? " open" : "")} onClick={() => setOpen((o) => !o)} title="역할 전환 (RBAC 데모)">
        <span className="rt-av" style={{ width: 20, height: 20, borderRadius: 6, background: "var(--accent)", color: "#fff", display: "grid", placeItems: "center", fontSize: 11, fontWeight: 800, flex: "none" }}>{u.avatar}</span>
        <span style={{ fontWeight: 650 }}>{u.name}</span>
        <span className="tag" style={{ height: 17, fontSize: 10, padding: "0 5px" }}>{ROLE_LABEL[u.roles[0]]}</span>
        {qn > 0 && <span className="pill pill-warn" style={{ height: 17, fontSize: 10, padding: "0 6px" }}>승인 {qn}</span>}
        <Ms name="expand_more" className="pp-chev" />
      </button>
      {open && (
        <div className="pp-pop" role="listbox" style={{ minWidth: 244 }}>
          <div className="pp-group">역할 전환 — 권한 매트릭스 데모</div>
          {users.map((x) => {
            const sel = x.id === actorId;
            return (
              <button key={x.id} className={"pp-item" + (sel ? " sel" : "")} onClick={() => { setActorId(x.id); setOpen(false); }} style={{ alignItems: "flex-start", height: "auto", padding: "9px 12px" }}>
                <span style={{ width: 28, height: 28, borderRadius: 8, background: sel ? "var(--accent)" : "var(--bg-sunken)", color: sel ? "#fff" : "var(--ink-2)", display: "grid", placeItems: "center", fontSize: 13, fontWeight: 800, flex: "none" }}>{x.avatar}</span>
                <span style={{ flex: 1, minWidth: 0, textAlign: "left" }}>
                  <span style={{ display: "flex", alignItems: "center", gap: 6 }}><b style={{ fontSize: 13 }}>{x.name}</b><span className="tag" style={{ height: 16, fontSize: 9.5, padding: "0 5px" }}>{ROLE_LABEL[x.roles[0]]}</span><span className="tag" style={{ height: 16, fontSize: 9.5, padding: "0 5px" }}>{x.license}</span></span>
                  <span className="muted" style={{ fontSize: 11, display: "block", marginTop: 1 }}>{x.sub}</span>
                </span>
                {sel && <Ms name="check" style={{ fontSize: 16, color: "var(--accent)" }} />}
              </button>
            );
          })}
          {qn > 0 && (
            <button className="pp-item" style={{ borderTop: "1px solid var(--line)", color: "var(--accent-ink)" }} onClick={() => { setOpen(false); go("dashboard"); }}>
              <Ms name="approval" style={{ fontSize: 16 }} /><span style={{ fontWeight: 650, fontSize: 12.5 }}>승인 대기 {qn}건 — 대시보드에서 처리</span>
            </button>
          )}
        </div>
      )}
    </div>
  );
}

/* 스코프 전환 — 내 담당 / 우리 팀 / 전사 (사이드바) */
function ScopeSwitch({ scope, setScope, counts }) {
  const opts = [{ k: "mine", t: "내 담당" }, { k: "team", t: "우리 팀" }, { k: "firm", t: "전사" }];
  return (
    <div className="scope-switch">
      {opts.map((o) => (
        <button key={o.k} className={"scope-btn" + (scope === o.k ? " on" : "")} onClick={() => setScope(o.k)}>
          {o.t}<span className="scope-count">{counts[o.k]}</span>
        </button>
      ))}
    </div>
  );
}

/* 귀속월 선택기 — shadcn 스타일 커스텀 팝오버 (과거·현재·예정 귀속) */
function PeriodPicker({ periods, value, onChange }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  const cur = periods.find((p) => p.ym === value) || periods[0];

  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onKey); };
  }, [open]);

  const TAG = { current: { t: "진행 중", c: "cur" }, upcoming: { t: "예정", c: "up" }, past: { t: "마감", c: "past" } };
  const upcoming = periods.filter((p) => p.state === "upcoming");
  const current = periods.filter((p) => p.state === "current");
  const past = periods.filter((p) => p.state === "past");

  const Item = (p) => {
    const m = TAG[p.state] || TAG.past;
    const sel = p.ym === value;
    return (
      <button key={p.ym} className={"pp-item" + (sel ? " sel" : "")} role="option" aria-selected={sel}
        onClick={() => { onChange(p.ym); setOpen(false); }}>
        <Ms name="check" className="pp-check" />
        <span className="pp-label">{p.label}</span>
        <span className={"pp-tag " + m.c}>{m.t}</span>
      </button>
    );
  };

  return (
    <div className="pp" ref={ref}>
      <button className={"pp-trigger" + (open ? " open" : "")} onClick={() => setOpen((o) => !o)}
        aria-haspopup="listbox" aria-expanded={open}>
        <Ms name="calendar_month" className="pp-cal" />
        <span>{cur.label}</span>
        <span className="pp-suffix">귀속</span>
        <Ms name="expand_more" className="pp-chev" />
      </button>
      {open && (
        <div className="pp-pop" role="listbox">
          {upcoming.length > 0 && (<>
            <div className="pp-group">예정</div>
            {upcoming.map(Item)}
          </>)}
          {current.length > 0 && (<>
            {upcoming.length > 0 && <div className="pp-sep" />}
            {current.map(Item)}
          </>)}
          {past.length > 0 && (<>
            <div className="pp-sep" />
            <div className="pp-group"><Ms name="history" style={{ fontSize: 14 }} />지난 귀속</div>
            <div className="pp-scroll">{past.map(Item)}</div>
          </>)}
        </div>
      )}
    </div>
  );
}

/* 담당자 칩 (워크스페이스 헤더) */
function AssigneeChip({ user, label, icon, me }) {
  if (!user) return null;
  return (
    <span className="ws-assignee" title={label + ": " + user.name + " · " + user.title} style={me ? { borderColor: "var(--accent-line)", background: "var(--accent-soft)" } : null}>
      <span className="ws-assignee-av">{user.avatar}</span>
      <span className="ws-assignee-label">{icon ? <Ms name={icon} style={{ fontSize: 12, verticalAlign: "-2px" }} /> : null}{label}</span>
      <b>{user.name}</b>
      {me && <span className="ws-assignee-me">나</span>}
    </span>
  );
}

/* 토스트 — aria-live로 스크린리더에 낭독시킨다. 없으면 시각 사용자에게만 결과가 전달된다(E8). */
/* 알림 패널 — 지금 내가 할 일만. "읽을거리"가 아니라 "갈 곳"이다. */
function NotifPanel({ items, onClose, go }) {
  useEffect(() => {
    const h = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, []);
  return (
    <>
      <div style={{ position: "fixed", inset: 0, zIndex: 60 }} onMouseDown={onClose} />
      <div className="card" role="dialog" aria-label="알림"
        style={{ position: "absolute", top: 52, right: 16, width: 340, maxHeight: 420, overflowY: "auto", zIndex: 61, boxShadow: "var(--sh-2)" }}>
        <div className="card-hd"><Ms name="notifications" /><div className="card-hd-main"><h3>알림</h3><span className="card-hd-sub">{items.length}건</span></div></div>
        {items.length === 0
          ? <div className="muted" style={{ padding: "22px 20px", fontSize: 12.5, textAlign: "center" }}>지금 처리할 알림이 없습니다.</div>
          : items.map((n) => (
            <button key={n.id} className="notif-row" onClick={() => { n.go(); onClose(); }}
              style={{ display: "flex", gap: 11, width: "100%", textAlign: "left", padding: "11px 18px", background: "none", border: "none", borderTop: "1px solid var(--line)", cursor: "pointer", fontFamily: "inherit" }}>
              <Ms name={n.icon} style={{ fontSize: 19, color: n.tone === "danger" ? "var(--danger)" : n.tone === "warn" ? "var(--warn)" : "var(--accent)", flex: "none", marginTop: 1 }} />
              <span style={{ minWidth: 0 }}>
                <span style={{ display: "block", fontWeight: 650, fontSize: 12.5 }}>{n.title}</span>
                <span className="muted" style={{ display: "block", fontSize: 11.5, lineHeight: 1.5 }}>{n.body}</span>
              </span>
            </button>
          ))}
      </div>
    </>
  );
}

function ToastWrap({ toasts }) {
  return (
    <div className="toast-wrap" role="status" aria-live="polite" aria-atomic="false">
      {toasts.map((t) => (
        <div key={t.id} className={"toast" + (t.kind === "ok" ? " ok" : "")}>
          <Ms name={t.kind === "ok" ? "check_circle" : "info"} aria-hidden="true" />{t.msg}
        </div>
      ))}
    </div>
  );
}

function Tweaks({ t, setTweak }) {
  return (
    <TweaksPanel title="Tweaks">
      <TweakSection label="스타일 프리셋" />
      <TweakSelect
        label="전체 룩"
        value={t.stylePreset}
        options={[
          { value: "fintech", label: "차분한 핀테크" },
          { value: "workbench", label: "데이터 워크벤치" },
          { value: "paper", label: "종이 문서 톤" },
        ]}
        onChange={(v) => setTweak("stylePreset", v)}
      />
      <div style={{ fontSize: 10.5, lineHeight: 1.45, color: "rgba(41,38,27,.5)", marginTop: -3 }}>
        {{
          fintech: "뉴트럴 + 인디고, 부드러운 라운드 — 기본 톤.",
          workbench: "정보밀도↑, 각진 모서리, 모노 숫자, 평면 표 중심.",
          paper: "세리프 제목 · 따뜻한 크림 뉴트럴 — 명세서·대장이 주인공.",
        }[t.stylePreset]}
      </div>
      <TweakSection label="워크플로 표현" />
      <TweakRadio label="검수 단계 표시" value={t.reviewStyle} options={[{ value: "stepper", label: "스텝퍼" }, { value: "timeline", label: "타임라인" }, { value: "board", label: "보드" }]} onChange={(v) => setTweak("reviewStyle", v)} />
      <TweakSection label="계산 분해 뷰" />
      <TweakRadio label="레이아웃" value={t.calcLayout} options={[{ value: "waterfall", label: "워터폴" }, { value: "table", label: "표" }]} onChange={(v) => setTweak("calcLayout", v)} />
      <TweakSection label="비주얼" />
      <TweakColor label="액센트" value={t.accent} options={["#3f44a0", "#2a6fdb", "#1f7a6e", "#5a3f8c"]} onChange={(v) => setTweak("accent", v)} />
      <TweakRadio label="밀도" value={t.density} options={[{ value: "compact", label: "조밀" }, { value: "regular", label: "기본" }, { value: "comfy", label: "여유" }]} onChange={(v) => setTweak("density", v)} />
    </TweaksPanel>
  );
}
