/* ============================================================
   ui.jsx — shared presentational components
   Exports to window: Icon, Logo, AudioPlayer, Timer, Modal,
   Segmented, Field, OptionLetter, useToast/Toaster
   ============================================================ */

/* ---------------- Icons ---------------- */
const ICONS = {
  headphones: "M3 14v-2a9 9 0 0 1 18 0v2M3 14a2 2 0 0 1 2-2h1a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H5a2 2 0 0 1-2-2v-3Zm18 0a2 2 0 0 0-2-2h-1a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h1a2 2 0 0 0 2-2v-3Z",
  book: "M4 5a2 2 0 0 1 2-2h13v16H6a2 2 0 0 0-2 2V5Zm0 0v14M19 3v16",
  clock: "M12 7v5l3 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z",
  check: "M20 6 9 17l-5-5",
  checkCircle: "M9 12l2 2 4-4M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z",
  x: "M18 6 6 18M6 6l12 12",
  xCircle: "M15 9l-6 6M9 9l6 6M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z",
  plus: "M12 5v14M5 12h14",
  trash: "M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2m2 0v14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V6h12Z",
  edit: "M12 20h9M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5Z",
  copy: "M9 9h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1V10a1 1 0 0 1 1-1ZM5 15H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1",
  play: "M6 4l14 8-14 8V4Z",
  pause: "M7 4h3v16H7zM14 4h3v16h-3z",
  replay: "M3 12a9 9 0 1 0 3-6.7M3 4v4h4",
  lock: "M6 10V8a6 6 0 1 1 12 0v2M5 10h14a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1Z",
  settings: "M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm7.4-3a7.4 7.4 0 0 0-.1-1.3l2-1.6-2-3.4-2.4 1a7.3 7.3 0 0 0-2.2-1.3L14 2h-4l-.4 2.6A7.3 7.3 0 0 0 7.3 5.9l-2.4-1-2 3.4 2 1.6a7.4 7.4 0 0 0 0 2.6l-2 1.6 2 3.4 2.4-1a7.3 7.3 0 0 0 2.2 1.3L10 22h4l.4-2.6a7.3 7.3 0 0 0 2.2-1.3l2.4 1 2-3.4-2-1.6c.06-.43.1-.86.1-1.3Z",
  arrowRight: "M5 12h14M13 6l6 6-6 6",
  arrowLeft: "M19 12H5M11 18l-6-6 6-6",
  chevDown: "M6 9l6 6 6-6",
  chevRight: "M9 6l6 6-6 6",
  image: "M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5Zm0 12 5-5 4 4 5-5 4 4M9 9a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z",
  upload: "M12 16V4m0 0L7 9m5-5 5 5M4 16v3a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-3",
  download: "M12 4v12m0 0 5-5m-5 5-5-5M4 16v3a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-3",
  flag: "M5 21V4m0 0 6 2 5-2 3 1v9l-3-1-5 2-6-2",
  list: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01",
  grid: "M4 4h7v7H4zM13 4h7v7h-7zM4 13h7v7H4zM13 13h7v7h-7z",
  user: "M20 21a8 8 0 1 0-16 0M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z",
  doc: "M7 3h7l5 5v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Zm7 0v5h5",
  award: "M12 15a6 6 0 1 0 0-12 6 6 0 0 0 0 12Zm-3.5 4.5L7 22l5-2 5 2-1.5-2.5M9 13l-1 6m8-6 1 6",
  volume: "M11 5 6 9H2v6h4l5 4V5Zm4.5 3a5 5 0 0 1 0 8m2.5-11a8 8 0 0 1 0 14",
  reset: "M3 12a9 9 0 1 1 9 9 9 9 0 0 1-7.5-4M3 21v-5h5",
  search: "M21 21l-4.3-4.3M17 11a6 6 0 1 1-12 0 6 6 0 0 1 12 0Z",
  alert: "M12 9v4m0 4h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z",
  logout: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9",
  mic: "M12 2a3 3 0 0 1 3 3v7a3 3 0 0 1-6 0V5a3 3 0 0 1 3-3ZM19 10v2a7 7 0 0 1-14 0v-2M12 19v3M8 22h8",
  micOff: "M2 2l20 20M18.9 18.9A7 7 0 0 1 5 12v-2m9-6.1A3 3 0 0 1 15 5v4.17M12 19v3M8 22h8",
  users: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8Zm8 3a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm4 8v-2a3 3 0 0 0-2-2.83",
  filter: "M22 3H2l8 9.46V19l4 2v-8.54L22 3Z",
  calendarDays: "M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Zm3 12h.01M12 16h.01M16 16h.01M8 12h.01M12 12h.01M16 12h.01",
};

function Icon({ name, size = 18, stroke = 2, fill, style, className }) {
  const d = ICONS[name];
  const filled = name === "play" || name === "pause";
  return (
    <svg width={size} height={size} viewBox="0 0 24 24"
      fill={filled ? (fill || "currentColor") : "none"}
      stroke={filled ? "none" : "currentColor"}
      strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"
      className={className} style={style} aria-hidden="true">
      <path d={d} />
    </svg>
  );
}

/* ---------------- Logo ---------------- */
function Logo({ size = 30, withText = true, color = "var(--primary)" }) {
  return (
    <div className="row gap-3" style={{ alignItems: "center" }}>
      <div style={{
        width: size, height: size, borderRadius: 8,
        background: color, color: "#fff",
        display: "flex", alignItems: "center", justifyContent: "center",
        fontFamily: "var(--font-serif)", fontWeight: 700, fontSize: size * 0.52,
        boxShadow: "var(--sh-sm)", flexShrink: 0,
      }}>E</div>
      {withText && (
        <div className="col" style={{ lineHeight: 1.1 }}>
          <span style={{ fontFamily: "var(--font-serif)", fontWeight: 600, fontSize: 16.5, color: "var(--ink)" }}>Proficiency</span>
          <span style={{ fontSize: 11, letterSpacing: "0.16em", textTransform: "uppercase", color: "var(--ink-faint)", fontWeight: 600 }}>English Test</span>
        </div>
      )}
    </div>
  );
}

/* ---------------- Audio Player ----------------
   Plays a real audioUrl if present; otherwise reads the transcript
   aloud via the Web Speech API so the listening section is functional
   in the prototype. Estimates progress for TTS by word count.        */
function AudioPlayer({ item, autoKey }) {
  const { useState, useRef, useEffect } = React;
  const [playing, setPlaying] = useState(false);
  const [progress, setProgress] = useState(0); // 0..1
  const [elapsed, setElapsed] = useState(0);
  const audioRef = useRef(null);
  const rafRef = useRef(null);
  const ttsRef = useRef({ start: 0, est: 1, timer: null });

  const hasFile = !!item.audioUrl;
  const script = (item.transcript || "").trim();
  const words = script ? script.split(/\s+/).length : 0;
  const estDur = Math.max(3, words / 2.6); // seconds, TTS estimate

  function stopAll() {
    if (window.speechSynthesis) window.speechSynthesis.cancel();
    if (ttsRef.current.timer) cancelAnimationFrame(ttsRef.current.timer);
    if (audioRef.current) audioRef.current.pause();
  }

  useEffect(() => () => stopAll(), []);
  // reset when the stimulus changes
  useEffect(() => { stopAll(); setPlaying(false); setProgress(0); setElapsed(0); }, [autoKey]);

  function tickTTS() {
    const t = (Date.now() - ttsRef.current.start) / 1000;
    const p = Math.min(1, t / ttsRef.current.est);
    setProgress(p); setElapsed(t);
    if (p < 1 && window.speechSynthesis && window.speechSynthesis.speaking) {
      ttsRef.current.timer = requestAnimationFrame(tickTTS);
    }
  }

  function play() {
    if (hasFile) {
      audioRef.current && audioRef.current.play();
      setPlaying(true);
      return;
    }
    if (!script || !window.speechSynthesis) return;
    window.speechSynthesis.cancel();
    const u = new SpeechSynthesisUtterance(script.replace(/\([A-D]\)/g, ". "));
    u.rate = 0.96; u.pitch = 1;
    u.onend = () => { setPlaying(false); setProgress(1); };
    ttsRef.current = { start: Date.now(), est: estDur, timer: null };
    window.speechSynthesis.speak(u);
    setPlaying(true);
    requestAnimationFrame(tickTTS);
  }

  function pause() {
    stopAll();
    setPlaying(false);
  }

  function replay() { stopAll(); setProgress(0); setElapsed(0); setTimeout(play, 60); }

  // real file progress
  useEffect(() => {
    if (!hasFile) return;
    const a = audioRef.current; if (!a) return;
    const onTime = () => { setProgress(a.currentTime / (a.duration || 1)); setElapsed(a.currentTime); };
    const onEnd = () => setPlaying(false);
    a.addEventListener("timeupdate", onTime); a.addEventListener("ended", onEnd);
    return () => { a.removeEventListener("timeupdate", onTime); a.removeEventListener("ended", onEnd); };
  }, [hasFile]);

  const total = hasFile ? (audioRef.current?.duration || 0) : estDur;
  const fmt = (s) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, "0")}`;

  return (
    <div className="panel" style={{ padding: 14, background: "var(--primary-tint)", borderColor: "#D9E4EF" }}>
      <div className="row gap-3" style={{ alignItems: "center" }}>
        <button className="btn btn-primary" onClick={playing ? pause : play}
          style={{ width: 46, height: 46, borderRadius: "50%", padding: 0, flexShrink: 0 }}
          aria-label={playing ? "Pause" : "Play"}>
          <Icon name={playing ? "pause" : "play"} size={20} />
        </button>
        <div className="grow col gap-2" style={{ minWidth: 0 }}>
          <div className="row spread" style={{ alignItems: "center" }}>
            <span className="row gap-2" style={{ color: "var(--primary-deep)", fontWeight: 600, fontSize: 13 }}>
              <Icon name="volume" size={15} /> Audio prompt
            </span>
            <span className="mono" style={{ fontSize: 12, color: "var(--ink-soft)" }}>{fmt(elapsed)} / {fmt(total)}</span>
          </div>
          <div style={{ height: 6, background: "#D4E0EC", borderRadius: 999, overflow: "hidden" }}>
            <div style={{ height: "100%", width: `${progress * 100}%`, background: "var(--primary)", borderRadius: 999, transition: "width .15s linear" }} />
          </div>
        </div>
        <button className="btn btn-ghost btn-sm" onClick={replay} style={{ flexShrink: 0 }} title="Replay">
          <Icon name="replay" size={15} /> Replay
        </button>
      </div>
      {hasFile && <audio ref={audioRef} src={item.audioUrl} preload="metadata" />}
      {!hasFile && !script && (
        <div className="hint" style={{ marginTop: 8 }}>No audio attached yet — add a transcript in the admin to enable spoken playback.</div>
      )}
    </div>
  );
}

/* ---------------- Timer ----------------
   Counts down from `minutes`. Calls onExpire once. */
function Timer({ minutes, runningKey, onExpire, paused }) {
  const { useState, useEffect, useRef } = React;
  const [left, setLeft] = useState(minutes * 60);
  const firedRef = useRef(false);
  useEffect(() => { setLeft(minutes * 60); firedRef.current = false; }, [runningKey, minutes]);
  useEffect(() => {
    if (paused) return;
    const id = setInterval(() => {
      setLeft((s) => {
        if (s <= 1) {
          clearInterval(id);
          if (!firedRef.current) { firedRef.current = true; onExpire && onExpire(); }
          return 0;
        }
        return s - 1;
      });
    }, 1000);
    return () => clearInterval(id);
  }, [paused, runningKey]);

  const m = Math.floor(left / 60), s = left % 60;
  const low = left <= 60;
  const warn = left <= 300 && !low;
  return (
    <div className="row gap-2" style={{
      alignItems: "center", padding: "7px 13px", borderRadius: 999,
      background: low ? "var(--incorrect-soft)" : warn ? "var(--warn-soft)" : "var(--surface-3)",
      color: low ? "var(--incorrect)" : warn ? "var(--warn)" : "var(--ink-soft)",
      fontFamily: "var(--font-mono)", fontWeight: 600, fontSize: 14.5,
      border: `1px solid ${low ? "var(--incorrect-line)" : "transparent"}`,
    }}>
      <Icon name="clock" size={15} stroke={2.2} />
      {String(m).padStart(2, "0")}:{String(s).padStart(2, "0")}
    </div>
  );
}

/* ---------------- Modal ---------------- */
function Modal({ open, onClose, children, width = 460, title }) {
  const { useEffect } = React;
  useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") onClose && onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open]);
  if (!open) return null;
  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, zIndex: 100,
      background: "rgba(20,32,46,0.42)", backdropFilter: "blur(2px)",
      display: "flex", alignItems: "center", justifyContent: "center", padding: 24,
      animation: "viewIn .18s ease both",
    }}>
      <div onClick={(e) => e.stopPropagation()} className="card view-in" style={{
        width, maxWidth: "100%", maxHeight: "88vh", overflow: "auto",
        boxShadow: "var(--sh-pop)", borderRadius: "var(--r-lg)",
      }}>
        {title && (
          <div className="row spread" style={{ padding: "16px 20px", borderBottom: "1px solid var(--line)", alignItems: "center" }}>
            <h3 style={{ fontSize: 17 }}>{title}</h3>
            <button className="btn btn-quiet btn-sm" onClick={onClose} style={{ padding: 6 }}><Icon name="x" size={18} /></button>
          </div>
        )}
        <div style={{ padding: 20 }}>{children}</div>
      </div>
    </div>
  );
}

/* ---------------- Segmented control ---------------- */
function Segmented({ value, onChange, options }) {
  return (
    <div className="row" style={{ background: "var(--surface-3)", borderRadius: 9, padding: 3, gap: 2 }}>
      {options.map((o) => {
        const active = o.value === value;
        return (
          <button key={o.value} onClick={() => onChange(o.value)} className="btn" style={{
            background: active ? "var(--surface)" : "transparent",
            color: active ? "var(--ink)" : "var(--ink-soft)",
            boxShadow: active ? "var(--sh-sm)" : "none",
            border: active ? "1px solid var(--line)" : "1px solid transparent",
            padding: "7px 14px", fontSize: 13.5, fontWeight: active ? 600 : 500,
          }}>{o.icon && <Icon name={o.icon} size={15} />}{o.label}</button>
        );
      })}
    </div>
  );
}

/* ---------------- Field ---------------- */
function Field({ label, hint, children, required }) {
  return (
    <div className="field">
      {label && <label className="label">{label}{required && <span style={{ color: "var(--incorrect)" }}> *</span>}</label>}
      {children}
      {hint && <span className="hint">{hint}</span>}
    </div>
  );
}

/* ---------------- Option letter badge ---------------- */
function OptionLetter({ index, state }) {
  // state: 'idle' | 'selected' | 'correct' | 'incorrect'
  const L = ["A", "B", "C", "D", "E"][index];
  const map = {
    idle: { bg: "var(--surface)", bd: "var(--line-strong)", fg: "var(--ink-soft)" },
    selected: { bg: "var(--primary)", bd: "var(--primary)", fg: "#fff" },
    correct: { bg: "var(--correct)", bd: "var(--correct)", fg: "#fff" },
    incorrect: { bg: "var(--incorrect)", bd: "var(--incorrect)", fg: "#fff" },
  };
  const c = map[state || "idle"];
  return (
    <span style={{
      width: 28, height: 28, borderRadius: "50%", flexShrink: 0,
      display: "inline-flex", alignItems: "center", justifyContent: "center",
      background: c.bg, border: `1.5px solid ${c.bd}`, color: c.fg,
      fontWeight: 600, fontSize: 13.5, fontFamily: "var(--font-ui)",
      transition: "all .14s ease",
    }}>{L}</span>
  );
}

Object.assign(window, { Icon, Logo, AudioPlayer, Timer, Modal, Segmented, Field, OptionLetter });
