/**
 * Build Effect - Runs npm build and returns structured error information
 *
 * This is the foundation for self-healing builds. It enables:
 * - (effect build.run) from Lisp to trigger npm build
 * - Structured error parsing (file, line, col, code, message)
 * - Integration with fixpoint iteration for auto-fixing
 */

import { spawn } from 'child_process';

/**
 * Structured result from running a build
 */
export interface BuildResult {
  ok: boolean;
  errors: BuildError[];
  warnings: BuildError[];
  duration_ms: number;
  raw_output: string;
}

/**
 * Structured error from TypeScript/build output
 */
export interface BuildError {
  file: string;
  line: number;
  col: number;
  severity: 'error' | 'warning';
  code: string;      // e.g., "TS2345"
  message: string;
  raw: string;       // original line
}

/**
 * Parse TypeScript compiler errors from tsc/esbuild output
 *
 * Handles two common formats:
 * - Format 1: src/file.ts(10,5): error TS2345: message
 * - Format 2: src/file.ts:10:5 - error TS2345: message
 */
export function parseTscErrors(output: string): BuildError[] {
  const errors: BuildError[] = [];

  // Pattern: src/file.ts(10,5): error TS2345: message
  // Or: src/file.ts:10:5 - error TS2345: message
  const pattern = /^(.+?)(?:\((\d+),(\d+)\)|:(\d+):(\d+))\s*[-:]\s*(error|warning)\s+(TS\d+):\s*(.+)$/gm;

  let match;
  while ((match = pattern.exec(output)) !== null) {
    errors.push({
      file: match[1],
      line: parseInt(match[2] || match[4], 10),
      col: parseInt(match[3] || match[5], 10),
      severity: match[6] as 'error' | 'warning',
      code: match[7],
      message: match[8],
      raw: match[0]
    });
  }
  return errors;
}

/**
 * Run npm build and return structured results
 *
 * @param cwd - Working directory (defaults to process.cwd())
 * @returns BuildResult with ok status, errors, warnings, and timing
 */
export async function runBuild(cwd: string = process.cwd()): Promise<BuildResult> {
  const start = Date.now();

  return new Promise((resolve) => {
    const proc = spawn('npm', ['run', 'build'], {
      cwd,
      shell: true,
      stdio: ['ignore', 'pipe', 'pipe']
    });

    let stdout = '';
    let stderr = '';

    proc.stdout.on('data', (data) => { stdout += data.toString(); });
    proc.stderr.on('data', (data) => { stderr += data.toString(); });

    proc.on('close', (code) => {
      const combined = stdout + stderr;
      const allErrors = parseTscErrors(combined);

      resolve({
        ok: code === 0,
        errors: allErrors.filter(e => e.severity === 'error'),
        warnings: allErrors.filter(e => e.severity === 'warning'),
        duration_ms: Date.now() - start,
        raw_output: combined
      });
    });

    proc.on('error', (err) => {
      resolve({
        ok: false,
        errors: [{
          file: '',
          line: 0,
          col: 0,
          severity: 'error',
          code: 'SPAWN_ERROR',
          message: err.message,
          raw: err.message
        }],
        warnings: [],
        duration_ms: Date.now() - start,
        raw_output: err.message
      });
    });
  });
}

/**
 * Convert BuildResult to Val for returning to Lisp
 */
export function buildResultToVal(result: BuildResult): {
  tag: 'Map';
  entries: Array<[{ tag: 'Str'; s: string }, unknown]>;
} {
  const errorToVal = (e: BuildError) => ({
    tag: 'Map' as const,
    entries: [
      [{ tag: 'Str' as const, s: 'file' }, { tag: 'Str' as const, s: e.file }],
      [{ tag: 'Str' as const, s: 'line' }, { tag: 'Num' as const, n: e.line }],
      [{ tag: 'Str' as const, s: 'col' }, { tag: 'Num' as const, n: e.col }],
      [{ tag: 'Str' as const, s: 'severity' }, { tag: 'Sym' as const, name: e.severity }],
      [{ tag: 'Str' as const, s: 'code' }, { tag: 'Str' as const, s: e.code }],
      [{ tag: 'Str' as const, s: 'message' }, { tag: 'Str' as const, s: e.message }],
    ] as Array<[{ tag: 'Str'; s: string }, unknown]>
  });

  const errorsVec = {
    tag: 'Vector' as const,
    items: result.errors.map(errorToVal)
  };

  const warningsVec = {
    tag: 'Vector' as const,
    items: result.warnings.map(errorToVal)
  };

  return {
    tag: 'Map',
    entries: [
      [{ tag: 'Str', s: 'ok' }, { tag: 'Bool', b: result.ok }],
      [{ tag: 'Str', s: 'errors' }, errorsVec],
      [{ tag: 'Str', s: 'warnings' }, warningsVec],
      [{ tag: 'Str', s: 'duration_ms' }, { tag: 'Num', n: result.duration_ms }],
      [{ tag: 'Str', s: 'raw_output' }, { tag: 'Str', s: result.raw_output }],
    ]
  };
}
