/**
 * Machine-readable output helper for interactive prompts
 *
 * Provides utilities for outputting prompt configurations as JSON
 * instead of rendering interactive UI, enabling AI agents and scripts
 * to interact with the CLI programmatically.
 *
 * Design notes:
 * - Stateless functions for easy unit testing
 * - Designed for future extraction as standalone npm package
 * - Supports both explicit --json flag and automatic non-TTY detection
 *
 * Exit code conventions:
 * - EXIT_SUCCESS (0): Command completed successfully
 * - EXIT_ERROR (1): Command failed with an error
 * - EXIT_NEEDS_INPUT (2): Command needs additional input (prompt required)
 */
/**
 * Exit code for successful command completion
 */
export const EXIT_SUCCESS = 0;
/**
 * Exit code for command failure/error
 */
export const EXIT_ERROR = 1;
/**
 * Exit code when command needs additional input (prompt required)
 * Use this exit code when outputting a prompt in JSON mode to signal
 * to scripts/agents that the command didn't fail, but needs more input.
 */
export const EXIT_NEEDS_INPUT = 2;
/**
 * All valid JSON envelope type discriminators.
 * Used for contract tests and schema validation.
 */
export const JSON_ENVELOPE_TYPES = [
    'prompt',
    'success',
    'error',
    'dry-run',
    'confirmation_needed',
    'execution_result',
];
/**
 * Required fields per envelope type for contract validation.
 * Tests use this to verify no fields are accidentally removed.
 */
export const JSON_ENVELOPE_REQUIRED_FIELDS = {
    prompt: ['type', 'prompt', 'metadata'],
    success: ['type', 'prompt', 'success', 'result', 'metadata'],
    error: ['type', 'error', 'metadata'],
    'dry-run': ['type', 'valid', 'metadata'],
    confirmation_needed: ['type', 'plan', 'confirm_command', 'message', 'metadata'],
    execution_result: ['type', 'result', 'metadata'],
};
/**
 * Validate that a parsed JSON object conforms to the machine-mode envelope schema.
 *
 * Returns an array of validation errors (empty = valid).
 * Useful for contract tests and runtime validation of JSON output.
 *
 * @param obj - Parsed JSON object to validate
 * @returns Array of validation error strings (empty if valid)
 */
export function validateJsonEnvelope(obj) {
    const errors = [];
    if (typeof obj !== 'object' || obj === null) {
        errors.push('Output must be a non-null object');
        return errors;
    }
    const record = obj;
    // Check type discriminator
    if (!('type' in record)) {
        errors.push('Missing required field: type');
        return errors;
    }
    const type = record.type;
    if (!JSON_ENVELOPE_TYPES.includes(type)) {
        errors.push(`Invalid envelope type: "${type}". Must be one of: ${JSON_ENVELOPE_TYPES.join(', ')}`);
        return errors;
    }
    // Check required fields for this type
    const requiredFields = JSON_ENVELOPE_REQUIRED_FIELDS[type];
    for (const field of requiredFields) {
        if (!(field in record)) {
            errors.push(`Missing required field for type "${type}": ${field}`);
        }
    }
    // Validate metadata structure
    if ('metadata' in record && record.metadata !== undefined) {
        const metadata = record.metadata;
        if (typeof metadata !== 'object' || metadata === null) {
            errors.push('metadata must be a non-null object');
        }
        else {
            if (!('command' in metadata) || typeof metadata.command !== 'string') {
                errors.push('metadata.command must be a string');
            }
            if (!('flags' in metadata) || typeof metadata.flags !== 'object') {
                errors.push('metadata.flags must be an object');
            }
        }
    }
    // Type-specific validation
    if (type === 'prompt' && 'prompt' in record && record.prompt !== null) {
        const prompt = record.prompt;
        if (!('type' in prompt)) {
            errors.push('prompt.type is required when prompt is non-null');
        }
    }
    if (type === 'error' && 'error' in record) {
        const error = record.error;
        if (typeof error !== 'object' || error === null) {
            errors.push('error must be a non-null object');
        }
        else {
            if (!('code' in error) || typeof error.code !== 'string') {
                errors.push('error.code must be a string');
            }
            if (!('message' in error) || typeof error.message !== 'string') {
                errors.push('error.message must be a string');
            }
        }
    }
    if (type === 'success') {
        if (record.success !== true) {
            errors.push('success field must be true for success type');
        }
        if (record.prompt !== null) {
            errors.push('prompt field must be null for success type');
        }
    }
    if (type === 'confirmation_needed') {
        if (typeof record.confirm_command !== 'string' || !record.confirm_command) {
            errors.push('confirm_command must be a non-empty string');
        }
    }
    return errors;
}
/**
 * Check if the current environment is non-TTY (piped input or output)
 *
 * Uses the "either" strategy: returns true if EITHER stdin OR stdout is non-TTY.
 * This covers the primary use case of scripts/agents calling prlt as a subprocess,
 * where both stdin and stdout are typically non-TTY.
 *
 * Returns true if:
 * - stdin is not a TTY (e.g., piped input)
 * - stdout is not a TTY (e.g., piped output)
 * - PRLT_JSON=1 environment variable is set (overrides TTY detection)
 *
 * Returns false if:
 * - PRLT_FORCE_TEXT=1 is set (forces text output in non-TTY environments, useful for testing)
 *
 * @returns true if either stdin or stdout is not a TTY, or PRLT_JSON=1 is set
 */
export function isNonTTY() {
    // PRLT_FORCE_TEXT overrides non-TTY detection, forcing human-readable text output.
    // Used in E2E tests where execSync creates a non-TTY child process but tests
    // assert on styled text output.
    if (process.env.PRLT_FORCE_TEXT === '1' || process.env.PRLT_FORCE_TEXT === 'true') {
        return false;
    }
    if (process.env.PRLT_JSON === '1' || process.env.PRLT_JSON === 'true') {
        return true;
    }
    return !process.stdout.isTTY || !process.stdin.isTTY;
}
/**
 * Determine if JSON output mode is active (for AI agents)
 *
 * Returns true if:
 * - The --json flag is set (or -m/--machine aliases)
 * - The PRLT_JSON=1 environment variable is set
 * - Either stdin or stdout is non-TTY (piped input/output)
 *
 * @param flags - Command flags object
 * @returns true if JSON mode should be used
 */
export function shouldOutputJson(flags) {
    // --json flag or --machine/-m flag
    if (flags.json === true || flags.machine === true) {
        return true;
    }
    // Automatic detection for non-TTY environments (includes PRLT_JSON env var)
    return isNonTTY();
}
/**
 * Alias for shouldOutputJson - clearer name for agent-focused code
 */
export const isAgentMode = shouldOutputJson;
/**
 * Determine if machine-readable output mode is active (for AI agents/scripts)
 *
 * Returns true if:
 * - The --json flag is set (or -m/--machine aliases)
 * - The PRLT_JSON=1 environment variable is set
 * - Either stdin or stdout is non-TTY (piped input/output)
 *
 * @param flags - Command flags object
 * @returns true if machine-readable output mode should be used
 */
export function isMachineOutput(flags) {
    // --json flag or --machine/-m flag
    if (flags.json === true || flags.machine === true) {
        return true;
    }
    // Automatic detection for non-TTY environments (includes PRLT_JSON env var)
    return isNonTTY();
}
/**
 * Create metadata object for JSON output
 *
 * @param command - Command name (e.g., "work spawn")
 * @param flags - Flags passed to the command
 * @returns Metadata object
 */
export function createMetadata(command, flags) {
    // Filter out internal/sensitive flags
    const filteredFlags = {};
    for (const [key, value] of Object.entries(flags)) {
        // Skip undefined values and internal flags
        if (value !== undefined && !key.startsWith('_')) {
            filteredFlags[key] = value;
        }
    }
    return {
        command,
        flags: filteredFlags,
        timestamp: new Date().toISOString(),
    };
}
/**
 * Output a prompt configuration as JSON and exit
 *
 * Use this when a command would normally show an interactive prompt.
 * Outputs the prompt config so agents can understand what input is needed,
 * then exits with EXIT_NEEDS_INPUT (2) to signal the command needs more input.
 *
 * @param config - Prompt configuration
 * @param metadata - Command metadata
 */
export function outputPromptAsJson(config, metadata) {
    const output = {
        type: 'prompt',
        prompt: config,
        metadata,
    };
    console.log(JSON.stringify(output, null, 2));
    process.exit(EXIT_NEEDS_INPUT);
}
/**
 * Output success result as JSON and exit
 *
 * Use this when all required data was provided via flags
 * and no prompt is needed. Exits with EXIT_SUCCESS (0) to signal
 * successful command completion.
 *
 * @param result - Command-specific result data
 * @param metadata - Command metadata
 */
export function outputSuccessAsJson(result, metadata) {
    const output = {
        type: 'success',
        prompt: null,
        success: true,
        result,
        metadata,
    };
    console.log(JSON.stringify(output, null, 2));
    process.exit(EXIT_SUCCESS);
}
/**
 * Output error as JSON and exit
 *
 * Use this when an error occurs and --json flag is active.
 * Provides structured error output for programmatic handling,
 * then exits with EXIT_ERROR (1).
 *
 * @param code - Machine-readable error code (e.g., "NO_TICKETS_AVAILABLE")
 * @param message - Human-readable error message
 * @param metadata - Command metadata
 */
export function outputErrorAsJson(code, message, metadata) {
    const output = {
        type: 'error',
        error: {
            code,
            message,
        },
        metadata,
    };
    console.log(JSON.stringify(output, null, 2));
    process.exit(EXIT_ERROR);
}
/**
 * Convert inquirer choices to PromptChoice format
 *
 * Handles both simple string choices and object choices,
 * filtering out Separator instances.
 *
 * @param choices - Inquirer choices array
 * @returns Array of PromptChoice objects
 */
export function normalizeChoices(choices) {
    const normalized = [];
    for (const choice of choices) {
        // Skip Separator instances (they have a 'type' property set to 'separator')
        if (typeof choice === 'object' &&
            choice !== null &&
            'type' in choice &&
            choice.type === 'separator') {
            continue;
        }
        // Handle string choices
        if (typeof choice === 'string') {
            normalized.push({ name: choice, value: choice });
            continue;
        }
        // Handle object choices
        if (typeof choice === 'object' &&
            choice !== null &&
            'name' in choice &&
            'value' in choice) {
            const obj = choice;
            normalized.push({
                name: obj.name,
                value: String(obj.value),
                ...(obj.disabled !== undefined && { disabled: Boolean(obj.disabled) }),
                ...(obj.command !== undefined && { command: obj.command }),
            });
        }
    }
    return normalized;
}
/**
 * Build a prompt config object from inquirer prompt options
 *
 * @param type - Prompt type
 * @param name - Field name
 * @param message - User-facing message
 * @param choices - Optional choices array
 * @param defaultValue - Optional default value
 * @returns PromptConfig object
 */
export function buildPromptConfig(type, name, message, choices, defaultValue) {
    const config = {
        type,
        name,
        message,
    };
    if (choices) {
        config.choices = normalizeChoices(choices);
    }
    if (defaultValue !== undefined) {
        config.default = defaultValue;
    }
    return config;
}
/**
 * Build a form prompt config with multiple fields
 *
 * @param fields - Array of form field configurations
 * @returns PromptConfig object with type 'form'
 */
export function buildFormPromptConfig(fields) {
    return {
        type: 'form',
        fields,
    };
}
/**
 * Output a successful dry-run result as JSON and exit
 *
 * Use this when --dry-run is set and all validation passes.
 * Shows what would be created without actually creating it.
 *
 * @param entityType - Type of entity that would be created (e.g., "ticket", "project")
 * @param wouldCreate - Data about what would be created
 * @param metadata - Command metadata
 */
export function outputDryRunSuccessAsJson(entityType, wouldCreate, metadata) {
    const output = {
        type: 'dry-run',
        valid: true,
        wouldCreate: {
            type: entityType,
            ...wouldCreate,
        },
        metadata,
    };
    console.log(JSON.stringify(output, null, 2));
    process.exit(EXIT_SUCCESS);
}
/**
 * Output a dry-run validation failure as JSON and exit
 *
 * Use this when --dry-run is set and validation fails.
 * Shows the validation errors without attempting to create.
 *
 * @param errors - Array of validation errors
 * @param metadata - Command metadata
 */
export function outputDryRunErrorsAsJson(errors, metadata) {
    const output = {
        type: 'dry-run',
        valid: false,
        errors,
        metadata,
    };
    console.log(JSON.stringify(output, null, 2));
    process.exit(EXIT_ERROR);
}
/**
 * Output a confirmation needed response as JSON and exit
 *
 * Use this in non-TTY mode when all required flags are provided but --yes is not set.
 * This allows agents to preview what will happen before confirming execution.
 *
 * @param plan - Details of what will happen if confirmed
 * @param confirmCommand - The full command to run with --yes to execute
 * @param message - Human-readable message explaining the confirmation
 * @param metadata - Command metadata
 */
export function outputConfirmationNeededAsJson(plan, confirmCommand, message, metadata) {
    const output = {
        type: 'confirmation_needed',
        plan,
        confirm_command: confirmCommand,
        message,
        metadata,
    };
    console.log(JSON.stringify(output, null, 2));
    process.exit(EXIT_NEEDS_INPUT);
}
/**
 * Output execution result as JSON (non-exiting version)
 *
 * Use this after execution completes in non-TTY mode to provide structured
 * results. Unlike other output functions, this does NOT exit - caller should
 * handle cleanup and exit.
 *
 * @param executions - Array of execution results
 * @param successCount - Number of successful executions
 * @param failCount - Number of failed executions
 * @param metadata - Command metadata
 */
export function outputExecutionResultAsJson(executions, successCount, failCount, metadata) {
    const output = {
        type: 'execution_result',
        result: {
            executions,
            successCount,
            failCount,
        },
        metadata,
    };
    console.log(JSON.stringify(output, null, 2));
}
//# sourceMappingURL=prompt-json.js.map