/* eslint-disable no-await-in-loop */
/**
 * FlagResolver - Unified flag resolution for human and machine interactive modes
 *
 * This abstraction unifies the two interaction patterns in the CLI:
 * - Human interactive mode: Uses inquirer prompts, collects input, returns flags
 * - Machine/JSON mode: Outputs prompt schema as JSON, exits for agent to process
 *
 * Both modes use the same underlying pattern: prompts PRODUCE flags.
 * The execution logic only sees flags, not prompt results.
 *
 * @example
 * ```typescript
 * // Define prompts for missing flags
 * const resolver = new FlagResolver({
 *   commandName: 'ticket create',
 *   baseCommand: 'prlt ticket create',
 *   jsonMode: shouldOutputJson(flags),
 *   flags,
 * });
 *
 * // Add prompts - they execute in order, can depend on previously resolved flags
 * resolver.addPrompt({
 *   flagName: 'column',
 *   type: 'list',
 *   message: 'Select column:',
 *   choices: async () => columns.map(c => ({ name: c, value: c })),
 * });
 *
 * resolver.addPrompt({
 *   flagName: 'title',
 *   type: 'input',
 *   message: 'Enter title:',
 *   when: (ctx) => ctx.flags.column !== undefined, // Only after column is selected
 * });
 *
 * // Resolve all missing flags
 * const resolved = await resolver.resolve();
 * // resolved.column and resolved.title are now guaranteed to have values
 * ```
 */
import inquirer from 'inquirer';
import { outputPromptAsJson, createMetadata, } from '../prompt-json.js';
import { multiLineInput } from '../multiline-input.js';
import { withSignalSafePrompt } from '../signal-handler.js';
/**
 * FlagResolver handles unified flag resolution for both human and machine modes.
 *
 * Usage pattern:
 * 1. Create resolver with command info and current flags
 * 2. Add prompt definitions for flags that may need resolution
 * 3. Call resolve() to get complete flags
 *
 * In JSON mode, resolve() outputs the first unresolved prompt and exits.
 * In interactive mode, resolve() prompts for each unresolved flag sequentially.
 */
export class FlagResolver {
    prompts = [];
    options;
    resolvedFlags;
    resolverContext;
    constructor(options) {
        this.options = options;
        this.resolvedFlags = { ...options.flags };
        this.resolverContext = {
            flags: this.resolvedFlags,
            args: options.args || {},
            commandName: options.commandName,
            baseCommand: options.baseCommand,
            ...options.context,
        };
    }
    /**
     * Add a prompt definition for resolving a flag.
     * Prompts are processed in the order they are added.
     */
    addPrompt(prompt) {
        this.prompts.push(prompt);
        return this;
    }
    /**
     * Add multiple prompt definitions at once.
     */
    addPrompts(prompts) {
        for (const prompt of prompts) {
            this.prompts.push(prompt);
        }
        return this;
    }
    /**
     * Update the context with additional data.
     * Useful for adding computed values that prompts might need.
     */
    setContext(key, value) {
        this.resolverContext[key] = value;
        return this;
    }
    /**
     * Get current context value
     */
    getContext(key) {
        return this.resolverContext[key];
    }
    /**
     * Check if a flag has a value (not undefined)
     */
    hasFlag(flagName) {
        return this.resolvedFlags[flagName] !== undefined;
    }
    /**
     * Get current flag value
     */
    getFlag(flagName) {
        return this.resolvedFlags[flagName];
    }
    /**
     * Set a flag value directly (bypasses prompting)
     */
    setFlag(flagName, value) {
        this.resolvedFlags[flagName] = value;
        this.resolverContext.flags = this.resolvedFlags;
        return this;
    }
    /**
     * Resolve all prompts and return complete flags.
     *
     * In JSON mode: outputs the first unresolved prompt as JSON and exits (never returns).
     * In interactive mode: prompts for each unresolved flag and returns all flags.
     *
     * @returns The fully resolved flags
     */
    async resolve() {
        for (const prompt of this.prompts) {
            // Check if flag already has a value
            const currentValue = this.resolvedFlags[prompt.flagName];
            if (currentValue !== undefined) {
                continue;
            }
            // Check conditional
            if (prompt.when && !prompt.when(this.resolverContext)) {
                continue;
            }
            // Resolve the message if it's a function
            const message = typeof prompt.message === 'function'
                ? prompt.message(this.resolverContext)
                : prompt.message;
            // Get choices if applicable
            let choices;
            if (prompt.choices) {
                choices = await Promise.resolve(prompt.choices(this.resolverContext));
            }
            // Get default value
            const defaultValue = typeof prompt.default === 'function'
                ? prompt.default(this.resolverContext)
                : prompt.default;
            if (this.options.jsonMode) {
                // JSON MODE: Output prompt config and exit
                await this.outputJsonPrompt(prompt, message, choices, defaultValue);
                // outputPromptAsJson calls process.exit, so we never reach here
            }
            else {
                // INTERACTIVE MODE: Prompt for value
                const value = await this.promptInteractive(prompt, message, choices, defaultValue);
                // Transform value if needed
                const finalValue = prompt.transform
                    ? prompt.transform(value, this.resolverContext)
                    : value;
                // Store resolved flag
                this.resolvedFlags[prompt.flagName] = finalValue;
                this.resolverContext.flags = this.resolvedFlags;
            }
        }
        return this.resolvedFlags;
    }
    /**
     * Output prompt as JSON for machine mode
     */
    async outputJsonPrompt(prompt, message, choices, defaultValue) {
        // Build prompt config
        const config = {
            type: prompt.type,
            name: prompt.flagName,
            message,
        };
        // Add choices with command field
        if (choices) {
            config.choices = choices.map(choice => {
                const promptChoice = {
                    name: choice.name,
                    value: String(choice.value),
                };
                if (choice.disabled !== undefined) {
                    promptChoice.disabled = Boolean(choice.disabled);
                }
                // Generate command for this choice
                if (choice.command) {
                    promptChoice.command = choice.command;
                }
                else if (!prompt.skipAutoCommand) {
                    // Auto-generate command
                    promptChoice.command = prompt.getCommand
                        ? prompt.getCommand(choice.value, this.resolverContext)
                        : this.buildCommand(prompt.flagName, choice.value);
                }
                return promptChoice;
            });
        }
        // Add default value
        if (defaultValue !== undefined) {
            config.default = defaultValue;
        }
        // Add context if provided
        if (prompt.context) {
            config.context = typeof prompt.context === 'function'
                ? prompt.context(this.resolverContext)
                : prompt.context;
        }
        // For input prompts without choices, add helpful context
        if (prompt.type === 'input' && !config.context) {
            config.context = {
                hint: `Provide ${prompt.flagName} with: ${this.buildCommand(prompt.flagName, '<value>')}`,
            };
        }
        // Output and exit
        outputPromptAsJson(config, createMetadata(this.options.commandName, this.options.flags));
        // outputPromptAsJson calls process.exit, but TypeScript doesn't know that
        // This line is never reached
        throw new Error('Unreachable');
    }
    /**
     * Prompt for value in interactive mode
     */
    async promptInteractive(prompt, message, choices, defaultValue) {
        // Handle multiline type specially - use our custom multiLineInput
        if (prompt.type === 'multiline') {
            const result = await multiLineInput({
                message,
                default: typeof defaultValue === 'string' ? defaultValue : '',
                validate: prompt.validate
                    ? (value) => prompt.validate(value, this.resolverContext)
                    : undefined,
            });
            if (result.cancelled) {
                throw new Error('Input cancelled');
            }
            return result.value;
        }
        // Build inquirer prompt config as a single question object
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const question = {
            type: prompt.type,
            name: prompt.flagName,
            message,
            default: defaultValue,
        };
        // Add choices for list/checkbox
        if (choices) {
            question.choices = choices.map(c => ({
                name: c.name,
                value: c.value,
                disabled: c.disabled,
            }));
        }
        // Add validation for input
        if (prompt.validate) {
            question.validate = (input) => {
                return prompt.validate(input, this.resolverContext);
            };
        }
        // Prompt and return value (with signal-safe wrapper)
        const answers = await withSignalSafePrompt(() => inquirer.prompt([question]));
        return answers[prompt.flagName];
    }
    /**
     * Build a command string for a flag value
     */
    buildCommand(flagName, value) {
        const { baseCommand } = this.options;
        // Build flags part from currently resolved flags
        let cmd = baseCommand;
        // Add any project flag from context
        const projectId = this.resolverContext.projectId;
        if (projectId && !this.resolvedFlags['project']) {
            cmd += ` -P ${projectId}`;
        }
        // Add resolved flags (except the one we're prompting for)
        for (const [key, val] of Object.entries(this.resolvedFlags)) {
            if (key === flagName || val === undefined || key === 'json')
                continue;
            if (typeof val === 'boolean') {
                if (val)
                    cmd += ` --${key}`;
            }
            else if (typeof val === 'string') {
                cmd += ` --${key} "${val}"`;
            }
            else if (Array.isArray(val)) {
                cmd += ` --${key} "${val.join(',')}"`;
            }
            else {
                cmd += ` --${key} ${val}`;
            }
        }
        // Add the current flag
        if (typeof value === 'boolean') {
            if (value)
                cmd += ` --${flagName}`;
        }
        else if (typeof value === 'string') {
            cmd += ` --${flagName} "${value}"`;
        }
        else if (value !== undefined) {
            cmd += ` --${flagName} ${value}`;
        }
        // Always add --json at the end
        cmd += ' --json';
        return cmd;
    }
}
/**
 * Convenience function to create a FlagResolver from command context
 */
export function createFlagResolver(options) {
    return new FlagResolver(options);
}
/**
 * Helper to check if we should use JSON mode
 * Re-exported from prompt-json for convenience
 */
export { shouldOutputJson, isMachineOutput } from '../prompt-json.js';
//# sourceMappingURL=resolver.js.map