// ============================================================================
//  UI primitives — icons, badges, score ring, toast host, helpers
// ============================================================================
const { useState, useEffect, useRef, useCallback, createContext, useContext } = React;

// ---- minimal geometric icons (no hand-drawn illustration) ------------------
function Icon({ name, size = 16, stroke = 1.75, style }) {
  const p = { fill: 'none', stroke: 'currentColor', strokeWidth: stroke, strokeLinecap: 'round', strokeLinejoin: 'round' };
  const paths = {
    check:    <polyline points="3.5 8.5 7 12 12.5 4.5" {...p} />,
    x:        <g {...p}><line x1="4" y1="4" x2="12" y2="12" /><line x1="12" y1="4" x2="4" y2="12" /></g>,
    arrow:    <g {...p}><line x1="3" y1="8" x2="13" y2="8" /><polyline points="9 4 13 8 9 12" /></g>,
    chevron:  <polyline points="6 4 10 8 6 12" {...p} />,
    play:     <polygon points="5 3.5 13 8 5 12.5" fill="currentColor" stroke="none" />,
    code:     <g {...p}><polyline points="6 5 3 8 6 11" /><polyline points="10 5 13 8 10 11" /></g>,
    grid:     <g {...p}><rect x="3" y="3" width="4" height="4" /><rect x="9" y="3" width="4" height="4" /><rect x="3" y="9" width="4" height="4" /><rect x="9" y="9" width="4" height="4" /></g>,
    folder:   <path d="M2.5 5C2.5 4.4 3 4 3.5 4H6l1.2 1.4H12.5c.6 0 1 .5 1 1V11.5c0 .6-.4 1-1 1h-9c-.5 0-1-.4-1-1z" {...p} />,
    user:     <g {...p}><circle cx="8" cy="5.5" r="2.5" /><path d="M3.5 13c0-2.5 2-4 4.5-4s4.5 1.5 4.5 4" /></g>,
    users:    <g {...p}><circle cx="6" cy="5.5" r="2" /><path d="M2.5 13c0-2 1.5-3.3 3.5-3.3" /><circle cx="11" cy="6" r="1.8" /><path d="M9 13c0-2 1.3-3.2 3-3.2s3 1.2 3 3.2" /></g>,
    book:     <g {...p}><path d="M3 4.5C3 3.7 3.7 3 4.5 3H13v9.5H4.5C3.7 12.5 3 11.8 3 11z" /><line x1="6" y1="6" x2="10" y2="6" /></g>,
    file:     <g {...p}><path d="M4 2.5h5l3 3V13.5H4z" /><polyline points="9 2.5 9 5.5 12 5.5" /></g>,
    lock:     <g {...p}><rect x="3.5" y="7" width="9" height="6.5" rx="1.2" /><path d="M5.5 7V5a2.5 2.5 0 0 1 5 0v2" /></g>,
    logout:   <g {...p}><path d="M9 3H4.5C3.7 3 3 3.7 3 4.5v7c0 .8.7 1.5 1.5 1.5H9" /><polyline points="11 5.5 13.5 8 11 10.5" /><line x1="6.5" y1="8" x2="13.5" y2="8" /></g>,
    plus:     <g {...p}><line x1="8" y1="3.5" x2="8" y2="12.5" /><line x1="3.5" y1="8" x2="12.5" y2="8" /></g>,
    trash:    <g {...p}><polyline points="3.5 4.5 12.5 4.5" /><path d="M5 4.5V3.5h6v1M5.5 4.5l.5 8h4l.5-8" /></g>,
    filter:   <polygon points="3 4 13 4 9.5 8.5 9.5 12.5 6.5 11 6.5 8.5" {...p} />,
    bolt:     <polygon points="9 2 4 9 7.5 9 6.5 14 12 7 8.5 7" fill="currentColor" stroke="none" />,
    dot:      <circle cx="8" cy="8" r="3" fill="currentColor" stroke="none" />,
  };
  return <svg width={size} height={size} viewBox="0 0 16 16" style={style} aria-hidden="true">{paths[name]}</svg>;
}

function Badge({ kind, children, style }) {
  return <span className={`badge ${kind || ''}`} style={style}>{children}</span>;
}
function RoleBadge({ role }) {
  return <span className={`badge role-${role}`}><span className="badge-dot"></span>{role}</span>;
}
function PassBadge({ passed }) {
  return passed
    ? <span className="badge pass"><Icon name="check" size={12} stroke={2.2} />PASS</span>
    : <span className="badge fail"><Icon name="x" size={12} stroke={2.2} />FAIL</span>;
}

function Spinner({ children }) {
  return <span style={{ display: 'inline-flex', alignItems: 'center', gap: 9 }}><span className="spin"></span>{children}</span>;
}

// circular score gauge --------------------------------------------------------
function ScoreRing({ value, total, passed, size = 116 }) {
  const r = (size - 14) / 2;
  const c = 2 * Math.PI * r;
  const pct = total ? value / total : 0;
  const col = passed ? 'var(--pass)' : (pct > 0 ? 'var(--warn)' : 'var(--fail)');
  return (
    <div style={{ position: 'relative', width: size, height: size, flexShrink: 0 }}>
      <svg width={size} height={size} style={{ transform: 'rotate(-90deg)' }}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="var(--line)" strokeWidth="7" />
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={col} strokeWidth="7" strokeLinecap="round"
          strokeDasharray={c} strokeDashoffset={c * (1 - pct)}
          style={{ transition: 'stroke-dashoffset .7s cubic-bezier(.2,.8,.2,1), stroke .3s', filter: `drop-shadow(0 0 7px ${col})` }} />
      </svg>
      <div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', textAlign: 'center' }}>
        <div>
          <div style={{ fontFamily: 'var(--mono)', fontSize: 28, fontWeight: 700, lineHeight: 1, color: col }}>{value}</div>
          <div style={{ fontFamily: 'var(--mono)', fontSize: 13, color: 'var(--faint)', marginTop: 2 }}>/ {total}</div>
        </div>
      </div>
    </div>
  );
}

// ---- toast bus -------------------------------------------------------------
const ToastCtx = createContext(null);
function ToastHost({ children }) {
  const [items, setItems] = useState([]);
  const push = useCallback((msg, kind) => {
    const id = Math.random().toString(36).slice(2);
    setItems((x) => [...x, { id, msg, kind }]);
    setTimeout(() => setItems((x) => x.filter((t) => t.id !== id)), 3400);
  }, []);
  return (
    <ToastCtx.Provider value={push}>
      {children}
      <div className="toast-wrap">
        {items.map((t) => (
          <div key={t.id} className={`toast ${t.kind || ''}`}>
            <span style={{ color: t.kind === 'err' ? 'var(--fail)' : 'var(--acc)', display: 'inline-flex' }}>
              <Icon name={t.kind === 'err' ? 'x' : 'check'} size={15} stroke={2.2} />
            </span>
            {t.msg}
          </div>
        ))}
      </div>
    </ToastCtx.Provider>
  );
}
const useToast = () => useContext(ToastCtx);

// ---- time formatting -------------------------------------------------------
// DB timestamps are UTC ("YYYY-MM-DD HH:MM:SS"). Render them in the course
// timezone — UTC+8 (China) — regardless of where the viewer's browser is.
function fmtTime(utc) {
  if (!utc) return '';
  const d = new Date(String(utc).replace(' ', 'T') + 'Z'); // parse as UTC, not local
  if (isNaN(d.getTime())) return utc;
  return d.toLocaleString('sv-SE', { timeZone: 'Asia/Shanghai' }); // → "2026-06-19 20:30:01"
}

// ---- empty / loading states ------------------------------------------------
function Loading({ label = 'Loading' }) {
  return (
    <div style={{ display: 'grid', placeItems: 'center', padding: '70px 0', color: 'var(--muted)' }}>
      <Spinner>{label}…</Spinner>
    </div>
  );
}
function Empty({ icon = 'dot', title, sub }) {
  return (
    <div className="panel panel-pad" style={{ textAlign: 'center', padding: '52px 28px', color: 'var(--muted)' }}>
      <div style={{ display: 'inline-grid', placeItems: 'center', width: 46, height: 46, borderRadius: 12, border: '1px solid var(--line)', color: 'var(--faint)', marginBottom: 14 }}>
        <Icon name={icon} size={20} />
      </div>
      <div style={{ fontWeight: 600, fontSize: 16, color: 'var(--text)' }}>{title}</div>
      {sub && <div style={{ fontSize: 14, marginTop: 6 }}>{sub}</div>}
    </div>
  );
}

// ---- modal overlay ---------------------------------------------------------
// Dismiss on Escape or backdrop click; the inner panel stops propagation.
function Modal({ title, sub, onClose, children, wide }) {
  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 60, display: 'grid', placeItems: 'center', padding: 24, background: 'oklch(0 0 0 / 0.62)', backdropFilter: 'blur(3px)' }}>
      <div onClick={(e) => e.stopPropagation()} className="panel fade-up" style={{ width: wide ? 880 : 560, maxWidth: '100%', maxHeight: '86vh', display: 'flex', flexDirection: 'column' }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, padding: '16px 20px', borderBottom: '1px solid var(--line)' }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontWeight: 600, fontSize: 16, letterSpacing: '-0.2px' }}>{title}</div>
            {sub && <div style={{ color: 'var(--muted)', fontSize: 13, marginTop: 3 }}>{sub}</div>}
          </div>
          <button className="btn btn-ghost btn-sm" onClick={onClose} title="Close (Esc)"><Icon name="x" size={14} stroke={2.2} /></button>
        </div>
        <div style={{ padding: 20, overflow: 'auto' }}>{children}</div>
      </div>
    </div>
  );
}

Object.assign(window, { Icon, Badge, RoleBadge, PassBadge, Spinner, ScoreRing, ToastHost, useToast, Loading, Empty, Modal, fmtTime });
