import { Command } from 'commander';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { loadPersona, listPersonas } from '../persona/loader';
import { generateAgentDefinition, type AgentDefinition } from '../agent-definitions/generator';
import { renderClaudeCodeAgent } from '../agent-definitions/render-claude-code';
import { renderGeminiAgent } from '../agent-definitions/render-gemini-cli';
import { renderCursorAgent } from '../agent-definitions/render-cursor';
import { computeSyncPlan, applySyncPlan } from '../slash-commands/sync';
import { resolvePersonasDir, resolveSkillsDir } from '../utils/paths';
import { CLIError, ExitCode, handleError } from '../utils/errors';
import type { Platform } from '../slash-commands/types';
import { VALID_PLATFORMS } from '../slash-commands/types';

export interface GenerateAgentDefsOptions {
  platforms: Platform[];
  global: boolean;
  output?: string;
  dryRun: boolean;
}

export interface GenerateAgentDefsResult {
  platform: string;
  added: string[];
  updated: string[];
  removed: string[];
  unchanged: string[];
  outputDir: string;
}

function resolveOutputDir(platform: Platform, opts: { global: boolean; output?: string }): string {
  if (opts.output) {
    return path.join(opts.output, platform);
  }
  if (opts.global) {
    const home = os.homedir();
    if (platform === 'claude-code') return path.join(home, '.claude', 'agents');
    if (platform === 'gemini-cli') return path.join(home, '.gemini', 'agents');
    if (platform === 'cursor') return path.join(home, '.cursor', 'agents');
    return path.join(home, '.codex', 'agents');
  }
  return path.join('agents', 'agents', platform);
}

function loadSkillContent(skillName: string): string | null {
  const skillsDir = resolveSkillsDir();
  const skillMdPath = path.join(skillsDir, skillName, 'SKILL.md');
  if (!fs.existsSync(skillMdPath)) return null;
  return fs.readFileSync(skillMdPath, 'utf-8');
}

function getRenderer(platform: Platform): (def: AgentDefinition) => string {
  if (platform === 'claude-code') return renderClaudeCodeAgent;
  if (platform === 'cursor') return renderCursorAgent;
  // gemini-cli and codex (Codex has no plugin agents field; this branch is
  // reachable only via gemini-cli today).
  return renderGeminiAgent;
}

export function generateAgentDefinitions(
  opts: GenerateAgentDefsOptions
): GenerateAgentDefsResult[] {
  const personasDir = resolvePersonasDir();
  const personaList = listPersonas(personasDir);
  if (!personaList.ok) return [];

  // Load all personas
  const personas = personaList.value
    .map((meta) => loadPersona(meta.filePath))
    .filter((r) => r.ok)
    .map((r) => r.value);

  // Load skill contents for all referenced skills
  const allSkillNames = new Set(personas.flatMap((p) => p.skills));
  const skillContents = new Map<string, string>();
  for (const skillName of allSkillNames) {
    const content = loadSkillContent(skillName);
    if (content) {
      skillContents.set(skillName, content);
    }
  }

  // Generate definitions
  const definitions = personas.map((p) => generateAgentDefinition(p, skillContents));

  const results: GenerateAgentDefsResult[] = [];

  for (const platform of opts.platforms) {
    const outputDir = resolveOutputDir(platform, opts);
    const renderer = getRenderer(platform);

    const rendered = new Map<string, string>();
    for (const def of definitions) {
      const filename = `${def.name}.md`;
      rendered.set(filename, renderer(def));
    }

    const plan = computeSyncPlan(outputDir, rendered);

    if (!opts.dryRun) {
      applySyncPlan(outputDir, rendered, plan, false);
    }

    results.push({
      platform,
      added: plan.added,
      updated: plan.updated,
      removed: plan.removed,
      unchanged: plan.unchanged,
      outputDir,
    });
  }

  return results;
}

function parsePlatforms(raw: string): Platform[] {
  const platforms = raw.split(',').map((p: string) => p.trim());
  for (const p of platforms) {
    if (!VALID_PLATFORMS.includes(p as Platform)) {
      throw new CLIError(
        `Invalid platform "${p}". Valid platforms: ${VALID_PLATFORMS.join(', ')}`,
        ExitCode.VALIDATION_FAILED
      );
    }
  }
  return platforms as Platform[];
}

function printAgentDefsResult(result: GenerateAgentDefsResult, dryRun: boolean): void {
  console.log(`\n${result.platform} → ${result.outputDir}`);
  if (result.added.length > 0) {
    console.log(`  + ${result.added.length} new: ${result.added.join(', ')}`);
  }
  if (result.updated.length > 0) {
    console.log(`  ~ ${result.updated.length} updated: ${result.updated.join(', ')}`);
  }
  if (result.removed.length > 0) {
    console.log(`  - ${result.removed.length} removed: ${result.removed.join(', ')}`);
  }
  if (result.unchanged.length > 0) {
    console.log(`  = ${result.unchanged.length} unchanged`);
  }
  if (dryRun) {
    console.log('  (dry run — no files written)');
  }
}

export function createGenerateAgentDefinitionsCommand(): Command {
  return new Command('generate-agent-definitions')
    .description(
      'Generate agent definition files from personas for Claude Code, Gemini CLI, and Cursor'
    )
    .option('--platforms <list>', 'Target platforms (comma-separated)', 'claude-code,gemini-cli')
    .option('--global', 'Write to global agent directories', false)
    .option('--output <dir>', 'Custom output directory')
    .option('--dry-run', 'Show what would change without writing', false)
    .action(async (opts, cmd) => {
      const globalOpts = cmd.optsWithGlobals();
      try {
        const platforms = parsePlatforms(opts.platforms);
        const results = generateAgentDefinitions({
          platforms,
          global: opts.global,
          output: opts.output,
          dryRun: opts.dryRun,
        });

        if (globalOpts.json) {
          console.log(JSON.stringify(results, null, 2));
          return;
        }

        for (const result of results) {
          printAgentDefsResult(result, opts.dryRun);
        }
      } catch (error) {
        handleError(error);
      }
    });
}
