// tests/lib/agent-frontmatter.test.mjs
// Pin the agent-frontmatter contract from Steps 1-3 of plan-v2:
//   every agents/*.md MUST declare:
//     - model:   (one of opus | sonnet | haiku)
//     - tools:   (allowlist) OR disallowedTools: (denylist), at least one
// Orchestrator agents (planning/research/review) MUST be model: opus and
// MUST include the `Agent` tool in their tools allowlist (they spawn the swarm).
//
// When this test fails, fix the agent file — do NOT relax the assertion to
// hide drift. The contract is what /ultra*-local commands rely on for
// disciplined model selection + tool scoping.

import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync, readdirSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = join(HERE, '..', '..');
const AGENTS_DIR = join(ROOT, 'agents');

const ORCHESTRATORS = new Set([
  'planning-orchestrator.md',
  'research-orchestrator.md',
  'review-orchestrator.md',
]);

const ALLOWED_MODELS = new Set(['opus', 'sonnet', 'haiku']);

function read(rel) {
  return readFileSync(join(ROOT, rel), 'utf-8');
}

function extractFrontmatter(text) {
  const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
  return m ? m[1] : null;
}

function hasTopLevelKey(fm, key) {
  return new RegExp(`^${key}\\s*:`, 'm').test(fm);
}

function getTopLevelValue(fm, key) {
  const m = fm.match(new RegExp(`^${key}\\s*:\\s*(.+?)\\s*$`, 'm'));
  return m ? m[1] : null;
}

const agentFiles = readdirSync(AGENTS_DIR).filter(f => f.endsWith('.md'));

test('every agents/*.md declares a model: field', () => {
  assert.ok(agentFiles.length > 0, 'No agent files found under agents/');
  for (const f of agentFiles) {
    const fm = extractFrontmatter(read(`agents/${f}`));
    assert.ok(fm, `agents/${f}: missing YAML frontmatter block`);
    assert.ok(
      hasTopLevelKey(fm, 'model'),
      `agents/${f}: required \`model:\` field missing from frontmatter`,
    );
    const value = getTopLevelValue(fm, 'model');
    assert.ok(
      value && ALLOWED_MODELS.has(value),
      `agents/${f}: model: "${value}" must be one of ${[...ALLOWED_MODELS].join(' | ')}`,
    );
  }
});

test('every agents/*.md declares tools: or disallowedTools:', () => {
  for (const f of agentFiles) {
    const fm = extractFrontmatter(read(`agents/${f}`));
    assert.ok(fm, `agents/${f}: missing YAML frontmatter block`);
    assert.ok(
      hasTopLevelKey(fm, 'tools') || hasTopLevelKey(fm, 'disallowedTools'),
      `agents/${f}: required \`tools:\` (allowlist) or \`disallowedTools:\` (denylist) field missing`,
    );
  }
});

test('every agents/*.md frontmatter name matches its filename', () => {
  for (const f of agentFiles) {
    const fm = extractFrontmatter(read(`agents/${f}`));
    assert.ok(fm, `agents/${f}: missing frontmatter`);
    const expected = f.replace(/\.md$/, '');
    const value = getTopLevelValue(fm, 'name');
    assert.equal(
      value,
      expected,
      `agents/${f}: frontmatter name="${value}" should match filename "${expected}"`,
    );
  }
});

test('orchestrator agents are model: opus and include the Agent tool', () => {
  for (const f of ORCHESTRATORS) {
    const path = `agents/${f}`;
    const fm = extractFrontmatter(read(path));
    assert.ok(fm, `${path}: missing frontmatter`);
    const model = getTopLevelValue(fm, 'model');
    assert.equal(
      model,
      'opus',
      `${path}: orchestrator must be model: opus (drives multi-agent swarm reasoning) — got "${model}"`,
    );
    const tools = getTopLevelValue(fm, 'tools');
    assert.ok(
      tools && /\bAgent\b/.test(tools),
      `${path}: orchestrator tools: must include "Agent" so it can spawn the swarm — got ${tools}`,
    );
  }
});

test('non-orchestrator agents do NOT include the Agent tool (no recursive swarming)', () => {
  for (const f of agentFiles) {
    if (ORCHESTRATORS.has(f)) continue;
    const fm = extractFrontmatter(read(`agents/${f}`));
    assert.ok(fm, `agents/${f}: missing frontmatter`);
    const tools = getTopLevelValue(fm, 'tools');
    if (tools === null) continue; // disallowedTools-only agent — fine
    assert.ok(
      !/\bAgent\b/.test(tools),
      `agents/${f}: non-orchestrator must NOT include the Agent tool ` +
      `(only orchestrators spawn sub-agents) — got tools: ${tools}`,
    );
  }
});
