import { useEffect, useMemo } from 'react';

import { cn } from '@/lib/utils';

import { play, type BoardState } from './engine';
import { useSolver } from './useSolver';
import { allowedNow, ANSWERS, primeGuessList } from './words';

const ROWS = ['qwertyuiop', 'asdfghjkl', 'zxcvbnm'];

/** Best-known state of each letter, for tinting the keys. */
function letterStates(board: BoardState): Record<string, 'G' | 'Y' | 'X'> {
  const rank = { X: 0, Y: 1, G: 2 } as const;
  const out: Record<string, 'G' | 'Y' | 'X'> = {};
  for (const { guess, pattern } of board.rows) {
    for (let i = 0; i < guess.length; i += 1) {
      const letter = guess[i]!;
      const tile = pattern[i] as 'G' | 'Y' | 'X';
      const current = out[letter];
      if (!current || rank[tile] > rank[current]) out[letter] = tile;
    }
  }
  return out;
}

const KEY_TINT: Record<string, string> = {
  G: 'bg-tile-exact text-tile-exact-fg',
  Y: 'bg-tile-present text-tile-present-fg',
  X: 'bg-tile-absent/70 text-tile-absent-fg',
};

export function Keyboard({
  state,
  onChange,
}: {
  state: BoardState;
  onChange: (next: BoardState) => void;
}) {
  const states = useMemo(() => letterStates(state), [state]);
  const done = state.status !== 'playing';
  const solver = useSolver(ANSWERS);

  useEffect(() => {
    void primeGuessList();
  }, []);

  const press = (key: string) => {
    if (done) return;
    if (key === 'enter') {
      if (state.draft.length !== 5) {
        onChange({ ...state, invalid: 'not enough letters' });
        return;
      }
      onChange(play(state, state.draft, allowedNow()));
      return;
    }
    if (key === 'back') {
      onChange({ ...state, draft: state.draft.slice(0, -1), invalid: null });
      return;
    }
    if (state.draft.length < 5) {
      onChange({ ...state, draft: state.draft + key, invalid: null });
    }
  };

  // A physical keyboard is how anyone on a laptop will actually play, and
  // wiring only the on-screen keys is the most common way that gets forgotten.
  useEffect(() => {
    const handler = (event: KeyboardEvent) => {
      if (event.metaKey || event.ctrlKey || event.altKey) return;
      const target = event.target as HTMLElement | null;
      if (target && /^(INPUT|TEXTAREA)$/.test(target.tagName)) return;
      if (event.key === 'Enter') press('enter');
      else if (event.key === 'Backspace') press('back');
      else if (/^[a-zA-Z]$/.test(event.key)) press(event.key.toLowerCase());
      else return;
      event.preventDefault();
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  });

  const hint = solver.state;

  return (
    <div className="grid gap-1 sm:gap-1.5" style={{ paddingBottom: 'max(0px, var(--safe-bottom))' }}>
      {/* The solver, live, on whatever word you picked — not a recording. This
          is the difference between a demo and a video: it answers for boards no
          rollout on this site ever covered. */}
      {hint.status !== 'unavailable' && !done ? (
        <div className="mb-1 flex min-h-9 flex-wrap items-center gap-2 text-xs">
          <button
            type="button"
            onClick={() => solver.ask(state.rows)}
            disabled={hint.status === 'thinking'}
            className="tap inline-flex items-center rounded-md border border-border px-2.5 py-1 font-medium transition-colors duration-1 hover:bg-accent-subtle disabled:opacity-50"
          >
            {hint.status === 'thinking' ? 'Thinking…' : 'What would the best player guess?'}
          </button>
          <span aria-live="polite" className="text-muted">
            {hint.status === 'ready' ? (
              <>
                <span className="font-mono font-semibold uppercase text-fg">
                  {hint.suggestion.guess}
                </span>{' '}
                — <span className="nums">{hint.suggestion.bits.toFixed(2)}</span> bits from{' '}
                <span className="nums">{hint.suggestion.candidatesBefore.toLocaleString('en-US')}</span>{' '}
                remaining
                {hint.suggestion.viable ? null : (
                  <>
                    {' '}
                    ·{' '}
                    <span className="text-warning">
                      cannot win — it is buying information
                    </span>
                  </>
                )}
              </>
            ) : null}
          </span>
        </div>
      ) : null}
      {ROWS.map((row, index) => (
        <div key={row} className="flex justify-center gap-1 sm:gap-1.5">
          {index === 2 ? (
            <button
              type="button"
              onClick={() => press('enter')}
              disabled={done}
              className="tap flex-[1.6] rounded-md bg-surface-2 px-1 text-[0.65rem] font-semibold uppercase tracking-wide transition-colors duration-1 hover:bg-accent-subtle disabled:opacity-40"
            >
              Enter
            </button>
          ) : null}
          {[...row].map((letter) => (
            <button
              key={letter}
              type="button"
              onClick={() => press(letter)}
              disabled={done}
              aria-label={letter.toUpperCase()}
              className={cn(
                'tap min-w-0 flex-1 rounded-md text-sm font-semibold uppercase transition-colors duration-1 disabled:opacity-40',
                states[letter] ? KEY_TINT[states[letter]!] : 'bg-surface-2 hover:bg-accent-subtle',
              )}
            >
              {letter}
            </button>
          ))}
          {index === 2 ? (
            <button
              type="button"
              onClick={() => press('back')}
              disabled={done}
              aria-label="Backspace"
              className="tap flex-[1.6] rounded-md bg-surface-2 px-1 text-[0.65rem] font-semibold uppercase tracking-wide transition-colors duration-1 hover:bg-accent-subtle disabled:opacity-40"
            >
              Del
            </button>
          ) : null}
        </div>
      ))}
    </div>
  );
}

export default Keyboard;
