/* Language Partner — shared UI primitives & cards. Exported to window at the bottom. */
const { useState, useEffect, useRef } = React;

/* ---- tiny inline icon set (stroke, 1.6) ---- */
function Icon({ name, size = 22, style }) {
  const p = { fill: "none", stroke: "currentColor", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round" };
  const paths = {
    back: <path d="M15 5l-7 7 7 7" {...p} />,
    speaker: <g {...p}><path d="M4 9v6h4l5 4V5L8 9H4z" /><path d="M16 9a4 4 0 010 6" /></g>,
    refresh: <g {...p}><path d="M4 12a8 8 0 0114-5l2 2" /><path d="M20 5v4h-4" /><path d="M20 12a8 8 0 01-14 5l-2-2" /><path d="M4 19v-4h4" /></g>,
    check: <path d="M5 12.5l4.5 4.5L19 7" {...p} />,
    chevron: <path d="M6 9l6 6 6-6" {...p} />,
    plus: <g {...p}><path d="M12 5v14" /><path d="M5 12h14" /></g>,
    spark: <g {...p}><path d="M12 4v4M12 16v4M4 12h4M16 12h4" /><circle cx="12" cy="12" r="3" /></g>,
    book: <g {...p}><path d="M5 5a2 2 0 012-2h11v15H7a2 2 0 00-2 2z" /><path d="M18 18H7a2 2 0 00-2 2" /></g>,
    eye: <g {...p}><path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6z" /><circle cx="12" cy="12" r="2.5" /></g>,
    feather: <g {...p}><path d="M20 4C12 4 6 10 6 18l-2 2" /><path d="M6 14h8" /></g>,
    swap: <g {...p}><path d="M4 8h13" /><path d="M13 4l4 4-4 4" /><path d="M20 16H7" /><path d="M11 20l-4-4 4-4" /></g>,
    cog: <g {...p}><circle cx="12" cy="12" r="3" /><path d="M12 2v3M12 19v3M2 12h3M19 12h3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M19.1 4.9L17 7M7 17l-2.1 2.1" /></g>,
    send: <g {...p}><path d="M5 12h14M13 6l6 6-6 6" /></g>,
  };
  return (
    <svg viewBox="0 0 24 24" width={size} height={size} style={{ display: "block", ...style }} aria-hidden="true">
      {paths[name]}
    </svg>
  );
}

/* speak via Web Speech API when a voice exists; silent no-op otherwise */
function speak(text, lang) {
  try {
    if (!window.speechSynthesis) return;
    const u = new SpeechSynthesisUtterance(text);
    if (lang) u.lang = lang;
    u.rate = 0.85;
    window.speechSynthesis.cancel();
    window.speechSynthesis.speak(u);
  } catch (e) {}
}
const TTS_LANG = { zh: "zh-TW", fr: "fr-FR", es: "es-ES", hi: "hi-IN", lkt: "", ko: "ko-KR" };

function SpeakButton({ text, langId, accent }) {
  if (!text || !TTS_LANG[langId]) return null;
  return (
    <button className="iconbtn" title="Listen" onClick={() => speak(text, TTS_LANG[langId])}
      style={{ color: accent }}>
      <Icon name="speaker" size={20} />
    </button>
  );
}

/* ---------- refresh engine: a tiny bus + rotation helpers ---------- */
/* Only one section is mounted at a time inside a language page, so any mounted
   section that subscribes IS the active one — Helen's "re-teach" just emits. */
const LPBus = (function () {
  const subs = new Set();
  return { on(fn) { subs.add(fn); return () => subs.delete(fn); }, emit() { subs.forEach(fn => fn()); } };
})();

function useRound() {
  const [round, setRound] = useState(0);
  const bump = React.useCallback(() => setRound(r => r + 1), []);
  useEffect(() => LPBus.on(bump), [bump]);
  return [round, bump];
}

function seededShuffle(arr, seed) {
  const a = arr.slice();
  let s = (seed + 1) * 9301 + 49297;
  for (let i = a.length - 1; i > 0; i--) {
    s = (s * 9301 + 49297) % 233280;
    const j = Math.floor((s / 233280) * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

/* page through a pool: rotate when there's more than a screenful, reshuffle when not */
function rotate(arr, round, size) {
  if (!arr || !arr.length) return [];
  if (arr.length <= size) return round === 0 ? arr : seededShuffle(arr, round);
  const start = (round * size) % arr.length;
  const out = [];
  for (let i = 0; i < size; i++) out.push(arr[(start + i) % arr.length]);
  return out;
}

/* the per-section "New set" control */
function RefreshTab({ accent, onClick, label = "New set" }) {
  return (
    <button className="sec-refresh" style={{ borderColor: accent, color: accent }} onClick={onClick}>
      <Icon name="refresh" size={16} /> {label}
    </button>
  );
}

/* section heading with a small overline label */function SectionHead({ label, title, accent, sub }) {
  return (
    <div className="sec-head">
      <div className="overline" style={{ color: accent }}>{label}</div>
      <h2 className="sec-title">{title}</h2>
      {sub ? <p className="sec-sub">{sub}</p> : null}
    </div>
  );
}

/* a learning "word" card — big glyph, romanization, gloss, example */
function VocabCard({ item, langId, accent, kind }) {
  const big = kind === "letters" || langId === "zh";
  return (
    <div className="vcard">
      <div className="vcard-top">
        <div className="vcard-glyph" data-lang={langId} style={{ fontSize: big ? undefined : "2.1rem" }}>
          {item.glyph}
        </div>
        <SpeakButton text={item.ex || item.glyph} langId={langId} accent={accent} />
      </div>
      {item.roman ? <div className="vcard-roman" style={{ color: accent }}>{item.roman}</div> : null}
      <div className="vcard-gloss">{item.gloss}</div>
      {item.ex ? (
        <div className="vcard-ex">
          <span className="ex-native" data-lang={langId}>{item.ex}</span>
          {item.exRoman ? <span className="ex-roman">{item.exRoman}</span> : null}
          {item.exGloss ? <span className="ex-gloss">{item.exGloss}</span> : null}
        </div>
      ) : null}
    </div>
  );
}

/* flip card used in Refresh — front shows glyph, tap to reveal */
function FlipCard({ item, langId, accent, fresh }) {
  const [flipped, setFlipped] = useState(false);
  const len = (item.glyph || "").length;
  const gsize = len <= 4 ? 44 : len <= 7 ? 34 : len <= 11 ? 26 : 21;
  return (
    <button className={"flip" + (flipped ? " is-flipped" : "") + (fresh ? " flip-fresh" : "")} onClick={() => setFlipped(f => !f)} style={fresh ? { "--a": accent } : null}>
      {fresh ? <span className="flip-badge" style={{ background: accent }}>just added</span> : null}
      <div className="flip-inner">
        <div className="flip-face flip-front">
          <span className="flip-glyph" data-lang={langId} style={{ fontSize: gsize }}>{item.glyph}</span>
          <span className="flip-hint">tap to reveal</span>
        </div>
        <div className="flip-face flip-back" style={{ borderColor: accent }}>
          {item.roman ? <span className="flip-roman" style={{ color: accent }}>{item.roman}</span> : null}
          <span className="flip-gloss">{item.gloss}</span>
        </div>
      </div>
    </button>
  );
}

/* grammar accordion — newest open by default, expand to see the rest */
function GrammarStack({ items, langId, accent }) {
  const [open, setOpen] = useState(items.length ? items[0].id : null);
  return (
    <div className="gstack">
      {items.map((g, i) => {
        const isOpen = open === g.id;
        return (
          <div className={"gitem" + (isOpen ? " open" : "")} key={g.id}>
            <button className="grow" onClick={() => setOpen(isOpen ? null : g.id)}>
              <div className="grow-left">
                {g.isNew ? <span className="tag-new" style={{ color: accent, borderColor: accent }}>newest</span> : null}
                <span className="grow-title">{g.title}</span>
              </div>
              <span className="grow-chev" style={{ transform: isOpen ? "rotate(180deg)" : "none" }}>
                <Icon name="chevron" size={20} />
              </span>
            </button>
            {isOpen ? (
              <div className="gbody">
                <p className="gbody-text">{g.body}</p>
                {g.ex ? (
                  <div className="gbody-ex" style={{ borderColor: accent }}>
                    <span className="ex-native" data-lang={langId}>{g.ex}</span>
                    {g.exRoman ? <span className="ex-roman">{g.exRoman}</span> : null}
                    {g.exGloss ? <span className="ex-gloss">{g.exGloss}</span> : null}
                  </div>
                ) : null}
              </div>
            ) : null}
          </div>
        );
      })}
    </div>
  );
}

/* word-chip builder for Translation practice */
function ChipBuilder({ item, langId, accent }) {
  const [picked, setPicked] = useState([]);
  const [done, setDone] = useState(false);
  const correct = done && picked.map(p => p.split("#")[0]).join("\u0001") === item.order.join("\u0001");
  function pick(w, idx) {
    if (done) return;
    setPicked(p => [...p, w + "#" + idx]);
  }
  function remove(token) {
    if (done) return;
    setPicked(p => p.filter(t => t !== token));
  }
  function reset() { setPicked([]); setDone(false); }
  return (
    <div className="tcard">
      <div className="tprompt">{item.prompt}</div>
      <div className="tslots" style={{ borderColor: accent }}>
        {picked.length === 0 ? <span className="tslot-empty">tap the words in order — tap one again to remove it</span>
          : <div className="tbuilt-row">
              {picked.map((token) => (
                <button key={token} className="tbuilt-chip" data-lang={langId}
                  style={{ borderColor: accent }} disabled={done}
                  onClick={() => remove(token)} title="Remove">
                  {token.split("#")[0]}<span className="tbuilt-x">×</span>
                </button>
              ))}
            </div>}
      </div>
      <div className="tchips">
        {item.chips.map((w, i) => {
          const used = picked.includes(w + "#" + i);
          return (
            <button key={i} className={"tchip" + (used ? " used" : "")} data-lang={langId}
              disabled={used || done} onClick={() => pick(w, i)}>{w}</button>
          );
        })}
      </div>
      <div className="trow">
        {!done ? (
          <button className="btn-ghost" onClick={() => setDone(true)} disabled={picked.length === 0}>Check</button>
        ) : (
          <button className="btn-ghost" onClick={reset}>Try again</button>
        )}
        {done ? (
          <div className={"tverdict " + (correct ? "ok" : "no")}>
            {correct ? <Icon name="check" size={18} /> : null}
            <span>{correct ? "Correct" : "Answer:"}</span>
            <span className="tanswer" data-lang={langId}>{item.answer}</span>
            <span className="tanswer-r">{item.roman || ""}</span>
          </div>
        ) : null}
      </div>
    </div>
  );
}

/* glossed reading — hover/tap a word to gloss it AND light up its match in the translation */
function unitOf(langId, t) {
  if (langId === "hi") return "letter";
  if (langId === "zh") return [...t].length > 1 ? "word" : "character";
  return "word";
}
function GlossReader({ read, langId, accent }) {
  const [active, setActive] = useState(null);   // tapped (sticky)
  const [hover, setHover] = useState(null);      // pointer (transient)
  const [showAll, setShowAll] = useState(false);
  const lit = hover !== null ? hover : active;   // currently emphasized source index
  const hasMap = Array.isArray(read.trans);

  function toggle(i) { setActive(a => (a === i ? null : i)); }

  return (
    <div className="reader">
      <div className={"reader-passage" + (langId === "zh" ? "" : " spaced")} data-lang={langId}>
        {read.words.map((w, i) => {
          const isPunct = !w.t.trim() || w.g === "";
          const space = langId !== "zh" && i > 0 && !isPunct ? " " : "";
          if (isPunct) return <React.Fragment key={i}>{space}<span className="rpunct" data-lang={langId}>{w.t}</span></React.Fragment>;
          const on = lit === i;
          const showTip = on;
          return (
            <span key={i} className="rword-wrap">{space}
              <button className={"rword" + (on ? " on" : "")} data-lang={langId}
                style={on ? { color: accent, borderColor: accent, background: "color-mix(in oklab, " + accent + " 16%, transparent)" } : null}
                onClick={() => toggle(i)}
                onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(null)}>{w.t}</button>
              {showTip ? (
                <span className="rtip" style={{ borderColor: accent }}>
                  {w.r ? <b style={{ color: accent }}>{w.r}</b> : null}
                  {w.g && w.g !== w.r ? <span className="rtip-gloss"> {w.g}</span> : null}
                  <span className="rtip-unit">{unitOf(langId, w.t)}</span>
                </span>
              ) : null}
            </span>
          );
        })}
      </div>

      {read.phon ? (
        <div className="reader-phon">
          <span className="reader-phon-tag">say it</span>
          <span className="reader-phon-line">{read.phon}</span>
        </div>
      ) : null}

      <button className="reveal-line" onClick={() => setShowAll(s => !s)}>
        <Icon name="eye" size={18} /> {showAll ? "Hide translation" : "Show full translation"}
      </button>

      {showAll ? (
        hasMap ? (
          <div className="reader-trans">
            <div className="reader-trans-hint">Hover a word — its match lights up on both sides.</div>
            <p className="reader-trans-line">
              {read.trans.map((tk, k) => {
                const linked = tk.ref !== null && tk.ref !== undefined;
                const on = linked && lit === tk.ref;
                return (
                  <React.Fragment key={k}>
                    {k > 0 ? " " : ""}
                    <span className={"tword" + (linked ? " linked" : "") + (on ? " on" : "")}
                      style={on ? { color: accent, background: "color-mix(in oklab, " + accent + " 16%, transparent)" } : null}
                      onClick={() => linked && toggle(tk.ref)}
                      onMouseEnter={() => linked && setHover(tk.ref)} onMouseLeave={() => linked && setHover(null)}>
                      {tk.t}
                    </span>
                  </React.Fragment>
                );
              })}
            </p>
          </div>
        ) : <p className="reader-full">{read.full}</p>
      ) : null}
    </div>
  );
}

Object.assign(window, {
  Icon, speak, TTS_LANG, SpeakButton, SectionHead,
  VocabCard, FlipCard, GrammarStack, ChipBuilder, GlossReader,
  LPBus, useRound, rotate, seededShuffle, RefreshTab,
});
