import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { Dawg } from './dawg';

// The step-by-step DAWG cursor (root/final/next/arcs) is the primitive the move
// generator walks. These fast unit tests pin it against a small committed sample
// dictionary (backend/cmd/movegen); the full parity vs the Go solver lands with
// the generator's conformance fixtures.
const bytes = new Uint8Array(readFileSync(new URL('./testdata/sample_en.dawg', import.meta.url)));
const fixture = JSON.parse(
  readFileSync(new URL('./testdata/sample_en.words.json', import.meta.url), 'utf8'),
) as { numAdded: number; words: string[]; indexes: number[][] };

const key = (w: number[]): string => w.join(',');

// enumerateWords walks the whole automaton depth-first, collecting the index path
// at every accepting node — i.e. every stored word.
function enumerateWords(d: Dawg): number[][] {
  const out: number[][] = [];
  const path: number[] = [];
  const visit = (node: number): void => {
    d.arcs(node, (label, dest, final) => {
      path.push(label);
      if (final) out.push(path.slice());
      visit(dest);
      path.pop();
      return true;
    });
  };
  if (d.final(d.root())) out.push([]);
  visit(d.root());
  return out;
}

describe('dawg cursor', () => {
  it('parses the sample fixture', () => {
    const d = new Dawg(bytes);
    expect(d.numAdded).toBe(fixture.numAdded);
  });

  it('root is not an accepting state (the sample has no empty word)', () => {
    const d = new Dawg(bytes);
    expect(d.final(d.root())).toBe(false);
  });

  it('enumerates exactly the stored words', () => {
    const d = new Dawg(bytes);
    const got = enumerateWords(d).map(key).sort();
    const want = fixture.indexes.map(key).sort();
    expect(got).toEqual(want);
  });

  it('next walks a stored word to an accepting node and rejects a non-edge', () => {
    const d = new Dawg(bytes);
    let node = d.root();
    for (const ch of [2, 0, 17, 4, 3]) {
      // "cared"
      node = d.next(node, ch);
      expect(node).toBeGreaterThanOrEqual(0);
    }
    expect(d.final(node)).toBe(true);
    // "care" (index [2,0,17,4]) is an internal accepting node on the way to "cared".
    const care = [2, 0, 17, 4].reduce((n, ch) => d.next(n, ch), d.root());
    expect(d.final(care)).toBe(true);
    // No stored word starts with 'z' (index 25).
    expect(d.next(d.root(), 25)).toBe(-1);
  });
});
