// =====================================================================
// irikos — Classrooms shared components.
// Avatar, JoinCode, Tabs, @autocomplete, status pills, family bars,
// completion ring, mini activity chart, skeletons, empty/error states,
// toast, modal. Globals: React, FAMILY_META, CLASS_FAMILIES.
// =====================================================================

const { useState: shUseState, useEffect: shUseEffect, useRef: shUseRef, useMemo: shUseMemo } = React;

function initials(name) {
  return (name || "?").split(/\s+/).slice(0, 2).map(w => w[0] || "").join("").toUpperCase();
}
function relTime(ts) {
  if (!ts) return "";
  const diff = Date.now() - ts;
  const d = Math.floor(diff / 86400000);
  if (d <= 0) return "today";
  if (d === 1) return "yesterday";
  if (d < 7) return `${d}d ago`;
  if (d < 30) return `${Math.floor(d / 7)}w ago`;
  return `${Math.floor(d / 30)}mo ago`;
}
function seenLabel(daysAgo) {
  if (daysAgo === 0) return "active today";
  if (daysAgo === 1) return "yesterday";
  if (daysAgo < 7) return `${daysAgo}d ago`;
  return `${Math.floor(daysAgo / 7)}w ago`;
}
function lvl(acc) { // acc 0..1
  if (acc >= 0.8) return "ok";
  if (acc >= 0.5) return "warn";
  return "no";
}

function Avatar({ name, hue, size }) {
  return (
    <div className={`avatar ${size || ""}`} style={{ "--h": hue ?? 200 }} aria-hidden="true">
      {initials(name)}
    </div>
  );
}

// ---------- copyable join code ----------
function JoinCode({ code, onRotate }) {
  const [done, setDone] = shUseState(false);
  const copy = () => {
    try { navigator.clipboard?.writeText(code); } catch (e) {}
    setDone(true);
    setTimeout(() => setDone(false), 1600);
  };
  return (
    <div className="joincode">
      <span className="joincode-val">{code}</span>
      <button className={`joincode-btn ${done ? "is-done" : ""}`} onClick={copy} aria-label="Copy join code">
        {done ? "✓ Copied" : "Copy"}
      </button>
      {onRotate && (
        <button className="joincode-rotate" onClick={onRotate} aria-label="Generate a new join code" title="New code">
          <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
            <path d="M21 12a9 9 0 1 1-2.64-6.36" /><path d="M21 4v5h-5" />
          </svg>
        </button>
      )}
    </div>
  );
}

// ---------- tabs ----------
function Tabs({ tabs, active, onChange }) {
  return (
    <div className="ctabs" role="tablist">
      {tabs.map(t => (
        <button
          key={t.id} role="tab" aria-selected={active === t.id}
          className={`ctab ${active === t.id ? "is-on" : ""}`}
          onClick={() => onChange(t.id)}
        >
          {t.label}
          {t.count != null && <span className="ctab-count">{t.count}</span>}
        </button>
      ))}
    </div>
  );
}

// ---------- status pill ----------
const STATUS_LABEL = {
  "completed": "Completed", "in-progress": "In progress",
  "not-started": "Not started", "overdue": "Overdue",
};
function StatusPill({ status }) {
  return <span className="status-pill" data-st={status}><span className="dot" />{STATUS_LABEL[status] || status}</span>;
}

// ---------- family accuracy bars ----------
function FamilyBars({ byFamily }) {
  return (
    <div className="fam-bars">
      {CLASS_FAMILIES.map(fam => {
        const f = byFamily[fam] || { acc: 0, seen: 0 };
        const pct = f.seen ? Math.round(f.acc * 100) : 0;
        return (
          <div className="fam-row" key={fam}>
            <span className="fam-name">{FAMILY_META[fam].label}</span>
            <span className="fam-track"><span className="fam-fill" data-lvl={lvl(f.acc)} style={{ width: `${f.seen ? pct : 0}%` }} /></span>
            <span className="fam-pct">{f.seen ? `${pct}%` : "—"}</span>
          </div>
        );
      })}
    </div>
  );
}

// ---------- completion ring ----------
function CompletionRing({ frac, children }) {
  const r = 38, c = 2 * Math.PI * r, dash = c * Math.max(0, Math.min(1, frac));
  return (
    <div className="ring">
      <svg viewBox="0 0 92 92" width="92" height="92">
        <circle cx="46" cy="46" r={r} fill="none" stroke="var(--bg-2)" strokeWidth="9" />
        <circle cx="46" cy="46" r={r} fill="none" stroke="var(--accent)" strokeWidth="9" strokeLinecap="round" strokeDasharray={`${dash} ${c - dash}`} />
      </svg>
      <div className="ring-pct">{children}</div>
    </div>
  );
}

// ---------- mini activity chart (14 days) ----------
function MiniChart({ series }) {
  const max = Math.max(1, ...series.map(s => s.answered));
  return (
    <div className="mini-chart" role="img" aria-label="Answers over the last 14 days">
      {series.map((s, i) => {
        const h = (s.answered / max) * 100;
        const acc = s.answered ? (s.correct / s.answered) * 100 : null;
        return (
          <div className="mini-col" key={i} title={`${s.key.slice(5)} · ${s.answered} answered`}>
            <div className="mini-stack">
              {acc != null && <div className="mini-acc" style={{ bottom: `${acc}%` }} />}
              <div className="mini-bar" style={{ height: `${h}%` }} />
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ---------- @username autocomplete ----------
function AtAutocomplete({ directory, exclude, onPick, placeholder }) {
  const [q, setQ] = shUseState("");
  const [open, setOpen] = shUseState(false);
  const [active, setActive] = shUseState(0);
  const wrapRef = shUseRef(null);

  shUseEffect(() => {
    const onDoc = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, []);

  const clean = q.replace(/^@/, "").toLowerCase().trim();
  const matches = shUseMemo(() => {
    if (!clean) return [];
    return Object.values(directory)
      .filter(p => !exclude.includes(p.handle))
      .filter(p => p.handle.toLowerCase().includes(clean) || p.name.toLowerCase().includes(clean))
      .slice(0, 6);
  }, [clean, directory, exclude]);

  const exactKnown = Object.values(directory).some(p => p.handle.toLowerCase() === clean);

  const pick = (handle) => { onPick(handle); setQ(""); setOpen(false); setActive(0); };
  const onKey = (e) => {
    if (!open) return;
    if (e.key === "ArrowDown") { e.preventDefault(); setActive(a => Math.min(a + 1, matches.length - 1)); }
    else if (e.key === "ArrowUp") { e.preventDefault(); setActive(a => Math.max(a - 1, 0)); }
    else if (e.key === "Enter") {
      e.preventDefault();
      if (matches[active]) pick(matches[active].handle);
      else if (clean) pick(clean);
    } else if (e.key === "Escape") setOpen(false);
  };

  return (
    <div className="ac-wrap" ref={wrapRef}>
      <div className="ac-field">
        <span className="ac-at">@</span>
        <input
          className="ac-input" value={q}
          onChange={e => { setQ(e.target.value); setOpen(true); setActive(0); }}
          onFocus={() => setOpen(true)}
          onKeyDown={onKey}
          placeholder={placeholder || "add by username"}
          aria-label="Add student by username" aria-expanded={open} role="combobox" aria-autocomplete="list"
        />
      </div>
      {open && clean && (
        <div className="ac-menu" role="listbox">
          {matches.map((p, i) => (
            <button key={p.handle} role="option" aria-selected={i === active}
              className={`ac-item ${i === active ? "is-active" : ""}`}
              onMouseEnter={() => setActive(i)} onClick={() => pick(p.handle)}>
              <Avatar name={p.name} hue={p.avatarHue} size="sm" />
              <span className="stack-sm" style={{ gap: 1 }}>
                <span className="ac-item-name">{p.name}</span>
                <span className="ac-item-handle">@{p.handle}</span>
              </span>
              <span className="spacer" />
              <span className="ac-item-add">Add →</span>
            </button>
          ))}
          {matches.length === 0 && (
            (/^[a-z0-9_.]{2,24}$/.test(clean) && !exactKnown) ? (
              // No directory match, but a valid handle — let the teacher invite it
              // directly. The server resolves whether that @username has a synced
              // account and creates a pending invite (or returns a clear error).
              <button type="button" className="ac-item is-active" onClick={() => pick(clean)}>
                <span style={{ width: 28, height: 28, borderRadius: "50%", background: "var(--accent-2, #d7f0ec)", color: "var(--accent)", display: "grid", placeItems: "center", fontWeight: 700, flex: "0 0 auto" }}>@</span>
                <span className="stack-sm" style={{ gap: 1 }}>
                  <span className="ac-item-name">Invite @{clean}</span>
                  <span className="ac-item-handle">Sends an invite if they have a synced account</span>
                </span>
                <span className="spacer" />
                <span className="ac-item-add">Invite →</span>
              </button>
            ) : (
              <div className="ac-empty">
                <div className="ac-empty-h">{exactKnown ? "Already in this class" : "Enter a valid @username"}</div>
                <div className="ac-empty-s">
                  {exactKnown ? "This student is already a member or has a pending invite." : "2–24 characters — letters, numbers, “_” or “.” — or share your join code instead."}
                </div>
              </div>
            )
          )}
        </div>
      )}
    </div>
  );
}

// ---------- toast ----------
function useToast() {
  const [toasts, setToasts] = shUseState([]);
  const push = React.useCallback((msg) => {
    const id = Math.random().toString(36).slice(2);
    setToasts(t => [...t, { id, msg }]);
    setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), 2600);
  }, []);
  const node = (
    <div className="toast-wrap" aria-live="polite">
      {toasts.map(t => <div className="toast" key={t.id}><span className="tick">✓</span>{t.msg}</div>)}
    </div>
  );
  return [node, push];
}

// ---------- modal ----------
function Modal({ title, sub, children, onClose, foot }) {
  shUseEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose?.(); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [onClose]);
  return (
    <div className="modal-scrim" onMouseDown={e => { if (e.target === e.currentTarget) onClose?.(); }}>
      <div className="modal" role="dialog" aria-modal="true" aria-label={title}>
        {title && (
          <div className="modal-head">
            <div className="modal-title">{title}</div>
            {sub && <div className="modal-sub">{sub}</div>}
          </div>
        )}
        <div className="modal-body">{children}</div>
        {foot && <div className="modal-foot">{foot}</div>}
      </div>
    </div>
  );
}

// ---------- empty / error / skeleton ----------
function EmptyState({ icon, title, children, cta }) {
  return (
    <div className="empty">
      <div className="empty-mark">{icon || "○"}</div>
      <div className="empty-title">{title}</div>
      <p className="muted">{children}</p>
      {cta && <div className="empty-cta">{cta}</div>}
    </div>
  );
}
function ErrorState({ title, children, onRetry }) {
  return (
    <div className="empty">
      <div className="empty-mark is-error">!</div>
      <div className="empty-title">{title || "Something went wrong"}</div>
      <p className="muted">{children || "We couldn't load this just now."}</p>
      {onRetry && <div className="empty-cta"><button className="cta-secondary" onClick={onRetry}>Try again</button></div>}
    </div>
  );
}
function SkeletonRows({ n }) {
  return (
    <div className="panel">
      {Array.from({ length: n || 4 }).map((_, i) => (
        <div className="skel-row" key={i}>
          <div className="skel skel-av" />
          <div className="stack-sm" style={{ gap: 8 }}>
            <div className="skel skel-line" style={{ width: `${40 + (i % 3) * 12}%` }} />
            <div className="skel skel-line" style={{ width: "26%", height: 9 }} />
          </div>
          <div className="skel skel-line" style={{ width: 50 }} />
        </div>
      ))}
    </div>
  );
}
function SkeletonCards({ n }) {
  return (
    <div className="cls-grid">
      {Array.from({ length: n || 3 }).map((_, i) => (
        <div className="skel-card" key={i}>
          <div className="skel skel-line" style={{ width: "60%", height: 18 }} />
          <div className="skel skel-line" style={{ width: "35%" }} />
          <div className="skel skel-line" style={{ width: "80%", height: 8, marginTop: 8 }} />
        </div>
      ))}
    </div>
  );
}

Object.assign(window, {
  ClsInitials: initials, relTime, seenLabel, accLvl: lvl,
  Avatar, JoinCode, Tabs, StatusPill, FamilyBars, CompletionRing, MiniChart,
  AtAutocomplete, useToast, Modal, EmptyState, ErrorState, SkeletonRows, SkeletonCards,
});
