// lib/validators/profile-validator.mjs
// Validate model-profile YAML files (lib/profiles/*.yaml + custom voyage-profiles/*.yaml).
//
// Profile schema (v4.1, profile_version 1.0):
//   name                       : string
//   phase_models               : list-of-dicts [{phase: string, model: string}, ...]
//                                  6 entries required (brief, research, plan, execute, review, continue)
//   parallel_agents_min        : number (≥ 1)
//   parallel_agents_max        : number (≥ parallel_agents_min)
//   external_research_enabled  : boolean
//   brief_reviewer_iter_cap    : number (≥ 1)
//
// Issue codes:
//   PROFILE_MISSING_FIELD  — required top-level frontmatter field absent
//   PROFILE_INVALID_MODEL  — phase_models[N].model not in allowlist
//   PROFILE_INVALID_ENUM   — wrong-type value (e.g. external_research_enabled is string)
//   PROFILE_READ_ERROR     — file unreadable or parse-error
//   PROFILE_NOT_FOUND      — file does not exist
//
// Allowed model values: ['sonnet', 'opus']. Haiku is allowed only when
// VOYAGE_ALLOW_HAIKU=1 (per global CLAUDE.md modellvalg-prinsipp: Haiku skal
// ikke brukes som default; eksplisitt opt-in for spesielle bruksmønstre).

import { readFileSync, existsSync } from 'node:fs';
import { parseDocument } from '../util/frontmatter.mjs';
import { issue, ok, fail } from '../util/result.mjs';

export const PROFILE_REQUIRED_FIELDS = Object.freeze([
  'name',
  'phase_models',
  'parallel_agents_min',
  'parallel_agents_max',
  'external_research_enabled',
  'brief_reviewer_iter_cap',
]);

export const PROFILE_REQUIRED_PHASES = Object.freeze([
  'brief', 'research', 'plan', 'execute', 'review', 'continue',
]);

const BASE_ALLOWED_MODELS = Object.freeze(['sonnet', 'opus']);

function getAllowedModels(env = process.env) {
  if (env.VOYAGE_ALLOW_HAIKU === '1') {
    return [...BASE_ALLOWED_MODELS, 'haiku'];
  }
  return BASE_ALLOWED_MODELS;
}

export function validateProfileContent(text, opts = {}) {
  const env = opts.env || process.env;
  const allowedModels = getAllowedModels(env);

  const doc = parseDocument(text);
  if (!doc.valid) {
    return fail(issue('PROFILE_READ_ERROR', `Frontmatter parse error: ${doc.errors[0]?.message || 'unknown'}`));
  }

  const fm = doc.parsed.frontmatter || {};
  const errors = [];
  const warnings = [];

  // Required field presence
  for (const k of PROFILE_REQUIRED_FIELDS) {
    if (!(k in fm)) {
      errors.push(issue('PROFILE_MISSING_FIELD', `Required profile field missing: ${k}`));
    }
  }

  // Type checks for scalar fields
  if ('name' in fm && typeof fm.name !== 'string') {
    errors.push(issue('PROFILE_INVALID_ENUM', `name must be string (got ${typeof fm.name})`));
  }

  if ('parallel_agents_min' in fm && typeof fm.parallel_agents_min !== 'number') {
    errors.push(issue('PROFILE_INVALID_ENUM',
      `parallel_agents_min must be number (got ${typeof fm.parallel_agents_min})`));
  }

  if ('parallel_agents_max' in fm && typeof fm.parallel_agents_max !== 'number') {
    errors.push(issue('PROFILE_INVALID_ENUM',
      `parallel_agents_max must be number (got ${typeof fm.parallel_agents_max})`));
  }

  if (typeof fm.parallel_agents_min === 'number' && typeof fm.parallel_agents_max === 'number') {
    if (fm.parallel_agents_max < fm.parallel_agents_min) {
      errors.push(issue('PROFILE_INVALID_ENUM',
        `parallel_agents_max (${fm.parallel_agents_max}) < parallel_agents_min (${fm.parallel_agents_min})`));
    }
  }

  if ('external_research_enabled' in fm && typeof fm.external_research_enabled !== 'boolean') {
    errors.push(issue('PROFILE_INVALID_ENUM',
      `external_research_enabled must be boolean (got ${typeof fm.external_research_enabled})`));
  }

  if ('brief_reviewer_iter_cap' in fm && typeof fm.brief_reviewer_iter_cap !== 'number') {
    errors.push(issue('PROFILE_INVALID_ENUM',
      `brief_reviewer_iter_cap must be number (got ${typeof fm.brief_reviewer_iter_cap})`));
  }

  // phase_models validation
  if ('phase_models' in fm) {
    if (!Array.isArray(fm.phase_models)) {
      errors.push(issue('PROFILE_INVALID_ENUM', `phase_models must be a list-of-dicts (got ${typeof fm.phase_models})`));
    } else {
      const seenPhases = new Set();
      for (let i = 0; i < fm.phase_models.length; i++) {
        const entry = fm.phase_models[i];
        if (!entry || typeof entry !== 'object') {
          errors.push(issue('PROFILE_INVALID_ENUM',
            `phase_models[${i}] must be a dict {phase, model} (got ${typeof entry})`,
            undefined,
            `phase_models[${i}]`));
          continue;
        }
        if (typeof entry.phase !== 'string') {
          errors.push(issue('PROFILE_INVALID_ENUM',
            `phase_models[${i}].phase must be string`,
            undefined,
            `phase_models[${i}].phase`));
        } else {
          seenPhases.add(entry.phase);
          if (!PROFILE_REQUIRED_PHASES.includes(entry.phase)) {
            errors.push(issue('PROFILE_INVALID_ENUM',
              `phase_models[${i}].phase "${entry.phase}" not in [${PROFILE_REQUIRED_PHASES.join(', ')}]`,
              undefined,
              `phase_models[${i}].phase`));
          }
        }
        if (typeof entry.model !== 'string') {
          errors.push(issue('PROFILE_INVALID_MODEL',
            `phase_models[${i}].model must be string`,
            undefined,
            `phase_models[${i}].model`));
        } else if (!allowedModels.includes(entry.model)) {
          errors.push(issue('PROFILE_INVALID_MODEL',
            `phase_models[${i}].model "${entry.model}" not in [${allowedModels.join(', ')}]` +
              (entry.model === 'haiku' ? ' (set VOYAGE_ALLOW_HAIKU=1 to allow)' : ''),
            undefined,
            `phase_models[${i}].model`));
        }
      }
      // All 6 required phases must be present
      for (const required of PROFILE_REQUIRED_PHASES) {
        if (!seenPhases.has(required)) {
          errors.push(issue('PROFILE_MISSING_FIELD',
            `phase_models missing required phase entry: ${required}`));
        }
      }
    }
  }

  return { valid: errors.length === 0, errors, warnings, parsed: { frontmatter: fm } };
}

export function validateProfile(filePath, opts = {}) {
  if (!existsSync(filePath)) {
    return fail(issue('PROFILE_NOT_FOUND', `Profile file not found: ${filePath}`));
  }
  let text;
  try { text = readFileSync(filePath, 'utf-8'); }
  catch (e) { return fail(issue('PROFILE_READ_ERROR', `Cannot read ${filePath}: ${e.message}`)); }
  const r = validateProfileContent(text, opts);
  return { ...r, parsed: { ...(r.parsed || {}), filePath } };
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const args = process.argv.slice(2);
  const filePath = args.find(a => !a.startsWith('--'));
  if (!filePath) {
    process.stderr.write('Usage: profile-validator.mjs [--json] <profile.yaml>\n');
    process.exit(2);
  }
  const r = validateProfile(filePath);
  if (args.includes('--json')) {
    process.stdout.write(JSON.stringify({ valid: r.valid, errors: r.errors, warnings: r.warnings }, null, 2) + '\n');
  } else {
    process.stdout.write(`profile-validator: ${r.valid ? 'PASS' : 'FAIL'} ${filePath}\n`);
    for (const e of r.errors) process.stderr.write(`  ERROR  [${e.code}] ${e.message}${e.location ? ' at ' + e.location : ''}\n`);
    for (const w of r.warnings) process.stderr.write(`  WARN   [${w.code}] ${w.message}\n`);
  }
  process.exit(r.valid ? 0 : 1);
}
