import { useCallback, useEffect, useRef, useState } from 'react';

import type { Pattern } from './engine';
import type { SolverRequest, SolverResponse } from './solver.worker';

/**
 * The entropy solver, on a worker, with a main-thread fallback.
 *
 * This is what lets the page answer for ANY word the visitor picks rather than
 * only the handful the recordings cover — the difference between a demo and a
 * video. The opening scan is roughly 4,600 x 4,600 pattern computations, which
 * visibly freezes a phone if it runs on the main thread at the exact moment
 * somebody first touches the board.
 *
 * `new URL(..., import.meta.url)` gives a same-origin module in the build.
 * Never Vite's `?worker&inline`: that yields a blob: URL, and production CSP has
 * no `worker-src`, so it falls back to `default-src 'self'` and the worker is
 * blocked with no console error — in production only. CI greps the bundle for
 * exactly that construction.
 */
export interface Suggestion {
  guess: string;
  bits: number;
  viable: boolean;
  candidatesBefore: number;
}

export type SolverState =
  | { status: 'idle' }
  | { status: 'thinking' }
  | { status: 'ready'; suggestion: Suggestion }
  | { status: 'unavailable' };

export function useSolver(pool: readonly string[]) {
  const workerRef = useRef<Worker | null>(null);
  const nextId = useRef(0);
  const pending = useRef<number | null>(null);
  const [state, setState] = useState<SolverState>({ status: 'idle' });

  useEffect(() => {
    let worker: Worker | null = null;
    try {
      worker = new Worker(new URL('./solver.worker.ts', import.meta.url), { type: 'module' });
      worker.onmessage = (event: MessageEvent<SolverResponse>) => {
        // Ignore everything but the most recent request, or a slow first scan
        // lands after a later, faster one and the hint goes backwards.
        if (event.data.id !== pending.current) return;
        const { guess, bits, viable, candidatesBefore } = event.data;
        setState({ status: 'ready', suggestion: { guess, bits, viable, candidatesBefore } });
      };
      worker.onerror = () => setState({ status: 'unavailable' });
      workerRef.current = worker;
    } catch {
      // Some embedded browsers refuse module workers outright. The button is
      // hidden rather than shown broken.
      setState({ status: 'unavailable' });
    }
    return () => {
      worker?.terminate();
      workerRef.current = null;
    };
  }, []);

  const ask = useCallback(
    (history: readonly { guess: string; pattern: Pattern }[]) => {
      const worker = workerRef.current;
      if (!worker) {
        setState({ status: 'unavailable' });
        return;
      }
      const id = (nextId.current += 1);
      pending.current = id;
      setState({ status: 'thinking' });
      const request: SolverRequest = {
        id,
        pool: pool as string[],
        history: history.map((h) => ({ guess: h.guess, pattern: h.pattern })),
      };
      worker.postMessage(request);
    },
    [pool],
  );

  const reset = useCallback(() => {
    pending.current = null;
    setState({ status: 'idle' });
  }, []);

  return { state, ask, reset };
}
