/**
 * Integration tests for TUI command parsing + registry (P8-019)
 *
 * Covers:
 * - parseSlashCommand() + commandRegistry.find() round-trip for all aliases
 * - /help, /stop, /cost, /status resolve to 'local' execution
 * - Unknown commands return null from find()
 * - Alias resolution: /h → help, /m → model, /n → new, etc.
 * - filterCommands prefix filtering
 */
import { describe, it, expect, beforeEach } from 'vitest';
import { parseSlashCommand } from './parse.js';
import { CommandRegistry } from './registry.js';
import type { CommandDef } from '@mosaicstack/types';

// ─── Parse + Registry Round-trip ─────────────────────────────────────────────

describe('parseSlashCommand + CommandRegistry — integration', () => {
  let registry: CommandRegistry;

  // Gateway-style commands to simulate a live manifest
  const gatewayCommands: CommandDef[] = [
    {
      name: 'model',
      description: 'Switch the active model',
      aliases: ['m'],
      args: [{ name: 'model-name', type: 'string', optional: false, description: 'Model name' }],
      scope: 'core',
      execution: 'socket',
      available: true,
    },
    {
      name: 'thinking',
      description: 'Set thinking level',
      aliases: ['t'],
      args: [
        {
          name: 'level',
          type: 'enum',
          optional: false,
          values: ['none', 'low', 'medium', 'high', 'auto'],
          description: 'Thinking level',
        },
      ],
      scope: 'core',
      execution: 'socket',
      available: true,
    },
    {
      name: 'new',
      description: 'Start a new conversation',
      aliases: ['n'],
      scope: 'core',
      execution: 'socket',
      available: true,
    },
    {
      name: 'agent',
      description: 'Switch or list available agents',
      aliases: ['a'],
      args: [{ name: 'args', type: 'string', optional: true, description: 'list or <agent-id>' }],
      scope: 'agent',
      execution: 'socket',
      available: true,
    },
    {
      name: 'preferences',
      description: 'View or set user preferences',
      aliases: ['pref'],
      args: [
        {
          name: 'action',
          type: 'enum',
          optional: true,
          values: ['show', 'set', 'reset'],
          description: 'Action',
        },
      ],
      scope: 'core',
      execution: 'rest',
      available: true,
    },
    {
      name: 'gc',
      description: 'Trigger garbage collection sweep',
      aliases: [],
      scope: 'core',
      execution: 'socket',
      available: true,
    },
    {
      name: 'mission',
      description: 'View or set active mission',
      aliases: [],
      args: [{ name: 'args', type: 'string', optional: true, description: 'status | set <id>' }],
      scope: 'agent',
      execution: 'socket',
      available: true,
    },
  ];

  beforeEach(() => {
    registry = new CommandRegistry();
    registry.updateManifest({ version: 1, commands: gatewayCommands, skills: [] });
  });

  // ── parseSlashCommand tests ──

  it('returns null for non-slash input', () => {
    expect(parseSlashCommand('hello world')).toBeNull();
    expect(parseSlashCommand('')).toBeNull();
    expect(parseSlashCommand('model')).toBeNull();
  });

  it('parses "/model claude-3-opus" → command=model args=claude-3-opus', () => {
    const parsed = parseSlashCommand('/model claude-3-opus');
    expect(parsed).not.toBeNull();
    expect(parsed!.command).toBe('model');
    expect(parsed!.args).toBe('claude-3-opus');
    expect(parsed!.raw).toBe('/model claude-3-opus');
  });

  it('parses "/gc" with no args → command=gc args=null', () => {
    const parsed = parseSlashCommand('/gc');
    expect(parsed).not.toBeNull();
    expect(parsed!.command).toBe('gc');
    expect(parsed!.args).toBeNull();
  });

  it('parses "/system you are a helpful assistant" → args contains full text', () => {
    const parsed = parseSlashCommand('/system you are a helpful assistant');
    expect(parsed!.command).toBe('system');
    expect(parsed!.args).toBe('you are a helpful assistant');
  });

  it('parses "/help" → command=help args=null', () => {
    const parsed = parseSlashCommand('/help');
    expect(parsed!.command).toBe('help');
    expect(parsed!.args).toBeNull();
  });

  // ── Round-trip: parse then find ──

  it('round-trip: /m → resolves to "model" command via alias', () => {
    const parsed = parseSlashCommand('/m claude-3-haiku');
    expect(parsed).not.toBeNull();
    const cmd = registry.find(parsed!.command);
    expect(cmd).not.toBeNull();
    // /m → model (alias map in registry)
    expect(cmd!.name === 'model' || cmd!.aliases.includes('m')).toBe(true);
  });

  it('round-trip: /h → resolves to "help" (local command)', () => {
    const parsed = parseSlashCommand('/h');
    expect(parsed).not.toBeNull();
    const cmd = registry.find(parsed!.command);
    expect(cmd).not.toBeNull();
    expect(cmd!.name === 'help' || cmd!.aliases.includes('h')).toBe(true);
  });

  it('round-trip: /n → resolves to "new" via gateway manifest', () => {
    const parsed = parseSlashCommand('/n');
    expect(parsed).not.toBeNull();
    const cmd = registry.find(parsed!.command);
    expect(cmd).not.toBeNull();
    expect(cmd!.name === 'new' || cmd!.aliases.includes('n')).toBe(true);
  });

  it('round-trip: /a → resolves to "agent" via gateway manifest', () => {
    const parsed = parseSlashCommand('/a list');
    expect(parsed).not.toBeNull();
    const cmd = registry.find(parsed!.command);
    expect(cmd).not.toBeNull();
    expect(cmd!.name === 'agent' || cmd!.aliases.includes('a')).toBe(true);
  });

  it('round-trip: /pref → resolves to "preferences" via alias', () => {
    const parsed = parseSlashCommand('/pref show');
    expect(parsed).not.toBeNull();
    const cmd = registry.find(parsed!.command);
    expect(cmd).not.toBeNull();
    expect(cmd!.name === 'preferences' || cmd!.aliases.includes('pref')).toBe(true);
  });

  it('round-trip: /t → resolves to "thinking" via alias', () => {
    const parsed = parseSlashCommand('/t high');
    expect(parsed).not.toBeNull();
    const cmd = registry.find(parsed!.command);
    expect(cmd).not.toBeNull();
    expect(cmd!.name === 'thinking' || cmd!.aliases.includes('t')).toBe(true);
  });

  // ── Local commands resolve to 'local' execution ──

  it('/help resolves to local execution', () => {
    const cmd = registry.find('help');
    expect(cmd).not.toBeNull();
    expect(cmd!.execution).toBe('local');
  });

  it('/stop resolves to local execution', () => {
    const cmd = registry.find('stop');
    expect(cmd).not.toBeNull();
    expect(cmd!.execution).toBe('local');
  });

  it('/cost resolves to local execution', () => {
    const cmd = registry.find('cost');
    expect(cmd).not.toBeNull();
    expect(cmd!.execution).toBe('local');
  });

  it('/status resolves to local execution (TUI local override)', () => {
    const cmd = registry.find('status');
    expect(cmd).not.toBeNull();
    // status is 'local' in the TUI registry (local takes precedence over gateway)
    expect(cmd!.execution).toBe('local');
  });

  // ── Unknown commands return null ──

  it('find() returns null for unknown command', () => {
    expect(registry.find('nonexistent')).toBeNull();
    expect(registry.find('xyz')).toBeNull();
    expect(registry.find('')).toBeNull();
  });

  it('find() returns null when no gateway manifest and command not local', () => {
    const emptyRegistry = new CommandRegistry();
    expect(emptyRegistry.find('model')).toBeNull();
    expect(emptyRegistry.find('gc')).toBeNull();
  });

  // ── getAll returns combined local + gateway ──

  it('getAll() includes both local and gateway commands', () => {
    const all = registry.getAll();
    const names = all.map((c) => c.name);
    // Local commands
    expect(names).toContain('help');
    expect(names).toContain('stop');
    expect(names).toContain('cost');
    expect(names).toContain('status');
    // Gateway commands
    expect(names).toContain('model');
    expect(names).toContain('gc');
  });

  it('getLocalCommands() returns only local commands', () => {
    const local = registry.getLocalCommands();
    expect(local.every((c) => c.execution === 'local')).toBe(true);
    expect(local.some((c) => c.name === 'help')).toBe(true);
    expect(local.some((c) => c.name === 'stop')).toBe(true);
  });
});

// ─── filterCommands (autocomplete) ────────────────────────────────────────────

describe('filterCommands (from CommandAutocomplete)', () => {
  // Import inline since filterCommands is not exported — replicate the logic here
  function filterCommands(commands: CommandDef[], query: string): CommandDef[] {
    if (!query) return commands;
    const q = query.toLowerCase();
    return commands.filter(
      (c) =>
        c.name.includes(q) ||
        c.aliases.some((a) => a.includes(q)) ||
        c.description.toLowerCase().includes(q),
    );
  }

  const commands: CommandDef[] = [
    {
      name: 'model',
      description: 'Switch the active model',
      aliases: ['m'],
      scope: 'core',
      execution: 'socket',
      available: true,
    },
    {
      name: 'mission',
      description: 'View or set active mission',
      aliases: [],
      scope: 'agent',
      execution: 'socket',
      available: true,
    },
    {
      name: 'help',
      description: 'Show available commands',
      aliases: ['h'],
      scope: 'core',
      execution: 'local',
      available: true,
    },
    {
      name: 'gc',
      description: 'Trigger garbage collection sweep',
      aliases: [],
      scope: 'core',
      execution: 'socket',
      available: true,
    },
  ];

  it('returns all commands when query is empty', () => {
    expect(filterCommands(commands, '')).toHaveLength(commands.length);
  });

  it('filters by name prefix "mi" → mission only (not model, as "mi" not in model name or aliases)', () => {
    const result = filterCommands(commands, 'mi');
    const names = result.map((c) => c.name);
    expect(names).toContain('mission');
    expect(names).not.toContain('gc');
  });

  it('filters by name prefix "mo" → model only', () => {
    const result = filterCommands(commands, 'mo');
    const names = result.map((c) => c.name);
    expect(names).toContain('model');
    expect(names).not.toContain('mission');
    expect(names).not.toContain('gc');
  });

  it('filters by exact name "gc" → gc only', () => {
    const result = filterCommands(commands, 'gc');
    expect(result).toHaveLength(1);
    expect(result[0]!.name).toBe('gc');
  });

  it('filters by alias "h" → help', () => {
    const result = filterCommands(commands, 'h');
    const names = result.map((c) => c.name);
    expect(names).toContain('help');
  });

  it('filters by description keyword "switch" → model', () => {
    const result = filterCommands(commands, 'switch');
    const names = result.map((c) => c.name);
    expect(names).toContain('model');
  });

  it('returns empty array when no commands match', () => {
    const result = filterCommands(commands, 'zzznotfound');
    expect(result).toHaveLength(0);
  });
});
