<script lang="ts">
  import { onMount } from 'svelte';
  import { gateway } from '../lib/gateway';
  import { gameSource, isLocalGameId } from '../lib/gamesource';
  import { connection } from '../lib/connection.svelte';
  import { handleError, showToast } from '../lib/app.svelte';
  import { t } from '../lib/i18n/index.svelte';
  import { alphabetLetters } from '../lib/alphabet';
  import { canCheckWord, dictionaryLookupUrl, sanitizeCheckWord } from '../lib/checkword';
  import { onExternalLinkClick } from '../lib/links';
  import { getCachedGame } from '../lib/gamecache';
  import type { Variant } from '../lib/model';

  // Word-check on its own screen: unlimited dictionary lookups, each with a
  // complaint, off the board so the soft keyboard never relayouts the play area.
  let { id }: { id: string } = $props();

  let variant = $state<Variant>('scrabble_en');
  let dictVersion = $state('');
  let word = $state('');
  let result = $state<{ word: string; legal: boolean } | null>(null);
  // unavailable is set when an offline check cannot run because the game's pinned dictionary is not
  // on the device (not cached/bundled); the panel then shows a neutral "offline" note.
  let unavailable = $state(false);
  let cooling = $state(false);
  const checked = new Map<string, boolean>();

  onMount(async () => {
    // Seed the variant + pinned version from the cached game first (present once the board has opened,
    // and preloaded for ongoing games): an online game's dictionary must keep working while offline,
    // where the gameState call below cannot run. The variant's alphabet is already cached from opening
    // the board, so input sanitising and the offline dawg fallback work off the seed alone.
    const cached = getCachedGame(id)?.view.game;
    if (cached) {
      variant = cached.variant;
      dictVersion = cached.dictVersion;
    }
    try {
      // Refresh over the network (and cache the alphabet on a cold deep-link that skipped the board).
      const st = await gameSource(id).gameState(id, true);
      variant = st.game.variant;
      dictVersion = st.game.dictVersion;
    } catch (e) {
      // Offline or a transient failure: the cache seed already set variant + version, so only surface
      // an error when there was nothing cached to fall back to.
      if (!cached) handleError(e);
    }
  });

  function onInput(e: Event) {
    word = sanitizeCheckWord((e.target as HTMLInputElement).value, alphabetLetters(variant));
    unavailable = false;
  }
  // Disabled while cooling, for an already-checked word, or an out-of-range length.
  function canCheck(): boolean {
    return canCheckWord(word, checked.has(word.trim().toUpperCase()), cooling);
  }
  async function runCheck() {
    if (!canCheck()) return;
    const w = word.trim().toUpperCase();
    cooling = true;
    setTimeout(() => (cooling = false), 5000);
    unavailable = false;
    try {
      // Offline in an online game the network check_word would fail, so fall back to the game's own
      // pinned dictionary read on-device: exact when that dawg is cached/bundled, otherwise the check
      // is unavailable. A local game already resolves checkWord on-device, so it keeps that path.
      if (!isLocalGameId(id) && !connection.online) {
        // Dynamically imported so the dawg reader/loader stays out of the app entry bundle (the dict
        // subsystem is lazy everywhere else too); it loads only when an offline check actually runs.
        const { localWordCheck } = await import('../lib/dict/check');
        const legal = await localWordCheck(variant, dictVersion, w);
        if (legal === null) {
          result = null;
          unavailable = true;
          return;
        }
        checked.set(w, legal);
        result = { word: w, legal };
        return;
      }
      const r = await gameSource(id).checkWord(id, w, variant);
      checked.set(w, r.legal);
      result = { word: w, legal: r.legal };
    } catch (e) {
      handleError(e);
    }
  }
  async function complain() {
    if (!result) return;
    try {
      await gateway.complaint(id, result.word, '');
      showToast(t('game.complaintSent'));
      result = null;
    } catch (e) {
      handleError(e);
    }
  }
</script>

<div class="wrap">
  <div class="check">
    <input
      value={word}
      oninput={onInput}
      onkeydown={(e) => e.key === 'Enter' && runCheck()}
      placeholder={t('game.checkWordPrompt')}
    />
    <button onclick={runCheck} disabled={!canCheck()}>{t('game.check')}</button>
  </div>
  {#if unavailable}
    <p class="verdict">{t('game.checkOffline')}</p>
  {:else if result}
    <p class="verdict" class:ok={result.legal} class:bad={!result.legal}>
      {result.legal
        ? t('game.wordLegal', { word: result.word })
        : t('game.wordIllegal', { word: result.word })}
    </p>
    <div class="actions">
      <!-- Complaints go to the admin over the network; a local (offline) game has no backend to
           receive them, so the control is dropped there. -->
      {#if !isLocalGameId(id) && connection.online}
        <button class="complain" onclick={complain}>{t('game.complain')}</button>
      {/if}
      {#if result.legal && connection.online}
        <a
          class="lookup"
          href={dictionaryLookupUrl(result.word, variant)}
          target="_blank"
          rel="noopener noreferrer"
          onclick={onExternalLinkClick}>{t('game.lookup')}</a>
      {/if}
    </div>
  {/if}
</div>

<style>
  .wrap {
    padding: 16px var(--pad);
    display: flex;
    flex-direction: column;
    gap: 14px;
  }
  .check {
    display: flex;
    gap: 8px;
  }
  .check input {
    flex: 1;
    min-width: 0;
    padding: 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg);
    color: var(--text);
    text-transform: uppercase;
  }
  .check button {
    padding: 10px 16px;
    border: 1px solid var(--accent);
    background: var(--accent);
    color: var(--accent-text);
    border-radius: var(--radius-sm);
  }
  .check button:disabled {
    opacity: 0.5;
  }
  .verdict {
    margin: 0;
    font-weight: 600;
  }
  .verdict.ok {
    color: var(--ok, #2e7d32);
  }
  .verdict.bad {
    color: var(--danger, #c0392b);
  }
  .actions {
    display: flex;
    align-items: center;
    gap: 12px;
  }
  .complain {
    padding: 8px 14px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    border-radius: var(--radius-sm);
  }
  .lookup {
    color: var(--accent);
    text-decoration: underline;
    font-size: 0.95em;
  }
</style>
