/**
 * completion-generator.ts — Commander command-tree → POSIX shell completion script.
 *
 * Phase B1 of olam-operator-onboarding-parity (plan
 * ~/.claude/plans/olam-operator-onboarding-parity.md).
 *
 * Decision 4 (plan): runtime-emit from the commander tree, NOT static
 * fixtures in the tarball. One source of truth: new CLI commands auto-
 * included in the next `olam completion <shell>` invocation.
 *
 * Supported shells: zsh + bash. Other shells throw `UnsupportedShellError`.
 *
 * Nesting depth: TWO levels of subcommand dispatch (top-level + one
 * nested). Covers ~95% of olam's tree (`olam kg build`, `olam auth login`,
 * `olam workspace list`, etc.). Deeper nesting (`olam world snapshot
 * restore`) falls through gracefully — top + one level still complete.
 *
 * Escaping:
 *   - zsh: descriptions inside `name:description` syntax need `:` escaped
 *     (since `:` is the field separator) and `'` doubled (since strings
 *     are single-quoted).
 *   - bash: descriptions inside double-quoted strings need `"`, `\`, `$`,
 *     and backtick escaped.
 *
 * Hidden commands (commander's private `_hidden` flag) are excluded:
 *   - `olam crystallize` is registered with `{ hidden: !isPleriConfigured() }`
 *     for fresh-install operators.
 *
 * Determinism: commander preserves command-registration order in
 * `Command.commands`. The walk is a pure-functional map → the output is
 * a deterministic function of the program tree. Snapshot tests rely on
 * this.
 */
export class UnsupportedShellError extends Error {
    constructor(shell) {
        super(`Unsupported shell: ${shell}. Supported shells: zsh, bash.`);
        this.name = 'UnsupportedShellError';
    }
}
/**
 * Walk a commander `Command` recursively, extracting (name, description,
 * subcommands). Hidden commands (commander's private `_hidden` flag,
 * set via `.command(..., { hidden: true })`) are excluded.
 */
export function extractCommandTree(program) {
    return {
        name: program.name(),
        description: program.description() ?? '',
        subcommands: program.commands
            .filter((c) => !isHidden(c))
            .map((c) => extractCommandTree(c)),
    };
}
function isHidden(cmd) {
    // Commander stores hidden flag privately. Access defensively in case
    // the field name changes across versions.
    const c = cmd;
    return c._hidden === true;
}
/**
 * Escape a description string for zsh's `name:description` completion
 * syntax. Three transformations:
 *   1. Collapse newlines + carriage returns to single spaces (multi-line
 *      descriptions break single-quoted `'name:description'` syntax —
 *      a literal `\n` in the source string forces the next line to start
 *      a new entry without a leading quote → zsh parse error → operator's
 *      shell rc fails to load; audit finding B-ADV-3).
 *   2. Double single quotes (single-quoted-string termination).
 *   3. Backslash-escape colons (field separator in `name:description`).
 */
function escapeZsh(s) {
    return s
        .replace(/[\r\n]+/g, ' ')
        .replace(/'/g, "''")
        .replace(/:/g, '\\:');
}
/**
 * Escape a string for bash double-quoted contexts. Three transformations:
 *   1. Collapse newlines + carriage returns to single spaces (bash
 *      double-quoted strings tolerate `\n` but `compgen -W` interprets
 *      whitespace as word boundaries — a newline silently splits the
 *      word list; audit finding B-ADV-3).
 *   2. Backslash-escape `"`, `\`, `$`, backtick (the four chars bash
 *      interprets inside double quotes).
 */
function escapeBash(s) {
    return s
        .replace(/[\r\n]+/g, ' ')
        .replace(/(["\\$`])/g, '\\$1');
}
/**
 * Emit a zsh completion script.
 *
 * Format:
 *   #compdef <program>
 *
 *   _<program>() {
 *     local -a l1_subs
 *     l1_subs=(
 *       '<sub>:<description>'
 *       ...
 *     )
 *     if (( CURRENT == 2 )); then
 *       _describe '<program> command' l1_subs
 *       return
 *     fi
 *     case $words[2] in
 *       <sub-with-children>)
 *         if (( CURRENT == 3 )); then
 *           local -a l2_subs
 *           l2_subs=(...)
 *           _describe '<sub> subcommand' l2_subs
 *         fi
 *         ;;
 *       ...
 *     esac
 *   }
 *
 *   _<program> "$@"
 */
export function generateZshCompletion(tree) {
    const lines = [];
    lines.push(`#compdef ${tree.name}`);
    lines.push('');
    lines.push(`_${tree.name}() {`);
    lines.push('  local -a l1_subs');
    lines.push('  l1_subs=(');
    for (const sub of tree.subcommands) {
        lines.push(`    '${sub.name}:${escapeZsh(sub.description)}'`);
    }
    lines.push('  )');
    lines.push('');
    lines.push('  if (( CURRENT == 2 )); then');
    lines.push(`    _describe '${tree.name} command' l1_subs`);
    lines.push('    return');
    lines.push('  fi');
    lines.push('');
    const subsWithChildren = tree.subcommands.filter((s) => s.subcommands.length > 0);
    if (subsWithChildren.length > 0) {
        lines.push('  case $words[2] in');
        for (const sub of subsWithChildren) {
            lines.push(`    ${sub.name})`);
            lines.push('      if (( CURRENT == 3 )); then');
            lines.push('        local -a l2_subs');
            lines.push('        l2_subs=(');
            for (const s2 of sub.subcommands) {
                lines.push(`          '${s2.name}:${escapeZsh(s2.description)}'`);
            }
            lines.push('        )');
            lines.push(`        _describe '${sub.name} subcommand' l2_subs`);
            lines.push('      fi');
            lines.push('      ;;');
        }
        lines.push('  esac');
    }
    lines.push('}');
    lines.push('');
    lines.push(`_${tree.name} "$@"`);
    lines.push('');
    return lines.join('\n');
}
/**
 * Emit a bash completion script.
 *
 * Format:
 *   _<program>_completions() {
 *     local cur="${COMP_WORDS[COMP_CWORD]}"
 *     local cword=$COMP_CWORD
 *     if [[ $cword -eq 1 ]]; then
 *       COMPREPLY=( $(compgen -W "<sub1> <sub2> ..." -- "$cur") )
 *       return
 *     fi
 *     case "${COMP_WORDS[1]}" in
 *       <sub-with-children>)
 *         if [[ $cword -eq 2 ]]; then
 *           COMPREPLY=( $(compgen -W "<l2-1> <l2-2> ..." -- "$cur") )
 *         fi
 *         ;;
 *     esac
 *   }
 *   complete -F _<program>_completions <program>
 */
export function generateBashCompletion(tree) {
    const lines = [];
    lines.push(`_${tree.name}_completions() {`);
    lines.push('  local cur="${COMP_WORDS[COMP_CWORD]}"');
    lines.push('  local cword=$COMP_CWORD');
    lines.push('');
    lines.push('  if [[ $cword -eq 1 ]]; then');
    const l1Names = tree.subcommands.map((s) => s.name).join(' ');
    lines.push(`    COMPREPLY=( $(compgen -W "${escapeBash(l1Names)}" -- "$cur") )`);
    lines.push('    return');
    lines.push('  fi');
    lines.push('');
    const subsWithChildren = tree.subcommands.filter((s) => s.subcommands.length > 0);
    if (subsWithChildren.length > 0) {
        lines.push('  case "${COMP_WORDS[1]}" in');
        for (const sub of subsWithChildren) {
            lines.push(`    ${sub.name})`);
            lines.push('      if [[ $cword -eq 2 ]]; then');
            const l2Names = sub.subcommands.map((s) => s.name).join(' ');
            lines.push(`        COMPREPLY=( $(compgen -W "${escapeBash(l2Names)}" -- "$cur") )`);
            lines.push('      fi');
            lines.push('      ;;');
        }
        lines.push('  esac');
    }
    lines.push('}');
    lines.push('');
    lines.push(`complete -F _${tree.name}_completions ${tree.name}`);
    lines.push('');
    return lines.join('\n');
}
/**
 * Top-level API: walk the commander program + emit a completion script
 * for the requested shell.
 */
export function generateCompletion(program, shell) {
    const tree = extractCommandTree(program);
    if (shell === 'zsh')
        return generateZshCompletion(tree);
    if (shell === 'bash')
        return generateBashCompletion(tree);
    throw new UnsupportedShellError(shell);
}
//# sourceMappingURL=completion-generator.js.map