import { spawn, execSync } from 'node:child_process';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { getGlobalConfigPath, getGlobalConfig, saveGlobalConfig, } from '../core/global-config.js';
import { getNestedValue, setNestedValue, deleteNestedValue, coerceValue, formatValueYaml, validateConfigKeyPath, validateConfig, DEFAULT_CONFIG, } from '../core/config-schema.js';
import { CORE_WORKFLOWS, ALL_WORKFLOWS, getProfileWorkflows } from '../core/profiles.js';
import { OPENSPEC_DIR_NAME } from '../core/config.js';
import { hasProjectConfigDrift } from '../core/profile-sync-drift.js';
const WORKFLOW_PROMPT_META = {
    propose: {
        name: 'Propose change',
        description: 'Create proposal, design, and tasks from a request',
    },
    explore: {
        name: 'Explore ideas',
        description: 'Investigate a problem before implementation',
    },
    new: {
        name: 'New change',
        description: 'Create a new change scaffold quickly',
    },
    continue: {
        name: 'Continue change',
        description: 'Resume work on an existing change',
    },
    apply: {
        name: 'Apply tasks',
        description: 'Implement tasks from the current change',
    },
    ff: {
        name: 'Fast-forward',
        description: 'Run a faster implementation workflow',
    },
    sync: {
        name: 'Sync specs',
        description: 'Sync change artifacts with specs',
    },
    archive: {
        name: 'Archive change',
        description: 'Finalize and archive a completed change',
    },
    'bulk-archive': {
        name: 'Bulk archive',
        description: 'Archive multiple completed changes together',
    },
    verify: {
        name: 'Verify change',
        description: 'Run verification checks against a change',
    },
    onboard: {
        name: 'Onboard',
        description: 'Guided onboarding flow for OpenSpec',
    },
};
function isPromptCancellationError(error) {
    return (error instanceof Error &&
        (error.name === 'ExitPromptError' || error.message.includes('force closed the prompt with SIGINT')));
}
/**
 * Resolve the effective current profile state from global config defaults.
 */
export function resolveCurrentProfileState(config) {
    const profile = config.profile || 'core';
    const delivery = config.delivery || 'both';
    const workflows = [
        ...getProfileWorkflows(profile, config.workflows ? [...config.workflows] : undefined),
    ];
    return { profile, delivery, workflows };
}
/**
 * Derive profile type from selected workflows.
 */
export function deriveProfileFromWorkflowSelection(selectedWorkflows) {
    const isCoreMatch = selectedWorkflows.length === CORE_WORKFLOWS.length &&
        CORE_WORKFLOWS.every((w) => selectedWorkflows.includes(w));
    return isCoreMatch ? 'core' : 'custom';
}
/**
 * Format a compact workflow summary for the profile header.
 */
export function formatWorkflowSummary(workflows, profile) {
    return `${workflows.length} selected (${profile})`;
}
function stableWorkflowOrder(workflows) {
    const seen = new Set();
    const ordered = [];
    for (const workflow of ALL_WORKFLOWS) {
        if (workflows.includes(workflow) && !seen.has(workflow)) {
            ordered.push(workflow);
            seen.add(workflow);
        }
    }
    const extras = workflows.filter((w) => !ALL_WORKFLOWS.includes(w));
    extras.sort();
    for (const extra of extras) {
        if (!seen.has(extra)) {
            ordered.push(extra);
            seen.add(extra);
        }
    }
    return ordered;
}
/**
 * Build a user-facing diff summary between two profile states.
 */
export function diffProfileState(before, after) {
    const lines = [];
    if (before.delivery !== after.delivery) {
        lines.push(`delivery: ${before.delivery} -> ${after.delivery}`);
    }
    if (before.profile !== after.profile) {
        lines.push(`profile: ${before.profile} -> ${after.profile}`);
    }
    const beforeOrdered = stableWorkflowOrder(before.workflows);
    const afterOrdered = stableWorkflowOrder(after.workflows);
    const beforeSet = new Set(beforeOrdered);
    const afterSet = new Set(afterOrdered);
    const added = afterOrdered.filter((w) => !beforeSet.has(w));
    const removed = beforeOrdered.filter((w) => !afterSet.has(w));
    if (added.length > 0 || removed.length > 0) {
        const tokens = [];
        if (added.length > 0) {
            tokens.push(`added ${added.join(', ')}`);
        }
        if (removed.length > 0) {
            tokens.push(`removed ${removed.join(', ')}`);
        }
        lines.push(`workflows: ${tokens.join('; ')}`);
    }
    return {
        hasChanges: lines.length > 0,
        lines,
    };
}
function maybeWarnConfigDrift(projectDir, state, colorize) {
    const openspecDir = path.join(projectDir, OPENSPEC_DIR_NAME);
    if (!fs.existsSync(openspecDir)) {
        return;
    }
    if (!hasProjectConfigDrift(projectDir, state.workflows, state.delivery)) {
        return;
    }
    console.log(colorize('Warning: Global config is not applied to this project. Run `openspec update` to sync.'));
}
/**
 * Register the config command and all its subcommands.
 *
 * @param program - The Commander program instance
 */
export function registerConfigCommand(program) {
    const configCmd = program
        .command('config')
        .description('View and modify global OpenSpec configuration')
        .option('--scope <scope>', 'Config scope (only "global" supported currently)')
        .hook('preAction', (thisCommand) => {
        const opts = thisCommand.opts();
        if (opts.scope && opts.scope !== 'global') {
            console.error('Error: Project-local config is not yet implemented');
            process.exit(1);
        }
    });
    // config path
    configCmd
        .command('path')
        .description('Show config file location')
        .action(() => {
        console.log(getGlobalConfigPath());
    });
    // config list
    configCmd
        .command('list')
        .description('Show all current settings')
        .option('--json', 'Output as JSON')
        .action((options) => {
        const config = getGlobalConfig();
        if (options.json) {
            console.log(JSON.stringify(config, null, 2));
        }
        else {
            // Read raw config to determine which values are explicit vs defaults
            const configPath = getGlobalConfigPath();
            let rawConfig = {};
            try {
                if (fs.existsSync(configPath)) {
                    rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
                }
            }
            catch {
                // If reading fails, treat all as defaults
            }
            console.log(formatValueYaml(config));
            // Annotate profile settings
            const profileSource = rawConfig.profile !== undefined ? '(explicit)' : '(default)';
            const deliverySource = rawConfig.delivery !== undefined ? '(explicit)' : '(default)';
            console.log(`\nProfile settings:`);
            console.log(`  profile: ${config.profile} ${profileSource}`);
            console.log(`  delivery: ${config.delivery} ${deliverySource}`);
            if (config.profile === 'core') {
                console.log(`  workflows: ${CORE_WORKFLOWS.join(', ')} (from core profile)`);
            }
            else if (config.workflows && config.workflows.length > 0) {
                console.log(`  workflows: ${config.workflows.join(', ')} (explicit)`);
            }
            else {
                console.log(`  workflows: (none)`);
            }
        }
    });
    // config get
    configCmd
        .command('get <key>')
        .description('Get a specific value (raw, scriptable)')
        .action((key) => {
        const config = getGlobalConfig();
        const value = getNestedValue(config, key);
        if (value === undefined) {
            process.exitCode = 1;
            return;
        }
        if (typeof value === 'object' && value !== null) {
            console.log(JSON.stringify(value));
        }
        else {
            console.log(String(value));
        }
    });
    // config set
    configCmd
        .command('set <key> <value>')
        .description('Set a value (auto-coerce types)')
        .option('--string', 'Force value to be stored as string')
        .option('--allow-unknown', 'Allow setting unknown keys')
        .action((key, value, options) => {
        const allowUnknown = Boolean(options.allowUnknown);
        const keyValidation = validateConfigKeyPath(key);
        if (!keyValidation.valid && !allowUnknown) {
            const reason = keyValidation.reason ? ` ${keyValidation.reason}.` : '';
            console.error(`Error: Invalid configuration key "${key}".${reason}`);
            console.error('Use "openspec config list" to see available keys.');
            console.error('Pass --allow-unknown to bypass this check.');
            process.exitCode = 1;
            return;
        }
        const config = getGlobalConfig();
        const coercedValue = coerceValue(value, options.string || false);
        // Create a copy to validate before saving
        const newConfig = JSON.parse(JSON.stringify(config));
        setNestedValue(newConfig, key, coercedValue);
        // Validate the new config
        const validation = validateConfig(newConfig);
        if (!validation.success) {
            console.error(`Error: Invalid configuration - ${validation.error}`);
            process.exitCode = 1;
            return;
        }
        // Apply changes and save
        setNestedValue(config, key, coercedValue);
        saveGlobalConfig(config);
        const displayValue = typeof coercedValue === 'string' ? `"${coercedValue}"` : String(coercedValue);
        console.log(`Set ${key} = ${displayValue}`);
    });
    // config unset
    configCmd
        .command('unset <key>')
        .description('Remove a key (revert to default)')
        .action((key) => {
        const config = getGlobalConfig();
        const existed = deleteNestedValue(config, key);
        if (existed) {
            saveGlobalConfig(config);
            console.log(`Unset ${key} (reverted to default)`);
        }
        else {
            console.log(`Key "${key}" was not set`);
        }
    });
    // config reset
    configCmd
        .command('reset')
        .description('Reset configuration to defaults')
        .option('--all', 'Reset all configuration (required)')
        .option('-y, --yes', 'Skip confirmation prompts')
        .action(async (options) => {
        if (!options.all) {
            console.error('Error: --all flag is required for reset');
            console.error('Usage: openspec config reset --all [-y]');
            process.exitCode = 1;
            return;
        }
        if (!options.yes) {
            const { confirm } = await import('@inquirer/prompts');
            let confirmed;
            try {
                confirmed = await confirm({
                    message: 'Reset all configuration to defaults?',
                    default: false,
                });
            }
            catch (error) {
                if (isPromptCancellationError(error)) {
                    console.log('Reset cancelled.');
                    process.exitCode = 130;
                    return;
                }
                throw error;
            }
            if (!confirmed) {
                console.log('Reset cancelled.');
                return;
            }
        }
        saveGlobalConfig({ ...DEFAULT_CONFIG });
        console.log('Configuration reset to defaults');
    });
    // config edit
    configCmd
        .command('edit')
        .description('Open config in $EDITOR')
        .action(async () => {
        const editor = process.env.EDITOR || process.env.VISUAL;
        if (!editor) {
            console.error('Error: No editor configured');
            console.error('Set the EDITOR or VISUAL environment variable to your preferred editor');
            console.error('Example: export EDITOR=vim');
            process.exitCode = 1;
            return;
        }
        const configPath = getGlobalConfigPath();
        // Ensure config file exists with defaults
        if (!fs.existsSync(configPath)) {
            saveGlobalConfig({ ...DEFAULT_CONFIG });
        }
        // Spawn editor and wait for it to close
        // Avoid shell parsing to correctly handle paths with spaces in both
        // the editor path and config path
        const child = spawn(editor, [configPath], {
            stdio: 'inherit',
            shell: false,
        });
        await new Promise((resolve, reject) => {
            child.on('close', (code) => {
                if (code === 0) {
                    resolve();
                }
                else {
                    reject(new Error(`Editor exited with code ${code}`));
                }
            });
            child.on('error', reject);
        });
        try {
            const rawConfig = fs.readFileSync(configPath, 'utf-8');
            const parsedConfig = JSON.parse(rawConfig);
            const validation = validateConfig(parsedConfig);
            if (!validation.success) {
                console.error(`Error: Invalid configuration - ${validation.error}`);
                process.exitCode = 1;
            }
        }
        catch (error) {
            if (error.code === 'ENOENT') {
                console.error(`Error: Config file not found at ${configPath}`);
            }
            else if (error instanceof SyntaxError) {
                console.error(`Error: Invalid JSON in ${configPath}`);
                console.error(error.message);
            }
            else {
                console.error(`Error: Unable to validate configuration - ${error instanceof Error ? error.message : String(error)}`);
            }
            process.exitCode = 1;
        }
    });
    // config profile [preset]
    configCmd
        .command('profile [preset]')
        .description('Configure workflow profile (interactive picker or preset shortcut)')
        .action(async (preset) => {
        // Preset shortcut: `openspec config profile core`
        if (preset === 'core') {
            const config = getGlobalConfig();
            config.profile = 'core';
            config.workflows = [...CORE_WORKFLOWS];
            // Preserve delivery setting
            saveGlobalConfig(config);
            console.log('Config updated. Run `openspec update` in your projects to apply.');
            return;
        }
        if (preset) {
            console.error(`Error: Unknown profile preset "${preset}". Available presets: core`);
            process.exitCode = 1;
            return;
        }
        // Non-interactive check
        if (!process.stdout.isTTY) {
            console.error('Interactive mode required. Use `openspec config profile core` or set config via environment/flags.');
            process.exitCode = 1;
            return;
        }
        // Interactive picker
        const { select, checkbox, confirm } = await import('@inquirer/prompts');
        const chalk = (await import('chalk')).default;
        try {
            const config = getGlobalConfig();
            const currentState = resolveCurrentProfileState(config);
            console.log(chalk.bold('\nCurrent profile settings'));
            console.log(`  Delivery: ${currentState.delivery}`);
            console.log(`  Workflows: ${formatWorkflowSummary(currentState.workflows, currentState.profile)}`);
            console.log(chalk.dim('  Delivery = where workflows are installed (skills, commands, or both)'));
            console.log(chalk.dim('  Workflows = which actions are available (propose, explore, apply, etc.)'));
            console.log();
            const action = await select({
                message: 'What do you want to configure?',
                choices: [
                    {
                        value: 'both',
                        name: 'Delivery and workflows',
                        description: 'Update install mode and available actions together',
                    },
                    {
                        value: 'delivery',
                        name: 'Delivery only',
                        description: 'Change where workflows are installed',
                    },
                    {
                        value: 'workflows',
                        name: 'Workflows only',
                        description: 'Change which workflow actions are available',
                    },
                    {
                        value: 'keep',
                        name: 'Keep current settings (exit)',
                        description: 'Leave configuration unchanged and exit',
                    },
                ],
            });
            if (action === 'keep') {
                console.log('No config changes.');
                maybeWarnConfigDrift(process.cwd(), currentState, chalk.yellow);
                return;
            }
            const nextState = {
                profile: currentState.profile,
                delivery: currentState.delivery,
                workflows: [...currentState.workflows],
            };
            if (action === 'both' || action === 'delivery') {
                const deliveryChoices = [
                    {
                        value: 'both',
                        name: 'Both (skills + commands)',
                        description: 'Install workflows as both skills and slash commands',
                    },
                    {
                        value: 'skills',
                        name: 'Skills only',
                        description: 'Install workflows only as skills',
                    },
                    {
                        value: 'commands',
                        name: 'Commands only',
                        description: 'Install workflows only as slash commands',
                    },
                ];
                for (const choice of deliveryChoices) {
                    if (choice.value === currentState.delivery) {
                        choice.name += ' [current]';
                    }
                }
                nextState.delivery = await select({
                    message: 'Delivery mode (how workflows are installed):',
                    choices: deliveryChoices,
                    default: currentState.delivery,
                });
            }
            if (action === 'both' || action === 'workflows') {
                const formatWorkflowChoice = (workflow) => {
                    const metadata = WORKFLOW_PROMPT_META[workflow] ?? {
                        name: workflow,
                        description: `Workflow: ${workflow}`,
                    };
                    return {
                        value: workflow,
                        name: metadata.name,
                        description: metadata.description,
                        short: metadata.name,
                        checked: currentState.workflows.includes(workflow),
                    };
                };
                const selectedWorkflows = await checkbox({
                    message: 'Select workflows to make available:',
                    instructions: 'Space to toggle, Enter to confirm',
                    pageSize: ALL_WORKFLOWS.length,
                    theme: {
                        icon: {
                            checked: '[x]',
                            unchecked: '[ ]',
                        },
                    },
                    choices: ALL_WORKFLOWS.map(formatWorkflowChoice),
                });
                nextState.workflows = selectedWorkflows;
                nextState.profile = deriveProfileFromWorkflowSelection(selectedWorkflows);
            }
            const diff = diffProfileState(currentState, nextState);
            if (!diff.hasChanges) {
                console.log('No config changes.');
                maybeWarnConfigDrift(process.cwd(), nextState, chalk.yellow);
                return;
            }
            console.log(chalk.bold('\nConfig changes:'));
            for (const line of diff.lines) {
                console.log(`  ${line}`);
            }
            console.log();
            config.profile = nextState.profile;
            config.delivery = nextState.delivery;
            config.workflows = nextState.workflows;
            saveGlobalConfig(config);
            // Check if inside an OpenSpec project
            const projectDir = process.cwd();
            const openspecDir = path.join(projectDir, OPENSPEC_DIR_NAME);
            if (fs.existsSync(openspecDir)) {
                const applyNow = await confirm({
                    message: 'Apply changes to this project now?',
                    default: true,
                });
                if (applyNow) {
                    try {
                        execSync('npx openspec update', { stdio: 'inherit', cwd: projectDir });
                        console.log('Run `openspec update` in your other projects to apply.');
                    }
                    catch {
                        console.error('`openspec update` failed. Please run it manually to apply the profile changes.');
                        process.exitCode = 1;
                    }
                    return;
                }
            }
            console.log('Config updated. Run `openspec update` in your projects to apply.');
        }
        catch (error) {
            if (isPromptCancellationError(error)) {
                console.log('Config profile cancelled.');
                process.exitCode = 130;
                return;
            }
            throw error;
        }
    });
}
//# sourceMappingURL=config.js.map