export const COMPLETION_HELP = `kb completion — generate shell completion scripts

Usage:
  kb completion bash
  kb completion zsh
  kb completion fish

Shells:
  bash                  Print a bash completion function for the \`kb\` command.
  zsh                   Print a zsh completion function for the \`kb\` command.
  fish                  Print fish completion commands for the \`kb\` command.

Options:
  --help, -h            Show this help.

Examples:
  kb completion bash > ~/.local/share/bash-completion/completions/kb
  kb completion zsh > ~/.zfunc/_kb
  kb completion fish > ~/.config/fish/completions/kb.fish
`;

interface CompletionOption {
  flags: string[];
  value: string | null;
  description: string;
}

interface CompletionCommand {
  name: string;
  summary: string;
  usage: string[];
  options: CompletionOption[];
}

type CompletionShell = 'bash' | 'zsh' | 'fish';

export async function runCompletion(
  rest: string[],
  commands: CompletionCommand[],
): Promise<number> {
  if (rest.length !== 1) {
    process.stderr.write('kb completion: expected one shell (bash, zsh, fish)\n');
    return 2;
  }
  const shell = rest[0];
  if (shell !== 'bash' && shell !== 'zsh' && shell !== 'fish') {
    process.stderr.write(`kb completion: unsupported shell '${shell}' (expected bash, zsh, or fish)\n`);
    return 2;
  }
  process.stdout.write(generateCompletionScript(shell, commands));
  return 0;
}

function generateCompletionScript(shell: CompletionShell, commands: CompletionCommand[]): string {
  switch (shell) {
    case 'bash': return generateBashCompletion(commands);
    case 'zsh': return generateZshCompletion(commands);
    case 'fish': return generateFishCompletion(commands);
  }
}

function generateBashCompletion(commands: CompletionCommand[]): string {
  const commandWords = ['help', ...commands.map((command) => command.name)].sort().join(' ');
  const commandCases = commands.map((command) => (
    `    ${command.name})\n` +
    `      _kb_compgen ${shellSingleQuote(completionWordsForCommand(command).join(' '))}\n` +
    '      ;;\n'
  )).join('');
  return `# bash completion for kb; generated by \`kb completion bash\`
_kb() {
  local cur command prev value_prefix
  COMPREPLY=()
  cur="\${COMP_WORDS[COMP_CWORD]}"
  prev=""
  value_prefix=""

  _kb_compgen() {
    COMPREPLY=( $(compgen -W "$1" -- "$cur") )
    if [[ -n "$value_prefix" ]]; then
      local i
      for i in "\${!COMPREPLY[@]}"; do
        COMPREPLY[$i]="\${COMPREPLY[$i]#"$value_prefix"}"
      done
    fi
  }

  if [[ \${COMP_CWORD} -eq 1 ]]; then
    _kb_compgen ${shellSingleQuote(commandWords)}
    return 0
  fi

  if [[ \${COMP_CWORD} -gt 0 ]]; then
    prev="\${COMP_WORDS[COMP_CWORD-1]}"
  fi
  if [[ "$prev" == --* && "$COMP_LINE" == *"$prev=$cur"* ]]; then
    value_prefix="$prev="
    cur="$value_prefix$cur"
  fi

  command="\${COMP_WORDS[1]}"
  case "$command" in
${commandCases}    help)
      _kb_compgen ${shellSingleQuote(`${commandWords} --format=md --format=json`)}
      ;;
    *)
      ;;
  esac
}
complete -F _kb kb
`;
}

function generateZshCompletion(commands: CompletionCommand[]): string {
  const commandEntries = [
    { name: 'help', summary: 'Show top-level or command-specific help' },
    ...commands.map((command) => ({ name: command.name, summary: command.summary })),
  ]
    .sort((a, b) => a.name.localeCompare(b.name))
    .map((entry) => `    ${shellSingleQuote(`${entry.name}:${escapeZshDescription(entry.summary)}`)}`)
    .join('\n');
  const commandCases = commands.map((command) => {
    if (command.name === 'completion') {
      return `      completion)\n        _values 'shell' bash zsh fish\n        ;;\n`;
    }
    const optionEntries = completionWordsForCommand(command)
      .map((word) => `        ${shellSingleQuote(word)}`)
      .join('\n');
    return `      ${command.name})\n        local -a opts\n        opts=(\n${optionEntries}\n        )\n        _describe -t options 'kb ${command.name} options' opts\n        ;;\n`;
  }).join('');
  return `#compdef kb
# zsh completion for kb; generated by \`kb completion zsh\`
_kb() {
  local -a commands
  commands=(
${commandEntries}
  )
  local curcontext="$curcontext" state line
  _arguments -C \\
    '1:command:->commands' \\
    '*::argument:->arguments'

  case "$state" in
    commands)
      _describe -t commands 'kb commands' commands
      ;;
    arguments)
      case "$line[1]" in
${commandCases}      help)
        _describe -t commands 'kb commands' commands
        ;;
      esac
      ;;
  esac
}
_kb "$@"
`;
}

function generateFishCompletion(commands: CompletionCommand[]): string {
  const lines = [
    '# fish completion for kb; generated by `kb completion fish`',
    'complete -c kb -f',
    fishCompletionLine('__fish_use_subcommand', 'help', 'Show top-level or command-specific help'),
  ];
  for (const command of commands) {
    lines.push(fishCompletionLine('__fish_use_subcommand', command.name, command.summary));
  }
  const commandNames = commands.map((command) => command.name).join(' ');
  lines.push(`complete -c kb -n '__fish_seen_subcommand_from help' -a ${shellSingleQuote(commandNames)} -d 'Command'`);
  lines.push("complete -c kb -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish' -d 'Shell'");
  for (const command of commands) {
    if (command.name === 'completion') continue;
    const actions = actionWordsForCommand(command);
    if (actions.length === 0) continue;
    lines.push(`complete -c kb -n ${shellSingleQuote(`__fish_seen_subcommand_from ${command.name}`)} -a ${shellSingleQuote(actions.join(' '))} -d 'Action'`);
  }
  for (const command of commands) {
    for (const option of command.options) {
      const line = fishOptionLine(command.name, option);
      if (line !== null) lines.push(line);
    }
  }
  return `${lines.join('\n')}\n`;
}

function completionWordsForCommand(command: CompletionCommand): string[] {
  const words = new Set<string>();
  for (const action of actionWordsForCommand(command)) words.add(action);
  for (const option of command.options) {
    for (const flag of option.flags) {
      words.add(flag);
      if (flag.startsWith('--')) {
        for (const value of enumOptionValues(option)) {
          words.add(`${flag}=${value}`);
        }
      }
    }
  }
  return [...words].sort();
}

function enumOptionValues(option: CompletionOption): string[] {
  if (option.value !== null && !option.value.includes('<') && !option.value.includes('>')) {
    const parts = option.value.split('|').map((part) => part.trim()).filter(Boolean);
    if (parts.length > 0) return parts;
  }
  const controlled = /Controlled vocabulary:\s*([A-Za-z0-9_-]+(?:,\s*[A-Za-z0-9_-]+)+)/.exec(option.description);
  if (controlled === null) return [];
  return controlled[1].split(',').map((part) => part.trim()).filter(Boolean);
}

function actionWordsForCommand(command: CompletionCommand): string[] {
  const actions = new Set<string>();
  const prefix = `kb ${command.name} `;
  for (const usage of command.usage) {
    if (!usage.startsWith(prefix)) continue;
    const token = usage.slice(prefix.length).trim().split(/\s+/, 1)[0] ?? '';
    for (const part of token.split('|')) {
      if (isLiteralActionWord(part)) actions.add(part);
    }
  }
  return [...actions].sort();
}

function isLiteralActionWord(value: string): boolean {
  return /^[A-Za-z0-9][A-Za-z0-9-]*$/.test(value);
}

function fishCompletionLine(condition: string, argument: string, description: string): string {
  return `complete -c kb -n ${shellSingleQuote(condition)} -a ${shellSingleQuote(argument)} -d ${shellSingleQuote(description)}`;
}

function fishOptionLine(commandName: string, option: CompletionOption): string | null {
  const switches = option.flags
    .map((flag) => {
      if (flag.startsWith('--') && flag.length > 2) return `-l ${shellSingleQuote(flag.slice(2))}`;
      if (/^-[A-Za-z0-9]$/.test(flag)) return `-s ${shellSingleQuote(flag.slice(1))}`;
      return null;
    })
    .filter((part): part is string => part !== null)
    .join(' ');
  if (switches === '') return null;
  const enumValues = enumOptionValues(option);
  const valuePart = enumValues.length > 0
    ? ` -a ${shellSingleQuote(enumValues.join(' '))}`
    : '';
  const descriptionPart = option.description === '' ? '' : ` -d ${shellSingleQuote(option.description)}`;
  return `complete -c kb -n ${shellSingleQuote(`__fish_seen_subcommand_from ${commandName}`)} ${switches}${valuePart}${descriptionPart}`;
}

function escapeZshDescription(value: string): string {
  return value.replace(/\\/g, '\\\\').replace(/:/g, '\\:');
}

function shellSingleQuote(value: string): string {
  return `'${value.replace(/'/g, "'\\''")}'`;
}
