/* Card Hand prototype — three rounds; deal cards from a hand into a growing shared timeline. */
const { useState, useRef, useEffect, useCallback } = React;
const K = window.QuizKit;
const LG = window.QuizLogic;

async function loadTodaysPuzzle() {
  const date = LG.todayKey();
  try {
    const res = await fetch("puzzles/" + date + ".json", { cache: "no-store" });
    if (!res.ok) throw new Error("no puzzle for " + date);
    const data = await res.json();
    const v = LG.validatePuzzle(data);
    if (!v.ok) throw new Error("invalid puzzle: " + v.errors.join(", "));
    return { puzzle: data, fallback: false };
  } catch (e) {
    // Offline / not-ready fallback: synthesize a puzzle from the hardcoded deck.
    const evs = K.pickEvents(10).map(function (ev) {
      return Object.assign({}, ev, { blurb: "" });
    });
    return {
      puzzle: { date: date, theme: "Mixed eras (offline)", subtitle: "Sample puzzle", difficulty: 2, events: evs },
      fallback: true
    };
  }
}

// localStorage helpers for daily play-once lock, streak, and stats.
const STATS_KEY = "hq:stats";
function readStats() {
  try { return JSON.parse(localStorage.getItem(STATS_KEY) || "null"); } catch (e) { return null; }
}
function writeStats(s) { try { localStorage.setItem(STATS_KEY, JSON.stringify(s)); } catch (e) {} }
function readPlayed(date) {
  try { return JSON.parse(localStorage.getItem("hq:played:" + date) || "null"); } catch (e) { return null; }
}
function writePlayed(date, result) { try { localStorage.setItem("hq:played:" + date, JSON.stringify(result)); } catch (e) {} }

const CH_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "paper",
  "juice": true
}/*EDITMODE-END*/;

const ROUNDS = [4, 3, 3];

function byYear(a, b) { return a.year - b.year; }

function Mini({ ev }) {
  return (
    <div className="mini">
      <div className="ic">{ev.icon}</div>
      <div className="ttl">{ev.title}</div>
    </div>
  );
}

function App() {
  const [t, setTweak] = useTweaks(CH_DEFAULTS);
  const juiceRef = useRef(t.juice); juiceRef.current = t.juice;

  const [bag, setBag] = useState([]);            // 10 events for the run
  const [round, setRound] = useState(1);
  const [roundPhase, setRoundPhase] = useState("place"); // place | reveal
  const [lineup, setLineup] = useState([]);       // [{key,ev,locked,status}]
  const [hand, setHand] = useState([]);           // [ev]
  const [score, setScore] = useState(0);
  const [perfectStreak, setPerfectStreak] = useState(0);
  const [roundResult, setRoundResult] = useState(null);
  const [roundScores, setRoundScores] = useState([]);
  const [phase, setPhase] = useState("play");
  const [drag, setDrag] = useState(null);         // {ev}
  const [armed, setArmed] = useState(null);
  const [ghost, setGhost] = useState({ x: 0, y: 0 });
  const [draggingId, setDraggingId] = useState(null);
  const [puzzle, setPuzzle] = useState(null);
  const [loadError, setLoadError] = useState(false);
  const [copied, setCopied] = useState(false);

  const screenRef = useRef(null);
  const cardRefs = useRef([]);

  useEffect(() => { K.applyTheme(t.theme); }, [t.theme]);
  useEffect(() => {
    const stage = document.getElementById("stage"), phone = document.getElementById("phone");
    K.fitStage(stage, phone, { max: 1.15 });
  }, []);

  const hands = useRef([]);
  const newGame = useCallback((events, seed) => {
    const src = events || (puzzle ? puzzle.events : []);
    // Shuffle so the deal is never pre-sorted. Daily puzzles use a date-seeded
    // shuffle (identical deal for everyone that day); practice uses a random one.
    const evs = seed ? LG.seededShuffle(src, seed) : K.shuffle(src);
    hands.current = [evs.slice(0, 4), evs.slice(4, 7), evs.slice(7, 10)];
    setBag(evs);
    setRound(1); setRoundPhase("place"); setLineup([]); setHand(hands.current[0]);
    setScore(0); setPerfectStreak(0); setRoundResult(null); setRoundScores([]);
    setPhase("play"); setDrag(null); setArmed(null); setDraggingId(null);
  }, [puzzle]);

  useEffect(() => {
    let alive = true;
    loadTodaysPuzzle().then(function (r) {
      if (!alive) return;
      setLoadError(r.fallback);
      setPuzzle(r.puzzle);
      const prior = readPlayed(r.puzzle.date);
      if (prior) {
        // Already played today: jump straight to the stored result, no replay.
        setBag(r.puzzle.events);
        setScore(prior.score);
        setRoundScores(prior.rounds.map((rd, i) => ({ round: i + 1, pct: rd.pct, gained: rd.gained, marks: rd.marks })));
        setPhase("done");
      } else {
        newGame(r.puzzle.events, r.puzzle.date);
      }
    });
    return function () { alive = false; };
  }, []); // eslint-disable-line

  const toLocal = (cx, cy) => {
    const r = screenRef.current.getBoundingClientRect(); const s = r.width / 390;
    return { x: (cx - r.left) / s, y: (cy - r.top) / s };
  };
  const insertionIndex = (clientY) => {
    let idx = lineup.length;
    for (let i = 0; i < lineup.length; i++) {
      const el = cardRefs.current[i]; if (!el) continue;
      const r = el.getBoundingClientRect();
      if (clientY < r.top + r.height / 2) { idx = i; break; }
    }
    return idx;
  };

  // drag a card from the hand into the lineup
  const onHandDown = (ev, e) => {
    if (roundPhase !== "place") return;
    e.preventDefault();
    setDrag({ ev }); setDraggingId(ev.id);
    setGhost(toLocal(e.clientX, e.clientY));
    setArmed(insertionIndex(e.clientY));
    const move = (m) => { setGhost(toLocal(m.clientX, m.clientY)); setArmed(insertionIndex(m.clientY)); };
    const up = (u) => {
      window.removeEventListener("pointermove", move);
      window.removeEventListener("pointerup", up);
      const idx = insertionIndex(u.clientY);
      // only accept drop if pointer is within the lineup region
      const sr = screenRef.current.getBoundingClientRect();
      const inLine = u.clientY < sr.bottom - sr.height * 0.30;
      setDrag(null); setDraggingId(null); setArmed(null);
      if (inLine) {
        setLineup(L => { const n = L.slice(); n.splice(idx, 0, { key: ev.id, ev, locked: false, status: null }); return n; });
        setHand(h => h.filter(x => x.id !== ev.id));
      }
    };
    window.addEventListener("pointermove", move);
    window.addEventListener("pointerup", up);
  };

  // tap a freshly placed card to send it back to the hand
  const returnToHand = (key) => {
    if (roundPhase !== "place") return;
    setLineup(L => { const e = L.find(x => x.key === key); if (e && !e.locked) setHand(h => [...h, e.ev]); return L.filter(x => x.key !== key); });
  };

  const lockRound = () => {
    // grade each fresh card by its immediate neighbors
    const graded = lineup.map((entry, i) => {
      if (entry.locked) return entry;
      const left = i > 0 ? lineup[i - 1].ev.year : -Infinity;
      const right = i < lineup.length - 1 ? lineup[i + 1].ev.year : Infinity;
      const ok = entry.ev.year >= left && entry.ev.year <= right;
      return { ...entry, status: ok ? "correct" : "wrong" };
    });
    const fresh = graded.filter(e => !e.locked);
    const correct = fresh.filter(e => e.status === "correct").length;
    const total = fresh.length;
    const perfect = correct === total;
    const nStreak = perfect ? perfectStreak + 1 : 0;
    const mult = perfect && nStreak >= 2 ? 2 : 1;
    const base = correct * 10 + (perfect ? 15 : 0);
    const gained = base * mult;
    setLineup(graded);
    setRoundPhase("reveal");
    setTimeout(() => {
      setScore(s => s + gained);
      setPerfectStreak(nStreak);
      setRoundResult({ correct, total, perfect, gained, mult });
      const marks = fresh.map(e => e.status);
      setRoundScores(rs => [...rs, { round, pct: Math.round((correct / total) * 100), gained, marks }]);
      if (juiceRef.current && perfect) K.burstFrom(screenRef.current, { count: 80 });
    }, 650);
  };

  const nextRound = () => {
    const sorted = lineup.map(e => e.ev).slice().sort(byYear).map(ev => ({ key: ev.id, ev, locked: true, status: null }));
    if (round >= ROUNDS.length) {
      setLineup(sorted); setPhase("done");
      const finalScore = score; // already includes this round's gained
      const rounds = roundScores.map(r => ({ pct: r.pct, gained: r.gained, marks: r.marks || [] }));
      if (!readPlayed(puzzle.date)) {
        writePlayed(puzzle.date, { score: finalScore, rounds: rounds });
        const next = LG.applyResult(readStats(), { date: puzzle.date, score: finalScore });
        writeStats(next);
      }
      if (juiceRef.current) setTimeout(() => K.burstFrom(screenRef.current, { count: 130 }), 200);
      return;
    }
    setLineup(sorted);
    const nr = round + 1;
    setRound(nr); setHand(hands.current[nr - 1]); setRoundPhase("place"); setRoundResult(null);
  };

  const handReady = hand.length === 0 && roundPhase === "place";

  // build lineup rows
  cardRefs.current = [];
  const rows = [];
  rows.push(<div key="g0" className={"gap" + (drag && armed === 0 ? " armed" : "")} style={{ marginLeft: 24 }} />);
  lineup.forEach((entry, i) => {
    const last = i === lineup.length - 1;
    const nodeCls = entry.locked ? "locked" : (roundPhase === "reveal" ? entry.status : "fresh");
    const showYear = entry.locked || roundPhase === "reveal";
    rows.push(
      <div className="row" key={entry.key}>
        <div className="rail"><div className={"node " + nodeCls}></div>{!last && <div className="stem"></div>}</div>
        <div className="body">
          <div ref={el => cardRefs.current[i] = el}
               className={"card sm" + (entry.locked ? "" : " fresh") + (roundPhase === "reveal" && !entry.locked ? " is-" + entry.status + " flip" : "")}
               onClick={() => !entry.locked && roundPhase === "place" && returnToHand(entry.key)}
               style={draggingId && entry.key === draggingId ? { opacity: .3 } : null}>
            <div className="ic">{entry.ev.icon}</div>
            <div style={{ flex: 1, minWidth: 0 }} className="ttl" >{entry.ev.title}</div>
            {showYear
              ? <span className={"pill year" + (entry.status === "correct" ? " good" : entry.status === "wrong" ? " bad" : "")}>{K.fmtYear(entry.ev.year)}</span>
              : <span className="qpill">?</span>}
          </div>
        </div>
      </div>
    );
    rows.push(<div key={"g" + (i + 1)} className={"gap" + (drag && armed === i + 1 ? " armed" : "")} style={{ marginLeft: 24 }} />);
  });

  // fanned hand layout
  const n = hand.length;
  const spread = Math.min(30, 80 / Math.max(1, n));
  const fan = hand.map((ev, i) => {
    const mid = (n - 1) / 2;
    const rot = (i - mid) * (n > 1 ? spread / 2 : 0);
    const tx = (i - mid) * 62;
    const ty = -Math.abs(i - mid) * 6;
    return (
      <div key={ev.id} className={"hand-card" + (draggingId === ev.id ? " dragging-src" : "")}
           style={{ transform: `translateX(calc(-50% + ${tx}px)) translateY(${ty}px) rotate(${rot}deg)`, zIndex: 10 + i }}
           onPointerDown={(e) => onHandDown(ev, e)}>
        <Mini ev={ev} />
      </div>
    );
  });

  if (!puzzle) {
    return (
      <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center",
                    justifyContent: "center", color: "var(--ink-soft)", fontFamily: "var(--font-sans)" }}>
        Loading today's puzzle…
      </div>
    );
  }

  return (
    <div ref={screenRef} style={{ position: "absolute", inset: 0, padding: "52px 16px 18px", display: "flex", flexDirection: "column" }}>
      <div className="ch-head">
        <div>
          <div className="ch-title">{puzzle.theme}</div>
          <div style={{ fontFamily: "var(--font-sans)", fontSize: 10, color: "var(--ink-soft)", marginTop: 2 }}>
            {puzzle.subtitle ? puzzle.subtitle + " · " : ""}{puzzle.date}{loadError ? " · offline sample" : ""}
          </div>
          <div className="round-dots">
            {ROUNDS.map((_, i) => <i key={i} className={i + 1 < round ? "done" : (i + 1 === round ? "now" : "")}>{i + 1 < round ? "✓" : i + 1}</i>)}
            <span style={{ fontFamily: "var(--font-sans)", fontSize: 11, fontWeight: 700, color: "var(--ink-soft)", marginLeft: 2, whiteSpace: "nowrap" }}>Round {round}/3</span>
          </div>
        </div>
        <div style={{ textAlign: "right" }}>
          <div className="scorechip"><b>{score}</b><span>pts</span></div>
          {perfectStreak >= 1 && <div className="streak" style={{ justifyContent: "flex-end" }}>🔥 {perfectStreak} perfect</div>}
        </div>
      </div>

      <div className="instr">
        {roundPhase === "place"
          ? (hand.length ? "Drag cards into the timeline — oldest at top" : "All placed — lock it in to reveal")
          : "The years are in…"}
      </div>

      <div className="line noscroll">
        {lineup.length > 0 && <div className="endcap">↑ EARLIER</div>}
        {rows}
        {lineup.length > 0 && <div className="endcap">LATER ↓</div>}
        {lineup.length === 0 && <div style={{ textAlign: "center", color: "var(--ink-soft)", fontFamily: "var(--font-sans)", fontSize: 13, padding: "30px 0" }}>Empty timeline — drop your first card</div>}
      </div>

      {/* bottom: hand / lock / reveal */}
      {roundPhase === "place" && !handReady &&
        <div className="hand-zone">
          <div className="hand-label">Your hand · {hand.length} {hand.length === 1 ? "card" : "cards"}</div>
          {fan}
        </div>}
      {roundPhase === "place" && handReady &&
        <div className="lock-bar"><button className="btn" onClick={lockRound}>🔒 Lock it in</button></div>}

      {roundPhase === "reveal" && roundResult &&
        <div className="reveal-panel">
          <div className={"rp-card" + (roundResult.perfect ? "" : " partial")}>
            <h3>{roundResult.perfect ? "Perfect round!" : `${roundResult.correct} of ${roundResult.total} in order`}</h3>
            <p>{roundResult.perfect ? "Every card landed right" : "The mismatched cards snap to their true spot"}</p>
            <div style={{ marginTop: 6 }}><span className="pill good">+{roundResult.gained} pts{roundResult.mult > 1 ? `  ×${roundResult.mult}` : ""}</span></div>
            <div style={{ marginTop: 8, textAlign: "left", display: "flex", flexDirection: "column", gap: 6 }}>
              {lineup.filter(function (e) { return !e.locked && e.ev.blurb; }).map(function (e) {
                return (
                  <div key={e.key} style={{ fontFamily: "var(--font-sans)", fontSize: 10, color: "var(--ink-soft)" }}>
                    <b style={{ color: "var(--ink)" }}>{K.fmtYear(e.ev.year)}</b> — {e.ev.blurb}
                  </div>
                );
              })}
            </div>
          </div>
          {roundResult.perfect && perfectStreak >= 1 && round < ROUNDS.length &&
            <div className="bonus">🔥 PERFECT — a second perfect round scores <b>×2</b></div>}
          <div className="lock-bar"><button className="btn" onClick={nextRound}>{round >= ROUNDS.length ? "See results" : "Next round →"}</button></div>
        </div>}

      {/* ghost */}
      {drag &&
        <div className="ghost" style={{ left: ghost.x, top: ghost.y, width: 150, transform: "translate(-50%,-50%) rotate(-3deg) scale(1.05)" }}>
          <Mini ev={drag.ev} />
        </div>}

      {/* results */}
      {phase === "done" &&
        <div className="results">
          <div style={{ fontWeight: 700, letterSpacing: 1, textTransform: "uppercase", color: "var(--ink-soft)", fontFamily: "var(--font-sans)", fontSize: 12 }}>Run complete · 10 cards</div>
          <div className="big">{score}</div>
          <h2>{roundScores.every(r => r.pct === 100) ? "Master historian!" : "Run logged"}</h2>
          {(() => { const s = readStats(); return s ? (
            <div style={{ fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 700, color: "var(--ink-soft)" }}>
              🔥 {s.currentStreak} day streak · best {s.maxStreak}
            </div>
          ) : null; })()}
          <div className="barwrap">
            {roundScores.map((r, i) =>
              <div className="barrow" key={i}><span style={{ width: 58 }}>Round {i + 1}</span><div className="bar"><i style={{ width: r.pct + "%" }} /></div><span>{r.pct}%</span></div>)}
          </div>
          <button className="btn" onClick={() => {
            const stats = readStats() || { currentStreak: 0 };
            const text = LG.shareString({
              date: puzzle.date,
              theme: puzzle.theme,
              score: score,
              currentStreak: stats.currentStreak,
              rounds: roundScores.map(r => ({ marks: r.marks || [] }))
            });
            if (navigator.clipboard) navigator.clipboard.writeText(text);
            setCopied(true); setTimeout(() => setCopied(false), 1500);
          }}>{copied ? "Copied! ✓" : "Share results"}</button>
          <button className="btn ghost" style={{ marginTop: 8 }}
                  onClick={() => newGame(K.pickEvents(10).map(e => Object.assign({}, e, { blurb: "" })))}>
            Practice with a random deck
          </button>
        </div>}

      <TweaksPanel title="Tweaks">
        <TweakSection label="Theme" />
        <TweakSelect label="Visual style" value={t.theme}
          options={K.THEMES.map(v => ({ value: v, label: K.THEME_LABELS[v] }))}
          onChange={(v) => setTweak("theme", v)} />
        <TweakSection label="Feel" />
        <TweakToggle label="Juicy effects" value={t.juice} onChange={(v) => setTweak("juice", v)} />
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
