import { execFileSync } from 'node:child_process';
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const scriptDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(scriptDir, '../..');

function quote(value) {
  return `'${String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\r/g, '\\r').replace(/\n/g, '\\n')}'`;
}

function propertyKey(value) {
  const text = String(value);
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(text) ? text : quote(text);
}

function toInlineTs(value) {
  if (value === null) return 'null';
  if (typeof value === 'string') return quote(value);
  if (typeof value === 'number' || typeof value === 'boolean') return JSON.stringify(value);
  if (Array.isArray(value)) {
    const entries = value.map(toInlineTs);
    if (entries.some((entry) => entry === null)) return null;
    return `[${entries.join(', ')}]`;
  }
  if (value && typeof value === 'object') {
    const entries = Object.entries(value).filter(([, entry]) => entry !== undefined);
    const rendered = entries.map(([key, entry]) => {
      const inlineEntry = toInlineTs(entry);
      return inlineEntry ? `${key}: ${inlineEntry}` : null;
    });
    if (rendered.some((entry) => entry === null)) return null;
    return `{ ${rendered.join(', ')} }`;
  }
  return null;
}

function toTs(value, indent = 0) {
  const inline = toInlineTs(value);
  if (inline && inline.length <= 120) return inline;

  const pad = ' '.repeat(indent);
  const childPad = ' '.repeat(indent + 2);
  if (Array.isArray(value)) {
    if (value.length === 0) return '[]';
    return `[\n${value.map((entry) => `${childPad}${toTs(entry, indent + 2)}`).join(',\n')},\n${pad}]`;
  }
  if (value && typeof value === 'object') {
    const entries = Object.entries(value).filter(([, entry]) => entry !== undefined);
    if (entries.length === 0) return '{}';
    return `{\n${entries.map(([key, entry]) => `${childPad}${key}: ${toTs(entry, indent + 2)}`).join(',\n')},\n${pad}}`;
  }
  if (typeof value === 'string') return quote(value);
  return JSON.stringify(value);
}

function upperSnake(value) {
  return value
    .replace(/[^A-Za-z0-9]+/g, '_')
    .replace(/^_+|_+$/g, '')
    .toUpperCase();
}

function pascalCase(value) {
  return value
    .split(/[^A-Za-z0-9]+/)
    .filter(Boolean)
    .map((part) => `${part[0].toUpperCase()}${part.slice(1)}`)
    .join('');
}

export function buildGeneratedSopDefinitionsSource(runtimeDefinitions) {
  const sortedDefinitions = [...runtimeDefinitions].sort((a, b) => a.id.localeCompare(b.id));
  const lines = [
    '/**',
    ' * Auto-generated by scripts/sop-definitions.mjs.',
    ' * Do not edit by hand. Run: pnpm gen:sop-definitions',
    ' */',
    '',
    `export const SOP_DEFINITION_IDS = [${sortedDefinitions.map((definition) => quote(definition.id)).join(', ')}] as const;`,
    'export type SopDefinitionId = (typeof SOP_DEFINITION_IDS)[number];',
    '',
  ];

  for (const definition of sortedDefinitions) {
    const constPrefix = upperSnake(definition.id);
    const typePrefix = pascalCase(definition.id);
    lines.push(
      `export const ${constPrefix}_SOP_STAGE_IDS = [${definition.stages
        .map((stage) => quote(stage.id))
        .join(', ')}] as const;`,
      `export type ${typePrefix}SopStageId = (typeof ${constPrefix}_SOP_STAGE_IDS)[number];`,
      `export const ${constPrefix}_SOP_DEFINITION = ${toTs(definition)} as const;`,
      '',
    );
  }

  lines.push(
    'export const SOP_DEFINITIONS = {',
    ...sortedDefinitions.map(
      (definition) => `  ${propertyKey(definition.id)}: ${upperSnake(definition.id)}_SOP_DEFINITION,`,
    ),
    '} as const;',
    '',
    'export type SopDefinition = (typeof SOP_DEFINITIONS)[SopDefinitionId];',
    `export const DEFAULT_SOP_DEFINITION_ID = SOP_DEFINITION_IDS[0];`,
    `export type RuntimeSopStageId = ${sortedDefinitions
      .map((definition) => `${pascalCase(definition.id)}SopStageId`)
      .join(' | ')};`,
    '',
    'export type ResolvedWorkflowSopSkill = {',
    "  readonly source: 'definition' | 'override';",
    '  readonly skill: string;',
    '};',
    '',
    'export function isSopDefinitionId(value: string): value is SopDefinitionId {',
    '  return (SOP_DEFINITION_IDS as readonly string[]).includes(value);',
    '}',
    '',
    'export function normalizeSopDefinitionId(value: string | null | undefined): SopDefinitionId {',
    '  return value && isSopDefinitionId(value) ? value : DEFAULT_SOP_DEFINITION_ID;',
    '}',
    '',
    'export function getSopDefinition(id: string | null | undefined): SopDefinition {',
    '  return SOP_DEFINITIONS[normalizeSopDefinitionId(id)];',
    '}',
    '',
    'export function resolveWorkflowSopSkill(sop: {',
    '  readonly sopDefinitionId?: string | null;',
    '  readonly stage: RuntimeSopStageId;',
    '  readonly nextSkill?: string | null;',
    '}): ResolvedWorkflowSopSkill {',
    '  const definition = getSopDefinition(sop.sopDefinitionId);',
    '  const stage = definition.stages.find((entry) => entry.id === sop.stage);',
    '  if (!stage) {',
    '    throw new Error(`Unknown SOP stage "${sop.stage}" for definition "${definition.id}"`);',
    '  }',
    '  if (sop.nextSkill) {',
    "    return { source: 'override', skill: sop.nextSkill };",
    '  }',
    "  return { source: 'definition', skill: stage.suggestedSkill };",
    '}',
    '',
  );

  return formatGeneratedSource(`${lines.join('\n')}\n`);
}

function formatGeneratedSource(source) {
  const tempDir = mkdtempSync(join(tmpdir(), 'cat-cafe-sop-definitions-'));
  const tempFile = join(tempDir, 'sop-definition.generated.ts');
  try {
    writeFileSync(tempFile, source, 'utf-8');
    execFileSync(
      'pnpm',
      ['biome', 'format', '--write', '--config-path', 'biome.json', '--vcs-use-ignore-file=false', tempFile],
      {
        cwd: repoRoot,
        stdio: 'pipe',
      },
    );
    return readFileSync(tempFile, 'utf-8');
  } finally {
    rmSync(tempDir, { force: true, recursive: true });
  }
}
