#!/usr/bin/env node
import * as fsp from 'node:fs/promises';
import * as os from 'node:os';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';

const SCRIPT_PATH = fileURLToPath(import.meta.url);
const REPO_ROOT = path.resolve(path.dirname(SCRIPT_PATH), '..');
const KB_NAME = 'dev-fixture';

const PROFILES = {
  small: { files: 3, targetChunksPerFile: 2 },
  medium: { files: 8, targetChunksPerFile: 4 },
};

const HELP = `npm run dev:fixture — generate a disposable seeded knowledge base

Usage:
  npm run dev:fixture -- [--out=<dir>] [--seed=<int>] [--profile=small|medium] [--keep] [--help]

Generates a deterministic Markdown corpus under a scratch root, writes a
matching retrieval-eval fixture, and prints copy-pasteable kb commands that
target the scratch root and a scratch FAISS index path. The contributor's
real KNOWLEDGE_BASES_ROOT_DIR and FAISS_INDEX_PATH are never touched.

Options:
  --out=<dir>           Output root (default: a fresh /tmp/kb-dev-fixture-<ts>).
  --seed=<int>          Mulberry32 seed for deterministic content (default: 1).
  --profile=small|medium
                        Corpus size profile (default: small).
                        small  = ${PROFILES.small.files} files, ~${PROFILES.small.targetChunksPerFile} chunks/file
                        medium = ${PROFILES.medium.files} files, ~${PROFILES.medium.targetChunksPerFile} chunks/file
  --keep                Print a hint to keep the scratch dir (no auto-cleanup).
  --help, -h            Show this help.
`;

function parseArgs(argv) {
  const out = { seed: 1, profile: 'small', keep: false, help: false, out: null };
  for (const arg of argv) {
    if (arg === '--help' || arg === '-h') {
      out.help = true;
    } else if (arg === '--keep') {
      out.keep = true;
    } else if (arg.startsWith('--out=')) {
      out.out = arg.slice('--out='.length);
      if (!out.out) throw new Error('--out requires a non-empty path');
    } else if (arg.startsWith('--seed=')) {
      const raw = arg.slice('--seed='.length);
      const value = Number(raw);
      if (!Number.isFinite(value) || !Number.isInteger(value)) {
        throw new Error(`--seed must be an integer, got: ${raw}`);
      }
      out.seed = value;
    } else if (arg.startsWith('--profile=')) {
      const value = arg.slice('--profile='.length);
      if (!Object.prototype.hasOwnProperty.call(PROFILES, value)) {
        throw new Error(`--profile must be one of: ${Object.keys(PROFILES).join(', ')}`);
      }
      out.profile = value;
    } else {
      throw new Error(`unknown argument: ${arg} (use --help to see options)`);
    }
  }
  return out;
}

function quoteYamlScalar(text) {
  return `"${text.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
}

async function run(argv) {
  let args;
  try {
    args = parseArgs(argv);
  } catch (err) {
    process.stderr.write(`dev:fixture: ${err.message}\n`);
    return 2;
  }

  if (args.help) {
    process.stdout.write(HELP);
    return 0;
  }

  const outRoot = args.out
    ? path.resolve(args.out)
    : path.join(os.tmpdir(), `kb-dev-fixture-${Date.now()}-${process.pid}`);
  const rootDir = path.join(outRoot, 'knowledge_bases');
  const faissDir = path.join(outRoot, 'faiss');
  const fixtureFile = path.join(outRoot, 'retrieval-eval.yml');

  await fsp.mkdir(rootDir, { recursive: true });
  await fsp.mkdir(faissDir, { recursive: true });

  const generatorUrl = new URL('../benchmarks/fixtures/generator.ts', import.meta.url);
  const { generateKnowledgeBaseFixture } = await import(generatorUrl.href);

  const profileCfg = PROFILES[args.profile];
  const fixture = await generateKnowledgeBaseFixture({
    files: profileCfg.files,
    knowledgeBaseName: KB_NAME,
    rootDir,
    seed: args.seed,
    targetChunksPerFile: profileCfg.targetChunksPerFile,
  });

  const expectedSource = `${KB_NAME}/doc-001.md`;
  const yamlBody = [
    `# Generated by scripts/dev-fixture.mjs`,
    `# seed=${args.seed} profile=${args.profile} files=${fixture.files} chunks=${fixture.chunkCount}`,
    `gate: false`,
    `cases:`,
    `  - name: dev-fixture seeded query`,
    `    query: ${quoteYamlScalar(fixture.query)}`,
    `    kb: ${KB_NAME}`,
    `    required_sources:`,
    `      - ${expectedSource}`,
    `    stale_policy: allow_stale`,
    ``,
  ].join('\n');
  await fsp.writeFile(fixtureFile, yamlBody, 'utf-8');

  const envPrefix = `KNOWLEDGE_BASES_ROOT_DIR=${rootDir} FAISS_INDEX_PATH=${faissDir}`;
  const lines = [
    '',
    `Disposable seeded knowledge base ready.`,
    `  out:                       ${outRoot}`,
    `  KNOWLEDGE_BASES_ROOT_DIR:  ${rootDir}`,
    `  FAISS_INDEX_PATH:          ${faissDir}`,
    `  knowledge base:            ${KB_NAME} (${fixture.files} file(s), ~${fixture.chunkCount} chunk(s))`,
    `  retrieval-eval fixture:    ${fixtureFile}`,
    '',
    'Try these commands (each prefixes the scratch env so your real KB is untouched):',
    `  ${envPrefix} kb list`,
    `  ${envPrefix} kb search ${quoteShell(fixture.query)} --kb=${KB_NAME}`,
    `  ${envPrefix} kb search ${quoteShell(fixture.query)} --kb=${KB_NAME} --group-by-source`,
    `  ${envPrefix} kb search ${quoteShell(fixture.query)} --kb=${KB_NAME} --format=json`,
    `  ${envPrefix} kb eval ${quoteShell(fixtureFile)}`,
    '',
    args.keep
      ? `--keep set. Remove later with: rm -rf ${outRoot}`
      : `Scratch dir is not auto-removed. Remove later with: rm -rf ${outRoot}`,
    '',
  ];
  process.stdout.write(`${lines.join('\n')}`);
  void REPO_ROOT;
  return 0;
}

function quoteShell(text) {
  return `'${text.replace(/'/g, `'\\''`)}'`;
}

const isDirectInvocation = process.argv[1] && path.resolve(process.argv[1]) === SCRIPT_PATH;
if (isDirectInvocation) {
  try {
    process.exitCode = await run(process.argv.slice(2));
  } catch (err) {
    const msg = err instanceof Error && err.stack ? err.stack : String(err);
    process.stderr.write(`dev:fixture: fatal: ${msg}\n`);
    process.exitCode = 1;
  }
}

export { parseArgs, run, PROFILES, KB_NAME, HELP };
