import type { Command } from 'commander';
import { resolveConfigPath } from '../config/paths.js';
import { resolveDefaultAgentWorkspaceDir } from '../config/workspace-defaults.js';

export interface CLIContext {
  configPath: string;
  workspacePath: string;
  isVerbose: boolean;
  argv: string[];
}

export function createDefaultContext(
  argv: string[] = process.argv,
  opts?: { config?: string; workspace?: string; verbose?: boolean }
): CLIContext {
  return {
    configPath: opts?.config || process.env.XOPC_CONFIG || resolveConfigPath(),
    workspacePath: opts?.workspace || process.env.XOPC_WORKSPACE || resolveDefaultAgentWorkspaceDir(),
    isVerbose: (opts?.verbose ?? (argv.includes('--verbose') || argv.includes('-v'))),
    argv,
  };
}

export interface CommandMetadata {
  category?: 'setup' | 'runtime' | 'maintenance' | 'utility';
  hidden?: boolean;
  unstable?: boolean;
  examples?: string[];
}

export interface CommandDefinition {
  id: string;
  name: string;
  description: string;
  factory: (ctx: CLIContext, getCtx?: () => CLIContext) => Command;
  metadata?: CommandMetadata;
}

export class CommandRegistry {
  private commands: CommandDefinition[] = [];
  private initialized = false;
  private suppressLateRegistrationWarnings = false;

  /**
   * Temporarily silence the "registered after initialization" warning. Used by
   * commands that need to bulk-load other command modules to harvest their
   * side effects during initialization.
   */
  setSuppressLateRegistrationWarnings(value: boolean): void {
    this.suppressLateRegistrationWarnings = value;
  }

  register(def: CommandDefinition): void {
    if (this.initialized && !this.suppressLateRegistrationWarnings) {
      console.warn(`Warning: Command "${def.id}" registered after initialization`);
    }

    const existing = this.commands.find(c => c.id === def.id);
    if (existing) {
      throw new Error(`Command with id "${def.id}" already registered (name: ${existing.name})`);
    }

    const existingName = this.commands.find(c => c.name === def.name);
    if (existingName) {
      throw new Error(`Command with name "${def.name}" already registered (id: ${existingName.id})`);
    }

    this.commands.push(def);
  }

  registerAll(defs: CommandDefinition[]): void {
    for (const def of defs) {
      this.register(def);
    }
  }

  getCommands(): ReadonlyArray<CommandDefinition> {
    return this.commands;
  }

  getCommandsByCategory(category: CommandMetadata['category']): ReadonlyArray<CommandDefinition> {
    return this.commands.filter(c => c.metadata?.category === category);
  }

  findById(id: string): CommandDefinition | undefined {
    return this.commands.find(c => c.id === id);
  }

  findByName(name: string): CommandDefinition | undefined {
    return this.commands.find(c => c.name === name);
  }

  install(program: Command, ctx: CLIContext, getCtx?: () => CLIContext): void {
    this.initialized = true;

    const categoryOrder = { setup: 0, runtime: 1, maintenance: 2, utility: 3 };
    const sorted = [...this.commands].sort((a, b) => {
      const orderA = categoryOrder[a.metadata?.category ?? 'utility'];
      const orderB = categoryOrder[b.metadata?.category ?? 'utility'];
      return orderA - orderB;
    });

    for (const def of sorted) {
      if (def.metadata?.hidden) continue;

      try {
        const cmd = def.factory(ctx, getCtx);
        program.addCommand(cmd);
      } catch (error) {
        console.error(`Failed to register command "${def.id}":`, error);
      }
    }
  }

  installOne(
    program: Command,
    name: string,
    ctx: CLIContext,
    getCtx?: () => CLIContext,
  ): boolean {
    this.initialized = true;
    const def = this.findByName(name);
    if (!def || def.metadata?.hidden) return false;
    try {
      const cmd = def.factory(ctx, getCtx);
      program.addCommand(cmd);
      return true;
    } catch (error) {
      console.error(`Failed to register command "${def.id}":`, error);
      return false;
    }
  }

  getStats(): { total: number; byCategory: Record<string, number> } {
    const byCategory: Record<string, number> = {};
    for (const cmd of this.commands) {
      const cat = cmd.metadata?.category ?? 'uncategorized';
      byCategory[cat] = (byCategory[cat] || 0) + 1;
    }
    return { total: this.commands.length, byCategory };
  }
}

export const registry = new CommandRegistry();

export function register(def: CommandDefinition): void {
  registry.register(def);
}

export function formatExamples(examples: string[]): string {
  if (examples.length === 0) return '';
  return '\nExamples:\n' + examples.map(e => `  $ ${e}`).join('\n');
}
