/**
 * Disk-backed loader for the token-efficiency fixture corpus (#1256).
 *
 * The corpus is 5 archetypes × 10 instances = 50 fixtures, generated by
 * `scripts/bench/generate-token-fixtures.mjs` and committed to disk under
 * this directory. Each fixture is a pair:
 *
 *   <archetype>/<archetype>-NN.html
 *   <archetype>/<archetype>-NN.ground-truth.json
 *
 * This loader is the single read path that the runner + tests use; the
 * inline `corpus.ts` re-exports its result so the import path
 * (`./fixtures/token-efficiency/corpus`) used elsewhere is unchanged.
 *
 * Per the issue spec the fixtures are FROZEN local: tests do not regenerate
 * them on the fly. The generator is the authoring tool only.
 */

import * as fs from 'fs';
import * as path from 'path';

import type { GroundTruthField, GroundTruthSpec } from '../../token-efficiency';

export type FixtureArchetype =
  | 'ecommerce'
  | 'news'
  | 'docs'
  | 'spa'
  | 'search-results';

/** Stable archetype order — drives test reporting + chart facet order. */
export const FIXTURE_ARCHETYPES: readonly FixtureArchetype[] = [
  'ecommerce',
  'news',
  'docs',
  'spa',
  'search-results',
];

export interface TokenEfficiencyFixture {
  name: string;
  archetype: FixtureArchetype;
  /** Full HTML document — the raw input every library receives. */
  html: string;
  groundTruth: GroundTruthSpec;
}

const FIXTURES_ROOT = __dirname;

interface GroundTruthFileShape {
  fixture: string;
  fields: GroundTruthField[];
}

function readGroundTruth(file: string): GroundTruthFileShape {
  const raw = fs.readFileSync(file, 'utf8');
  const parsed = JSON.parse(raw) as Partial<GroundTruthFileShape>;
  if (!parsed || typeof parsed.fixture !== 'string' || !Array.isArray(parsed.fields)) {
    throw new Error(`malformed ground-truth file: ${file}`);
  }
  for (const f of parsed.fields) {
    if (!f || typeof f.key !== 'string' || typeof f.expected !== 'string') {
      throw new Error(`malformed field in ${file}: ${JSON.stringify(f)}`);
    }
  }
  return parsed as GroundTruthFileShape;
}

function loadOne(archetype: FixtureArchetype, htmlFile: string): TokenEfficiencyFixture {
  const dir = path.join(FIXTURES_ROOT, archetype);
  const name = path.basename(htmlFile, '.html');
  const html = fs.readFileSync(path.join(dir, htmlFile), 'utf8');
  const gt = readGroundTruth(path.join(dir, `${name}.ground-truth.json`));
  if (gt.fixture !== name) {
    throw new Error(
      `ground-truth file ${name}.ground-truth.json declares fixture "${gt.fixture}"`,
    );
  }
  return {
    name,
    archetype,
    html,
    groundTruth: { fixture: name, fields: gt.fields },
  };
}

function compareFixtures(a: TokenEfficiencyFixture, b: TokenEfficiencyFixture): number {
  const ai = FIXTURE_ARCHETYPES.indexOf(a.archetype);
  const bi = FIXTURE_ARCHETYPES.indexOf(b.archetype);
  if (ai !== bi) return ai - bi;
  return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
}

/**
 * Read all fixtures from disk. Pure synchronous filesystem I/O — runs at
 * module load time so the corpus is a constant array, mirroring how the
 * previous inline corpus worked.
 */
export function loadTokenEfficiencyCorpus(): TokenEfficiencyFixture[] {
  const fixtures: TokenEfficiencyFixture[] = [];
  for (const archetype of FIXTURE_ARCHETYPES) {
    const dir = path.join(FIXTURES_ROOT, archetype);
    if (!fs.existsSync(dir)) continue;
    const htmlFiles = fs
      .readdirSync(dir)
      .filter((f) => f.endsWith('.html'));
    for (const htmlFile of htmlFiles) {
      fixtures.push(loadOne(archetype, htmlFile));
    }
  }
  return fixtures.sort(compareFixtures);
}

/**
 * Deterministic structured extraction baseline — parses the `data-field`
 * spans into a field-keyed record. Mirrors the prior inline-corpus extractor.
 * Returns a structured record, never a raw blob, so it is scored fairly by
 * `computeRetention`.
 */
export function deterministicExtract(html: string): Record<string, string> {
  const extracted: Record<string, string> = {};
  // `[\s\S]*?` so a span value that contains a newline still matches; the
  // generator emits values on one line today, but tightening to `[^<]*` would
  // brittle the loader against future cosmetic edits.
  const re = /<span data-field="([^"]+)">([\s\S]*?)<\/span>/g;
  let match: RegExpExecArray | null;
  while ((match = re.exec(html)) !== null) {
    extracted[match[1]] = match[2];
  }
  return extracted;
}
