import type { CommandContext, CommandDefinition, CommandResult } from './types.js';

const MAX_INPUT_LENGTH = 2000;

export class CommandRegistry {
  private commands = new Map<string, CommandDefinition>();
  private aliasToCommand = new Map<string, string>();

  register(def: CommandDefinition): void {
    const canonicalName = this.normalizeName(def.name);
    if (!canonicalName) {
      throw new Error('Command name is required');
    }
    if (this.commands.has(canonicalName)) {
      throw new Error(`Command already registered: ${canonicalName}`);
    }

    this.commands.set(canonicalName, { ...def, name: canonicalName });

    for (const alias of def.aliases ?? []) {
      const canonicalAlias = this.normalizeName(alias);
      if (!canonicalAlias) {
        continue;
      }
      if (canonicalAlias === canonicalName || this.aliasToCommand.has(canonicalAlias) || this.commands.has(canonicalAlias)) {
        throw new Error(`Command alias already registered: ${canonicalAlias}`);
      }
      this.aliasToCommand.set(canonicalAlias, canonicalName);
    }
  }

  get(nameOrAlias: string): CommandDefinition | undefined {
    const normalized = this.normalizeName(nameOrAlias);
    if (!normalized) {
      return undefined;
    }
    const canonicalName = this.aliasToCommand.get(normalized) ?? normalized;
    return this.commands.get(canonicalName);
  }

  list(): CommandDefinition[] {
    return Array.from(this.commands.values());
  }

  isCommand(input: string): boolean {
    return this.parse(input) !== null;
  }

  parse(input: string): { name: string; args: string[] } | null {
    const trimmed = input.trim();
    if (!trimmed.startsWith('/') || trimmed.length > MAX_INPUT_LENGTH) {
      return null;
    }

    const withoutSlash = trimmed.slice(1).trim();
    if (!withoutSlash) {
      return null;
    }

    const [rawName, ...rest] = withoutSlash.split(/\s+/);
    const name = this.normalizeName(rawName);
    if (!name) {
      return null;
    }

    return {
      name,
      args: rest,
    };
  }

  async execute(input: string, ctx: CommandContext): Promise<CommandResult> {
    const parsed = this.parse(input);
    if (!parsed) {
      return { handled: false, text: '' };
    }

    const command = this.get(parsed.name);
    if (!command) {
      return { handled: false, text: '' };
    }

    try {
      return await command.execute(parsed.args, ctx);
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown command error';
      return {
        handled: true,
        text: `Command failed: ${message}`,
      };
    }
  }

  private normalizeName(value: string): string {
    return value.trim().replace(/^\//, '').toLowerCase();
  }
}
