/* ============================================================
   Shared UI primitives + icon set  (exported to window)
   ============================================================ */
const { useState, useEffect, useRef, useCallback, createContext, useContext } = React;

/* ---------------- Icons (stroke, currentColor) ---------------- */
function Ic({ d, fill, vb = 24, sw = 1.8, children, ...p }) {
  return (
    <svg viewBox={`0 0 ${vb} ${vb}`} fill={fill || 'none'} stroke={fill ? 'none' : 'currentColor'}
      strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" {...p}>
      {children || <path d={d} />}
    </svg>
  );
}
const Icon = {
  home: (p) => <Ic {...p} d="M3 10.5 12 3l9 7.5M5 9.5V20h14V9.5" />,
  grid: (p) => <Ic {...p}><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></Ic>,
  calendar: (p) => <Ic {...p}><rect x="3" y="4.5" width="18" height="16" rx="2.5"/><path d="M3 9h18M8 2.5v4M16 2.5v4"/></Ic>,
  flow: (p) => <Ic {...p}><rect x="2.5" y="9" width="6" height="6" rx="1.5"/><rect x="15.5" y="9" width="6" height="6" rx="1.5"/><path d="M8.5 12h7"/></Ic>,
  list: (p) => <Ic {...p} d="M8 6h13M8 12h13M8 18h13M3.5 6h.01M3.5 12h.01M3.5 18h.01" />,
  layers: (p) => <Ic {...p}><path d="m12 3 9 5-9 5-9-5 9-5Z"/><path d="m3 13 9 5 9-5"/></Ic>,
  users: (p) => <Ic {...p}><circle cx="9" cy="8" r="3.2"/><path d="M3.5 19c0-3 2.6-4.8 5.5-4.8S14.5 16 14.5 19"/><path d="M16 5.2a3.2 3.2 0 0 1 0 6M20.5 19c0-2.4-1.4-4-3.5-4.5"/></Ic>,
  shield: (p) => <Ic {...p} d="M12 3l7 2.5v6c0 4.2-3 7.6-7 9-4-1.4-7-4.8-7-9v-6L12 3Z" />,
  clock: (p) => <Ic {...p}><circle cx="12" cy="12" r="9"/><path d="M12 7.5V12l3 2"/></Ic>,
  plus: (p) => <Ic {...p} d="M12 5v14M5 12h14" sw="2" />,
  check: (p) => <Ic {...p} d="M5 12.5l4.5 4.5L19 7" sw="2.2" />,
  checkCircle: (p) => <Ic {...p}><circle cx="12" cy="12" r="9"/><path d="M8 12.2l2.8 2.8L16 9.4"/></Ic>,
  x: (p) => <Ic {...p} d="M6 6l12 12M18 6 6 18" sw="2" />,
  chevR: (p) => <Ic {...p} d="M9 6l6 6-6 6" />,
  chevD: (p) => <Ic {...p} d="M6 9l6 6 6-6" />,
  chevL: (p) => <Ic {...p} d="M15 6l-6 6 6 6" />,
  chevsL: (p) => <Ic {...p}><path d="M11 17l-5-5 5-5" /><path d="M18 17l-5-5 5-5" /></Ic>,
  chevsR: (p) => <Ic {...p}><path d="M13 17l5-5-5-5" /><path d="M6 17l5-5-5-5" /></Ic>,
  arrowR: (p) => <Ic {...p} d="M5 12h14M13 6l6 6-6 6" />,
  download: (p) => <Ic {...p} d="M12 3v12m0 0l-4-4m4 4l4-4M4 17v2a2 2 0 002 2h12a2 2 0 002-2v-2" />,
  dots: (p) => <Ic {...p} fill="currentColor"><circle cx="12" cy="5" r="1.7"/><circle cx="12" cy="12" r="1.7"/><circle cx="12" cy="19" r="1.7"/></Ic>,
  edit: (p) => <Ic {...p} d="M4 20h4L18.5 9.5a2 2 0 0 0-2.8-2.8L5 17.2 4 20Z" />,
  pause: (p) => <Ic {...p}><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></Ic>,
  play: (p) => <Ic {...p} fill="currentColor"><path d="M7 5l12 7-12 7V5Z"/></Ic>,
  trash: (p) => <Ic {...p} d="M4 7h16M9 7V5h6v2M6 7l1 13h10l1-13" />,
  search: (p) => <Ic {...p}><circle cx="11" cy="11" r="7"/><path d="m20 20-3.2-3.2"/></Ic>,
  logout: (p) => <Ic {...p} d="M15 4h3a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-3M10 16l-4-4 4-4M6 12h11" />,
  user: (p) => <Ic {...p}><circle cx="12" cy="8" r="3.6"/><path d="M5 20c0-3.4 3-5.4 7-5.4s7 2 7 5.4"/></Ic>,
  doc: (p) => <Ic {...p}><path d="M6 3h8l4 4v14H6V3Z"/><path d="M14 3v4h4M9 13h6M9 17h6"/></Ic>,
  inbox: (p) => <Ic {...p}><path d="M3 13l3-8h12l3 8M3 13v6h18v-6M3 13h5l1.5 2.5h5L15 13h6"/></Ic>,
  alert: (p) => <Ic {...p}><circle cx="12" cy="12" r="9"/><path d="M12 7.5v5M12 16h.01"/></Ic>,
  spark: (p) => <Ic {...p} fill="currentColor"><path d="M12 2l1.8 5.6L19.5 9l-4.2 3.1 1.6 5.6L12 14.7 7.1 17.7l1.6-5.6L4.5 9l5.7-1.4L12 2Z"/></Ic>,
  target: (p) => <Ic {...p}><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="4"/><circle cx="12" cy="12" r=".6" fill="currentColor"/></Ic>,
  menu: (p) => <Ic {...p} d="M4 7h16M4 12h16M4 17h16" sw="2" />,
  send: (p) => <Ic {...p} d="M21 3 10 14M21 3l-7 18-4-7-7-4 18-7Z" />,
  filter: (p) => <Ic {...p} d="M3 5h18l-7 8v6l-4-2v-4L3 5Z" />,
};

/* ---------------- Avatar ---------------- */
function Avatar({ name, color = '#C8102E', initial, size = 34, square }) {
  return (
    <span className="avatar" style={{ background: color, width: size, height: size, borderRadius: square ? 9 : '50%', fontSize: size * 0.4 }}>
      {initial || (name || '?')[0]}
    </span>
  );
}

/* ---------------- Status pill / role badge ---------------- */
const STATUS_LABEL = { not_started: '시작 전', active: '진행 중', done: '완료' };
function StatusPill({ status }) {
  return <span className={`pill pill-${status}`}><span className="dot" />{STATUS_LABEL[status] || status}</span>;
}
function RoleBadge({ role }) {
  const cls = role === 'ADMIN' ? 'rb-admin' : role === 'CNX' ? 'rb-cnx' : role === 'PUB' ? 'rb-pub' : 'rb-lge';
  return <span className={`role-badge ${cls}`}>{role === 'PUB' ? '발행' : role}</span>;
}

/* ---------------- Button ---------------- */
function Btn({ variant = 'ghost', size, icon, children, className = '', ...p }) {
  const sz = size === 'sm' ? 'btn-sm' : size === 'lg' ? 'btn-lg' : '';
  return (
    <button className={`btn btn-${variant} ${sz} ${className}`} {...p}>
      {icon}{children}
    </button>
  );
}

/* ---------------- Dropdown menu ---------------- */
function Menu({ trigger, children, align = 'right' }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    if (!open) return;
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [open]);
  return (
    <span className="menu-wrap" ref={ref}>
      <span onClickCapture={(e) => { e.stopPropagation(); setOpen((o) => !o); }}>{trigger}</span>
      {open && <div className="menu" style={align === 'left' ? { left: 0, right: 'auto' } : null} onClick={(e) => { e.stopPropagation(); setOpen(false); }}>{children}</div>}
    </span>
  );
}

/* ---------------- Modal ---------------- */
function Modal({ title, sub, onClose, children, footer, wide }) {
  useEffect(() => {
    const h = (e) => e.key === 'Escape' && onClose && onClose();
    document.addEventListener('keydown', h);
    return () => document.removeEventListener('keydown', h);
  }, [onClose]);
  return (
    <div className="overlay" onMouseDown={(e) => e.target === e.currentTarget && onClose && onClose()}>
      <div className={`modal ${wide ? 'modal-lg' : ''}`} role="dialog" aria-modal="true">
        {title && (
          <div className="modal-hd">
            <div style={{ flex: 1 }}><h3>{title}</h3>{sub && <p>{sub}</p>}</div>
            {onClose && <button className="icon-btn" onClick={onClose} aria-label="닫기"><Icon.x /></button>}
          </div>
        )}
        <div className="modal-bd">{children}</div>
        {footer && <div className="modal-ft">{footer}</div>}
      </div>
    </div>
  );
}

/* ---------------- Drawer ---------------- */
function Drawer({ onClose, head, children, footer }) {
  useEffect(() => {
    const h = (e) => e.key === 'Escape' && onClose && onClose();
    document.addEventListener('keydown', h);
    return () => document.removeEventListener('keydown', h);
  }, [onClose]);
  return (
    <div className="drawer-overlay" onMouseDown={(e) => e.target === e.currentTarget && onClose && onClose()}>
      <div className="drawer" role="dialog" aria-modal="true">
        <div className="drawer-hd">{head}</div>
        <div className="drawer-bd">{children}</div>
        {footer && <div className="drawer-ft">{footer}</div>}
      </div>
    </div>
  );
}

/* ---------------- Toast ---------------- */
const ToastCtx = createContext(null);
function ToastProvider({ children }) {
  const [toasts, setToasts] = useState([]);
  const push = useCallback((msg, kind = 'ok') => {
    const id = Math.random().toString(36).slice(2);
    setToasts((t) => [...t, { id, msg, kind }]);
    setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 3200);
  }, []);
  return (
    <ToastCtx.Provider value={push}>
      {children}
      <div className="toast-host">
        {toasts.map((t) => (
          <div key={t.id} className={`toast ${t.kind}`}>
            <span className="tdot">{t.kind === 'ok' ? <Icon.check /> : <Icon.x />}</span>
            {t.msg}
          </div>
        ))}
      </div>
    </ToastCtx.Provider>
  );
}
const useToast = () => useContext(ToastCtx);

/* ---------------- Skeleton helpers ---------------- */
function Skel({ w, h = 14, r = 6, style }) { return <span className="skel" style={{ display: 'block', width: w || '100%', height: h, borderRadius: r, ...style }} />; }
function CardSkel() {
  return (
    <div className="card" style={{ padding: 20 }}>
      <Skel w="55%" h={18} /><Skel w="38%" h={12} style={{ marginTop: 10 }} />
      <div style={{ marginTop: 22, display: 'flex', flexDirection: 'column', gap: 12 }}>
        <Skel h={8} /><Skel h={8} />
      </div>
      <Skel w="100%" h={1} style={{ marginTop: 18 }} />
    </div>
  );
}

/* ---------------- Empty / Error ---------------- */
function EmptyState({ icon, title, desc, action }) {
  return (
    <div className="empty-state">
      <div className="empty-ic">{icon || <Icon.inbox />}</div>
      <h4>{title}</h4>{desc && <p>{desc}</p>}{action}
    </div>
  );
}
function ErrorState({ onRetry, msg }) {
  return (
    <div className="empty-state">
      <div className="empty-ic" style={{ background: 'var(--lg-red-50)', color: 'var(--lg-red)' }}><Icon.alert /></div>
      <h4>불러오지 못했습니다</h4>
      <p>{msg || '데이터를 가져오는 중 문제가 발생했습니다.'}</p>
      {onRetry && <Btn variant="primary" onClick={onRetry} style={{ marginTop: 18 }}>다시 시도</Btn>}
    </div>
  );
}

/* ---------------- async data hook ---------------- */
function useAsync(fn, deps) {
  const [state, setState] = useState({ loading: true, error: null, data: null });
  const run = useCallback(() => {
    setState({ loading: true, error: null, data: null });
    fn().then((data) => setState({ loading: false, error: null, data }))
      .catch((e) => setState({ loading: false, error: e.message || '오류', data: null }));
  }, deps || []);
  useEffect(() => { run(); }, [run]);
  return { ...state, reload: run, setData: (d) => setState((s) => ({ ...s, data: typeof d === 'function' ? d(s.data) : d })) };
}

/* ---------------- progress ring ---------------- */
function Ring({ value, total, size = 44, stroke = 4, color }) {
  const pct = total ? Math.min(value / total, 1) : 0;
  const r = (size - stroke) / 2, c = 2 * Math.PI * r;
  const col = color || (value === total && total ? 'var(--pub-green)' : value > total ? 'var(--lg-red)' : 'var(--amber)');
  return (
    <span className="quota-ring" style={{ width: size, height: size }}>
      <svg width={size} height={size}>
        <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="var(--slate-50)" strokeWidth={stroke} />
        <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke={col} strokeWidth={stroke} strokeLinecap="round"
          strokeDasharray={c} strokeDashoffset={c * (1 - pct)} transform={`rotate(-90 ${size / 2} ${size / 2})`} style={{ transition: 'stroke-dashoffset .5s, stroke .3s' }} />
      </svg>
      <span className="qn" style={{ color: col }}>{value}/{total}</span>
    </span>
  );
}

/* ---------------- date helpers ---------------- */
const fmtDate = (iso) => { const d = new Date(iso); return `${d.getFullYear()}.${String(d.getMonth() + 1).padStart(2, '0')}.${String(d.getDate()).padStart(2, '0')}`; };
const fmtDateTime = (iso) => { const d = new Date(iso); return `${fmtDate(iso)} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; };
const fmtMD = (iso) => { const d = new Date(iso); return `${d.getMonth() + 1}/${d.getDate()}`; };

Object.assign(window, {
  Icon, Avatar, StatusPill, RoleBadge, Btn, Menu, Modal, Drawer,
  ToastProvider, useToast, Skel, CardSkel, EmptyState, ErrorState, useAsync, Ring,
  fmtDate, fmtDateTime, fmtMD, STATUS_LABEL,
});
