import {
  describe,
  expect,
  it
} from 'vitest';
import type { PluginApiSlashCommandContribution } from '../models/plugin-api.models';
import {
  filterSlashCommands,
  findSlashCommand,
  parseSlashCommandArguments,
  parseSlashCommandInput,
  parseSlashCommandQuery,
  selectAvailableSlashCommands,
  type SlashCommandEntry
} from './slash-command.rules';

function entry(name: string, overrides: Partial<PluginApiSlashCommandContribution> = {}, pluginId = 'plugin.test'): SlashCommandEntry {
  return {
    contribution: {
      name,
      run: () => {},
      ...overrides
    },
    id: `${pluginId}:${name}`,
    pluginId
  };
}

describe('parseSlashCommandQuery', () => {
  it('returns the empty query for a lone slash', () => {
    expect(parseSlashCommandQuery('/')).toBe('');
  });

  it('returns the partial name while typing', () => {
    expect(parseSlashCommandQuery('/gi')).toBe('gi');
  });

  it('returns null once whitespace (arguments) are typed', () => {
    expect(parseSlashCommandQuery('/giphy cat')).toBeNull();
  });

  it('returns null for non-command text', () => {
    expect(parseSlashCommandQuery('hello')).toBeNull();
  });
});

describe('parseSlashCommandInput', () => {
  it('parses a command without arguments', () => {
    expect(parseSlashCommandInput('/ping')).toEqual({ name: 'ping', rawArgs: '' });
  });

  it('parses a command with arguments', () => {
    expect(parseSlashCommandInput('/giphy  funny cat ')).toEqual({ name: 'giphy', rawArgs: 'funny cat' });
  });

  it('returns null for a lone slash', () => {
    expect(parseSlashCommandInput('/')).toBeNull();
  });

  it('returns null for non-command text', () => {
    expect(parseSlashCommandInput('not a command')).toBeNull();
  });
});

describe('selectAvailableSlashCommands', () => {
  const entries = [
    entry('alpha', { scope: 'global' }),
    entry('zeta', { scope: 'server' }),
    entry('beta', { scope: 'global' })
  ];

  it('includes global and server commands on a server surface, sorted by name', () => {
    expect(selectAvailableSlashCommands(entries, 'server').map((item) => item.contribution.name)).toEqual([
      'alpha',
      'beta',
      'zeta'
    ]);
  });

  it('excludes server-scoped commands on a direct surface', () => {
    expect(selectAvailableSlashCommands(entries, 'direct').map((item) => item.contribution.name)).toEqual(['alpha', 'beta']);
  });

  it('treats a missing scope as global', () => {
    expect(selectAvailableSlashCommands([entry('plain')], 'direct')).toHaveLength(1);
  });
});

describe('filterSlashCommands', () => {
  const entries = [
    entry('giphy'),
    entry('gif-search'),
    entry('roll')
  ];

  it('returns all entries for an empty query', () => {
    expect(filterSlashCommands(entries, '')).toHaveLength(3);
  });

  it('ranks prefix matches above contains matches', () => {
    const names = filterSlashCommands([entry('a-gif'), entry('gif')], 'gif').map((item) => item.contribution.name);

    expect(names).toEqual(['gif', 'a-gif']);
  });

  it('matches case-insensitively', () => {
    expect(filterSlashCommands(entries, 'GIF').map((item) => item.contribution.name)).toContain('gif-search');
  });
});

describe('findSlashCommand', () => {
  const entries = [entry('Ping'), entry('roll')];

  it('matches the command name case-insensitively', () => {
    expect(findSlashCommand(entries, 'ping')?.contribution.name).toBe('Ping');
  });

  it('returns null when no command matches', () => {
    expect(findSlashCommand(entries, 'unknown')).toBeNull();
  });
});

describe('parseSlashCommandArguments', () => {
  it('returns an empty record when the command has no options', () => {
    expect(parseSlashCommandArguments('anything here', [])).toEqual({});
  });

  it('maps positional tokens to option names', () => {
    const args = parseSlashCommandArguments('6 advantage', [{ name: 'sides' }, { name: 'mode' }]);

    expect(args).toEqual({ sides: '6', mode: 'advantage' });
  });

  it('captures the remaining text for a rest option', () => {
    const args = parseSlashCommandArguments('happy birthday to you', [{ name: 'tone' }, { name: 'message', type: 'rest' }]);

    expect(args).toEqual({ tone: 'happy', message: 'birthday to you' });
  });

  it('fills missing positional options with empty strings', () => {
    expect(parseSlashCommandArguments('', [{ name: 'first' }])).toEqual({ first: '' });
  });
});
