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

// Conformance gate for the ported dawg reader: it must agree with the
// authoritative Go dafsa reader on indexOf for EVERY stored word and EVERY
// negative, across all three shipped dictionaries. The golden vectors are
// produced by `go run ./backend/cmd/dictgen`; point the test at them with:
//   DICT_DAWG_DIR=<scrabble-solver>/dawg DICT_GOLD_DIR=<dictgen out>
// When those are unset the suite skips (kept out of the default unit run until
// a committed sample fixture lands in Phase 1).
const dawgDir = process.env.DICT_DAWG_DIR;
const goldDir = process.env.DICT_GOLD_DIR;
const ready = !!dawgDir && !!goldDir && existsSync(dawgDir) && existsSync(goldDir);

const names = ['en_sowpods', 'ru_scrabble', 'ru_erudit'];

// eachWord walks the [1-byte length][index bytes] framing emitted by dictgen.
function* eachWord(buf: Buffer): Generator<Uint8Array> {
  let off = 0;
  while (off < buf.length) {
    const len = buf[off];
    yield buf.subarray(off + 1, off + 1 + len);
    off += 1 + len;
  }
}

describe.skipIf(!ready)('dawg reader parity vs Go dafsa', () => {
  for (const name of names) {
    it(
      name,
      () => {
        const bytes = new Uint8Array(readFileSync(join(dawgDir!, `${name}.dawg`)));
        const meta = JSON.parse(readFileSync(join(goldDir!, `${name}.meta.json`), 'utf8'));
        const dawg = new Dawg(bytes);

        expect(dawg.numAdded).toBe(meta.numAdded);
        expect(dawg.numNodes).toBe(meta.numNodes);

        // Every stored word: indexOf(word) equals its position in index order.
        let idx = 0;
        let mismatches = 0;
        const firstBad: string[] = [];
        for (const word of eachWord(readFileSync(join(goldDir!, `${name}.words.bin`)))) {
          const got = dawg.indexOf(word);
          if (got !== idx) {
            mismatches++;
            if (firstBad.length < 5) firstBad.push(`idx ${idx}: got ${got} word [${Array.from(word)}]`);
          }
          idx++;
        }
        expect(idx).toBe(meta.numAdded);
        expect(mismatches, firstBad.join('; ')).toBe(0);

        // Negatives must resolve to -1.
        let negMismatches = 0;
        const firstNeg: string[] = [];
        for (const word of eachWord(readFileSync(join(goldDir!, `${name}.neg.bin`)))) {
          const got = dawg.indexOf(word);
          if (got !== -1) {
            negMismatches++;
            if (firstNeg.length < 5) firstNeg.push(`got ${got} word [${Array.from(word)}]`);
          }
        }
        expect(negMismatches, firstNeg.join('; ')).toBe(0);
      },
      120_000,
    );
  }
});
