<script>
  import { getJSON, postJSON } from '$lib/api.js';

  let { variant = '5', onstatus } = $props();

  let length = $state(5);
  let maxGuesses = $state(6);
  let answer = $state(null);          // revealed by the server only at win/loss
  let why = $state(null);
  let date = $state('');
  let dict = $state(null);            // Set of valid guesses
  let guesses = $state([]);           // submitted rows (strings)
  let cols = $state([]);              // server-returned colours, parallel to guesses
  let current = $state('');           // the row being typed
  let status = $state('playing');     // 'playing' | 'won' | 'lost'
  let loading = $state(true);
  let submitting = $state(false);
  let message = $state('');
  let ready = $state(false);          // animate-in once loaded

  const ROWS = 'qwertyuiop|asdfghjkl|zxcvbnm'.split('|').map((r) => r.split(''));

  const stateKey = $derived(`goodnews:word:${variant}:${date}`);
  const statsKey = $derived(`goodnews:word:${variant}:stats`);

  async function load() {
    loading = true; ready = false;
    guesses = []; cols = []; current = ''; status = 'playing'; answer = null; why = null; message = '';
    try {
      const p = await getJSON('/api/puzzle/word?variant=' + variant);  // holds NO answer
      length = p.length; maxGuesses = p.guesses; date = p.date;
      if (!dict || dict._len !== length) {
        const words = await getJSON('/words-' + length + '.json');
        dict = new Set(words); dict._len = length;
      }
      restore();
    } catch {
      message = 'Could not load today’s puzzle.';
    }
    loading = false;
    requestAnimationFrame(() => (ready = true));
  }

  function restore() {
    try {
      const saved = JSON.parse(localStorage.getItem(stateKey) || 'null');
      if (saved && Array.isArray(saved.guesses)) {
        guesses = saved.guesses;
        cols = saved.cols || [];
        status = saved.status || 'playing';
        answer = saved.answer ?? null;
        why = saved.why ?? null;
      }
    } catch { /* ignore */ }
    onstatus?.(summary());
  }
  function persist() {
    try { localStorage.setItem(stateKey, JSON.stringify({ guesses, cols, status, answer, why })); } catch { /* ignore */ }
    onstatus?.(summary());
  }
  function summary() {
    return { variant, date, status, tries: guesses.length, max: maxGuesses };
  }

  // Best-known status per key for the on-screen keyboard, from server colours.
  let keyState = $derived.by(() => {
    const ks = {};
    const rank = { absent: 0, present: 1, correct: 2 };
    guesses.forEach((g, gi) => {
      const cs = cols[gi] || [];
      for (let i = 0; i < g.length; i++) {
        const k = g[i];
        if (cs[i] && (!(k in ks) || rank[cs[i]] > rank[ks[k]])) ks[k] = cs[i];
      }
    });
    return ks;
  });

  function flash(m) { message = m; setTimeout(() => (message = ''), 1400); }

  function key(k) {
    if (status !== 'playing' || loading || submitting) return;
    if (k === 'enter') return submit();
    if (k === 'back') { current = current.slice(0, -1); return; }
    if (/^[a-z]$/.test(k) && current.length < length) current += k;
  }

  async function submit() {
    if (submitting) return;
    if (current.length < length) return flash('Not enough letters');
    if (dict && !dict.has(current)) return flash('Not in word list');
    submitting = true;
    try {
      const res = await postJSON('/api/puzzle/word/guess', { variant, guess: current, n: guesses.length + 1 });
      guesses = [...guesses, current];
      cols = [...cols, res.colors];
      current = '';
      if (res.solved) { status = 'won'; answer = res.answer; why = res.why; recordStat(true); }
      else if (guesses.length >= maxGuesses) { status = 'lost'; answer = res.answer; why = res.why; recordStat(false); }
      persist();
    } catch {
      flash('Hmm — couldn’t check that. Try again.');
    } finally {
      submitting = false;
    }
  }

  function recordStat(won) {
    try {
      const s = JSON.parse(localStorage.getItem(statsKey) || 'null') || { played: 0, won: 0, streak: 0, dist: {} };
      s.played += 1;
      if (won) { s.won += 1; s.streak += 1; s.dist[guesses.length] = (s.dist[guesses.length] || 0) + 1; }
      else { s.streak = 0; }
      localStorage.setItem(statsKey, JSON.stringify(s));
    } catch { /* ignore */ }
  }
  let stats = $derived.by(() => {
    if (status === 'playing') return null;
    try { return JSON.parse(localStorage.getItem(statsKey) || 'null'); } catch { return null; }
  });

  function onKeydown(e) {
    if (e.metaKey || e.ctrlKey || e.altKey) return;
    const k = e.key.toLowerCase();
    if (k === 'enter' || k === 'backspace' || /^[a-z]$/.test(k)) {
      e.preventDefault();
      key(k === 'backspace' ? 'back' : k);
    }
  }

  const EMOJI = { correct: '🟩', present: '🟨', absent: '⬛' };
  let copied = $state(false);
  function share() {
    const label = variant === '6' ? 'Long Word' : 'Daily Word';
    const score = status === 'won' ? guesses.length : 'X';
    const grid = cols.map((cs) => cs.map((c) => EMOJI[c]).join('')).join('\n');
    const text = `Upbeat Bytes · ${label} ${date}\n${score}/${maxGuesses}\n${grid}\nupbeatbytes.com/play`;
    if (navigator.share) navigator.share({ text }).catch(() => {});
    else navigator.clipboard?.writeText(text).then(() => { copied = true; setTimeout(() => (copied = false), 1500); });
  }

  // Load on mount and whenever the variant toggles. Tracks ONLY `variant` — it
  // must not depend on `loading`, or load()'s own loading flip would re-fire it.
  $effect(() => {
    variant;
    load();
  });
</script>

<svelte:window onkeydown={onKeydown} />

<div class="wordgame" class:ready>
  {#if loading}
    <p class="muted">Loading today’s puzzle…</p>
  {:else}
    <div class="play-area">
      <div class="board" style="--cols:{length}; --rows:{maxGuesses}">
        {#each Array(maxGuesses) as _, r (r)}
          {@const g = guesses[r]}
          {@const cs = cols[r] || null}
          <div class="row">
            {#each Array(length) as _, c (c)}
              {@const ch = g ? g[c] : (r === guesses.length ? current[c] : '')}
              <div class="tile {cs ? cs[c] : ''}" class:filled={!!ch}>{(ch || '').toUpperCase()}</div>
            {/each}
          </div>
        {/each}
      </div>

      {#if message}<p class="flash">{message}</p>{/if}

      {#if status !== 'playing'}
        <div class="result rise">
          <p class="rmark">{status === 'won' ? '✦ nicely done ✦' : 'today’s word:'}
            {#if status === 'lost' && answer}<strong>{answer.toUpperCase()}</strong>{/if}</p>
          {#if why}<p class="why"><span class="lbl">Why this word</span>{why}</p>{/if}
          {#if stats}
            <div class="stats">
              <div><span class="n">{stats.played}</span><span class="l">played</span></div>
              <div><span class="n">{stats.played ? Math.round(100 * stats.won / stats.played) : 0}%</span><span class="l">solved</span></div>
              <div><span class="n">{stats.streak}</span><span class="l">streak</span></div>
            </div>
          {/if}
          <button class="share" onclick={share}>{copied ? 'Copied!' : 'Share result'}</button>
        </div>
      {/if}
    </div>

    {#if status === 'playing'}
      <div class="keyboard">
        {#each ROWS as row, ri (ri)}
          <div class="krow">
            {#if ri === 2}<button class="key wide enter" onclick={() => key('enter')} aria-label="Enter">↵</button>{/if}
            {#each row as k (k)}
              <button class="key {keyState[k] || ''}" onclick={() => key(k)}>{k.toUpperCase()}</button>
            {/each}
            {#if ri === 2}<button class="key wide" onclick={() => key('back')} aria-label="Backspace">⌫</button>{/if}
          </div>
        {/each}
      </div>
    {/if}
  {/if}
</div>

<style>
  .wordgame { max-width: 480px; margin: 0 auto; opacity: 0; transform: translateY(6px); }
  .wordgame.ready { opacity: 1; transform: none; transition: opacity 0.3s ease, transform 0.3s ease; }
  .board { display: grid; gap: 6px; margin: 0 auto 18px; width: min(100%, 340px); }
  .row { display: grid; grid-template-columns: repeat(var(--cols), 1fr); gap: 6px; }
  .tile {
    aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
    border: 2px solid var(--line); border-radius: 8px; font-family: var(--label);
    font-weight: 700; font-size: 1.5rem; color: var(--ink); text-transform: uppercase;
    background: var(--surface);
  }
  .tile.filled { border-color: #b7c0cb; }
  .tile.correct { background: #4a9d6e; border-color: #4a9d6e; color: #fff; }
  .tile.present { background: #d8b24a; border-color: #d8b24a; color: #fff; }
  .tile.absent { background: #9aa6b2; border-color: #9aa6b2; color: #fff; }
  .flash {
    text-align: center; background: var(--ink); color: #fff; border-radius: 8px;
    padding: 7px 14px; width: fit-content; margin: 0 auto 12px; font-size: 0.86rem;
  }
  /* Flat, warm, on-brand keys — off-white with a hairline border, soft radius,
     no heavy raised shadow. Centered, never a full-bleed slab. */
  .keyboard { display: flex; flex-direction: column; gap: 6px; margin: 10px auto 0; max-width: 430px; }
  .krow { display: flex; gap: 5px; justify-content: center; }
  .key {
    flex: 1; min-width: 0; max-width: 42px; height: 48px; border: 1px solid var(--line); border-radius: 11px;
    background: var(--surface); color: var(--ink); font-family: var(--label); font-weight: 600;
    font-size: 0.92rem; cursor: pointer; text-transform: uppercase; transition: background 0.12s ease, transform 0.05s ease;
  }
  .key:active { transform: translateY(1px); background: var(--bg); }
  .key.wide { max-width: 56px; font-size: 1.1rem; }
  .key.enter { background: var(--accent-soft); border-color: transparent; color: var(--accent-deep); }
  .key.correct { background: #4a9d6e; border-color: #4a9d6e; color: #fff; }
  .key.present { background: #d8b24a; border-color: #d8b24a; color: #fff; }
  .key.absent { background: #9aa6b2; border-color: #9aa6b2; color: #fff; }
  .key:hover { filter: brightness(0.98); }
  .muted { color: var(--muted); text-align: center; }

  /* Mobile: a focused app screen — board sizes to fit BOTH width and the height
     left above the keyboard, so the active row + keyboard are always visible. */
  @media (max-width: 720px) {
    .wordgame { display: flex; flex-direction: column; height: 100%; max-width: 100%; }
    .play-area { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column;
                 justify-content: center; padding: 4px 0; }
    .board {
      --tile: min(58px, calc((100vw - 44px) / var(--cols)), calc((100dvh - 300px) / var(--rows)));
      gap: 4px; width: fit-content; margin: 0 auto 10px;
    }
    .row { grid-template-columns: repeat(var(--cols), var(--tile)); gap: 4px; }
    .tile { width: var(--tile); height: var(--tile); aspect-ratio: auto; font-size: calc(var(--tile) * 0.46); }
    .keyboard { flex-shrink: 0; margin: 8px auto calc(env(safe-area-inset-bottom) + 4px); gap: 5px; }
    .key { height: 46px; }
  }
  .result { text-align: center; }
  .rmark { font-family: var(--serif); font-style: italic; color: var(--accent-deep); font-size: 1.2rem; margin: 0 0 10px; }
  .rmark strong { font-style: normal; letter-spacing: 0.06em; }
  .result .why { color: #3b4754; font-size: 0.95rem; margin: 0 auto 16px; max-width: 380px;
                 border-left: 2px solid var(--accent); padding-left: 12px; text-align: left; }
  .result .why .lbl { display: block; text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.68rem;
                      color: var(--accent-deep); font-weight: 600; margin-bottom: 2px; }
  .stats { display: flex; justify-content: center; gap: 26px; margin: 0 0 18px; }
  .stats .n { display: block; font-size: 1.6rem; font-weight: 700; font-family: var(--label); }
  .stats .l { color: var(--muted); font-size: 0.78rem; }
  .share { background: var(--accent); color: #fff; border: none; border-radius: 999px;
           padding: 11px 26px; font: inherit; font-weight: 600; cursor: pointer; }
  .share:hover { background: var(--accent-deep); }
</style>
