/*
- Date: 2026-06-29
  Spec: plans/2026-06-29-claude-code-raw-replay-harness-plan.md
  Relationship: Vendors OpenAI Codex app-server generated TypeScript protocol
  schemas as a single declaration bundle so Commander replay/coverage tests can
  type-check against the real JSON-RPC protocol surface without committing the
  upstream one-file-per-type layout.
*/
import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join, relative, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawn } from 'node:child_process';

type VendorOptions = {
  readonly sourceRepo: string | undefined;
  readonly sourceUrl: string;
  readonly sourceRef: string;
  readonly outFile: string;
  readonly manifestFile: string;
  readonly keepTemp: boolean;
  readonly check: boolean;
};

type PreparedSource = {
  readonly repoPath: string;
  readonly cleanup: (() => Promise<void>) | undefined;
};

const thisFile = fileURLToPath(import.meta.url);
const packageRoot = resolve(dirname(thisFile), '..');
const protocolSubtree = 'codex-rs/app-server-protocol/schema/typescript';
const defaultSourceUrl = 'https://github.com/openai/codex.git';
const vendorModuleName = '@linzumi/vendor/codex-app-server-protocol';
const defaultOutFile = join(
  packageRoot,
  'src/vendor/codex-app-server-protocol.d.ts'
);
const defaultManifestFile = join(
  packageRoot,
  'src/vendor/codex-app-server-protocol-manifest.json'
);

async function main(): Promise<void> {
  const args = process.argv.slice(2);

  if (args.includes('--help')) {
    printHelp();
    return;
  }

  const options = parseArgs(args);
  const source = await prepareSource(options);

  try {
    const sourceDir = join(source.repoPath, protocolSubtree);

    if (!existsSync(sourceDir)) {
      throw new Error(
        `Codex protocol schema directory not found: ${sourceDir}`
      );
    }

    const sourceCommit = await git(source.repoPath, ['rev-parse', 'HEAD']);
    if (options.check) {
      await checkVendoredOutput(options, sourceDir, sourceCommit.trim());
      process.stdout.write(
        `verified Codex app-server protocol declaration bundle\n`
      );
    } else {
      await writeDeclarationBundle(options, sourceDir, sourceCommit.trim());
      await writeManifest(options, sourceCommit.trim());
      process.stdout.write(
        `vendored Codex app-server protocol declaration bundle\n`
      );
    }
    process.stdout.write(`  source: ${options.sourceUrl}\n`);
    process.stdout.write(`  ref: ${options.sourceRef}\n`);
    process.stdout.write(`  commit: ${sourceCommit.trim()}\n`);
    process.stdout.write(`  module: ${vendorModuleName}\n`);
    process.stdout.write(`  out: ${options.outFile}\n`);
    process.stdout.write(`  manifest: ${options.manifestFile}\n`);
  } finally {
    await source.cleanup?.();
  }
}

function parseArgs(args: readonly string[]): VendorOptions {
  return {
    sourceRepo: optionalResolvedArg(args, '--codex-repo'),
    sourceUrl: argValue(args, '--source-url') ?? defaultSourceUrl,
    sourceRef: argValue(args, '--ref') ?? 'main',
    outFile: optionalResolvedArg(args, '--out-file') ?? defaultOutFile,
    manifestFile:
      optionalResolvedArg(args, '--manifest-file') ?? defaultManifestFile,
    keepTemp: args.includes('--keep-temp'),
    check: args.includes('--check'),
  };
}

async function checkVendoredOutput(
  options: VendorOptions,
  sourceDir: string,
  sourceCommit: string
): Promise<void> {
  const checkRoot = await mkdtemp(
    join(packageRoot, '.linzumi-codex-protocol-check-')
  );

  try {
    const expectedOutFile = join(checkRoot, 'codex-app-server-protocol.d.ts');
    const expectedManifestFile = join(
      checkRoot,
      'codex-app-server-protocol-manifest.json'
    );
    const expectedOptions = {
      ...options,
      outFile: expectedOutFile,
      manifestFile: expectedManifestFile,
      check: false,
    };

    await writeDeclarationBundle(expectedOptions, sourceDir, sourceCommit);
    await writeManifest(expectedOptions, sourceCommit);
    await normalizeExpectedManifestForCommittedPaths(
      expectedManifestFile,
      options.outFile
    );
    await assertSameFile(expectedOutFile, options.outFile);
    await assertSameFile(expectedManifestFile, options.manifestFile);
  } finally {
    await rm(checkRoot, { recursive: true, force: true });
  }
}

async function normalizeExpectedManifestForCommittedPaths(
  manifestPath: string,
  committedOutFile: string
): Promise<void> {
  const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as Record<
    string,
    unknown
  >;
  const normalized = {
    ...manifest,
    declarationFile: relative(packageRoot, committedOutFile),
  };

  await writeFile(
    manifestPath,
    `${JSON.stringify(normalized, null, 2)}\n`,
    'utf8'
  );
}

async function assertSameFile(
  expectedPath: string,
  actualPath: string
): Promise<void> {
  const expected = await readFile(expectedPath, 'utf8');
  const actual = await readFile(actualPath, 'utf8');

  if (expected !== actual) {
    throw new Error(
      `vendored Codex protocol drift detected: ${actualPath} does not match regenerated output`
    );
  }
}

async function prepareSource(options: VendorOptions): Promise<PreparedSource> {
  if (options.sourceRepo !== undefined) {
    return { repoPath: options.sourceRepo, cleanup: undefined };
  }

  const repoPath = await mkdtemp(join(tmpdir(), 'linzumi-openai-codex-'));
  await git(undefined, [
    'clone',
    '--filter=blob:none',
    '--sparse',
    options.sourceUrl,
    repoPath,
  ]);
  await git(repoPath, ['sparse-checkout', 'set', protocolSubtree]);
  await git(repoPath, ['fetch', '--depth', '1', 'origin', options.sourceRef]);
  await git(repoPath, ['checkout', 'FETCH_HEAD']);

  return {
    repoPath,
    cleanup: options.keepTemp
      ? undefined
      : async () => {
          await rm(repoPath, { recursive: true, force: true });
        },
  };
}

async function writeDeclarationBundle(
  options: VendorOptions,
  sourceDir: string,
  sourceCommit: string
): Promise<void> {
  const bundleRoot = await mkdtemp(
    join(tmpdir(), 'linzumi-codex-protocol-dts-')
  );

  try {
    const copiedProtocolDir = join(bundleRoot, 'protocol');
    const entryFile = join(bundleRoot, 'entry.ts');
    const rawBundleFile = join(bundleRoot, 'raw-bundle.d.ts');

    await cp(sourceDir, copiedProtocolDir, { recursive: true });
    await writeFile(
      entryFile,
      [
        "export * from './protocol/index';",
        "export * as v2 from './protocol/v2/index';",
        '',
      ].join('\n'),
      'utf8'
    );

    await command(packageRoot, [
      'pnpm',
      'exec',
      'tsc',
      '--declaration',
      '--emitDeclarationOnly',
      '--outFile',
      rawBundleFile,
      '--module',
      'amd',
      '--moduleResolution',
      'node',
      '--target',
      'ES2022',
      '--skipLibCheck',
      entryFile,
    ]);

    const rawBundle = await readFile(rawBundleFile, 'utf8');
    const bundled = rawBundle
      .replaceAll(
        'declare module "protocol/',
        `declare module "${vendorModuleName}/`
      )
      .replaceAll('from "protocol/', `from "${vendorModuleName}/`)
      .replace(
        'declare module "entry" {',
        `declare module "${vendorModuleName}" {`
      );
    const header = [
      '// GENERATED CODE! DO NOT MODIFY BY HAND!',
      '// Bundled from OpenAI Codex app-server protocol TypeScript schemas.',
      `// Source commit: ${sourceCommit}`,
      `// Module: ${vendorModuleName}`,
      '',
    ].join('\n');

    await mkdir(dirname(options.outFile), { recursive: true });
    await writeFile(options.outFile, `${header}${bundled}`, 'utf8');
    await formatGeneratedFile(options.outFile);
  } finally {
    await rm(bundleRoot, { recursive: true, force: true });
  }
}

async function formatGeneratedFile(path: string): Promise<void> {
  await command(packageRoot, [
    'pnpm',
    'exec',
    'biome',
    'format',
    '--write',
    '--no-errors-on-unmatched',
    path,
  ]);
}

async function writeManifest(
  options: VendorOptions,
  sourceCommit: string
): Promise<void> {
  const manifest = {
    source: 'openai/codex codex-rs/app-server-protocol/schema/typescript',
    sourceUrl: options.sourceUrl,
    requestedRef: options.sourceRef,
    sourceCommit,
    protocolSubtree,
    moduleName: vendorModuleName,
    declarationFile: relative(packageRoot, options.outFile),
    updateCommand:
      'pnpm --filter @linzumi/cli run vendor:codex-app-server-protocol -- --ref <openai-codex-ref>',
  };

  await mkdir(dirname(options.manifestFile), { recursive: true });
  await writeFile(
    options.manifestFile,
    `${JSON.stringify(manifest, null, 2)}\n`,
    'utf8'
  );
}

function optionalResolvedArg(
  args: readonly string[],
  name: string
): string | undefined {
  const value = argValue(args, name);
  return value === undefined ? undefined : resolve(value);
}

function argValue(args: readonly string[], name: string): string | undefined {
  const index = args.indexOf(name);

  if (index === -1) {
    return undefined;
  }

  const value = args[index + 1];

  if (value === undefined) {
    throw new Error(`${name} requires a value`);
  }

  return value;
}

function git(
  cwd: string | undefined,
  args: readonly string[]
): Promise<string> {
  return command(cwd, ['git', ...args]);
}

function command(
  cwd: string | undefined,
  args: readonly string[]
): Promise<string> {
  return new Promise((resolvePromise, reject) => {
    const [commandName, ...commandArgs] = args;

    if (commandName === undefined) {
      reject(new Error('command requires at least one argument'));
      return;
    }

    const child = spawn(commandName, commandArgs, {
      cwd,
      stdio: ['ignore', 'pipe', 'pipe'],
    });
    const stdout: string[] = [];
    const stderr: string[] = [];

    child.stdout.on('data', (chunk) => stdout.push(String(chunk)));
    child.stderr.on('data', (chunk) => stderr.push(String(chunk)));
    child.on('error', reject);
    child.on('close', (code) => {
      if (code === 0) {
        resolvePromise(stdout.join(''));
        return;
      }

      reject(
        new Error(
          `${args.join(' ')} failed with exit ${code}: ${stderr.join('')}`
        )
      );
    });
  });
}

function printHelp(): void {
  process.stdout.write(
    `Usage: pnpm run vendor:codex-app-server-protocol -- [options]\n\nOptions:\n  --codex-repo <path>       Bundle from an existing OpenAI Codex checkout\n  --ref <ref>               Git ref to vendor when cloning. Default: main\n  --source-url <url>        Codex git URL. Default: ${defaultSourceUrl}\n  --out-file <path>         Declaration bundle path. Default: src/vendor/codex-app-server-protocol.d.ts\n  --manifest-file <path>    Manifest path. Default: src/vendor/codex-app-server-protocol-manifest.json\n  --keep-temp               Keep temporary clone when cloning\n  --check                   Regenerate into a temp directory and fail if committed files differ\n\nExamples:\n  pnpm --filter @linzumi/cli run vendor:codex-app-server-protocol -- --ref main\n  pnpm --filter @linzumi/cli run vendor:codex-app-server-protocol -- --codex-repo /tmp/openai-codex\n  pnpm --filter @linzumi/cli run vendor:codex-app-server-protocol -- --codex-repo /tmp/openai-codex --check\n`
  );
}

main().catch((error) => {
  process.stderr.write(
    `${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`
  );
  process.exitCode = 1;
});
