import { Command } from 'commander';
import { createRequire } from 'module';
import ora from 'ora';
import path from 'path';
import { promises as fs } from 'fs';
import { AI_TOOLS } from '../core/config.js';
import { UpdateCommand } from '../core/update.js';
import { ListCommand } from '../core/list.js';
import { ArchiveCommand } from '../core/archive.js';
import { ViewCommand } from '../core/view.js';
import { registerSpecCommand } from '../commands/spec.js';
import { ChangeCommand } from '../commands/change.js';
import { ValidateCommand } from '../commands/validate.js';
import { ShowCommand } from '../commands/show.js';
import { CompletionCommand } from '../commands/completion.js';
import { FeedbackCommand } from '../commands/feedback.js';
import { registerConfigCommand } from '../commands/config.js';
import { registerSchemaCommand } from '../commands/schema.js';
import { statusCommand, instructionsCommand, applyInstructionsCommand, templatesCommand, schemasCommand, newChangeCommand, DEFAULT_SCHEMA, } from '../commands/workflow/index.js';
import { maybeShowTelemetryNotice, trackCommand, shutdown } from '../telemetry/index.js';
const program = new Command();
const require = createRequire(import.meta.url);
const { version } = require('../../package.json');
/**
 * Get the full command path for nested commands.
 * For example: 'change show' -> 'change:show'
 */
function getCommandPath(command) {
    const names = [];
    let current = command;
    while (current) {
        const name = current.name();
        // Skip the root 'openspec' command
        if (name && name !== 'openspec') {
            names.unshift(name);
        }
        current = current.parent;
    }
    return names.join(':') || 'openspec';
}
program
    .name('openspec')
    .description('AI-native system for spec-driven development')
    .version(version);
// Global options
program.option('--no-color', 'Disable color output');
// Apply global flags and telemetry before any command runs
// Note: preAction receives (thisCommand, actionCommand) where:
// - thisCommand: the command where hook was added (root program)
// - actionCommand: the command actually being executed (subcommand)
program.hook('preAction', async (thisCommand, actionCommand) => {
    const opts = thisCommand.opts();
    if (opts.color === false) {
        process.env.NO_COLOR = '1';
    }
    // Show first-run telemetry notice (if not seen)
    await maybeShowTelemetryNotice();
    // Track command execution (use actionCommand to get the actual subcommand)
    const commandPath = getCommandPath(actionCommand);
    await trackCommand(commandPath, version);
});
// Shutdown telemetry after command completes
program.hook('postAction', async () => {
    await shutdown();
});
const availableToolIds = AI_TOOLS.filter((tool) => tool.skillsDir).map((tool) => tool.value);
const toolsOptionDescription = `Configure AI tools non-interactively. Use "all", "none", or a comma-separated list of: ${availableToolIds.join(', ')}`;
program
    .command('init [path]')
    .description('Initialize OpenSpec in your project')
    .option('--tools <tools>', toolsOptionDescription)
    .option('--force', 'Auto-cleanup legacy files without prompting')
    .option('--profile <profile>', 'Override global config profile (core or custom)')
    .action(async (targetPath = '.', options) => {
    try {
        // Validate that the path is a valid directory
        const resolvedPath = path.resolve(targetPath);
        try {
            const stats = await fs.stat(resolvedPath);
            if (!stats.isDirectory()) {
                throw new Error(`Path "${targetPath}" is not a directory`);
            }
        }
        catch (error) {
            if (error.code === 'ENOENT') {
                // Directory doesn't exist, but we can create it
                console.log(`Directory "${targetPath}" doesn't exist, it will be created.`);
            }
            else if (error.message && error.message.includes('not a directory')) {
                throw error;
            }
            else {
                throw new Error(`Cannot access path "${targetPath}": ${error.message}`);
            }
        }
        const { InitCommand } = await import('../core/init.js');
        const initCommand = new InitCommand({
            tools: options?.tools,
            force: options?.force,
            profile: options?.profile,
        });
        await initCommand.execute(targetPath);
    }
    catch (error) {
        console.log(); // Empty line for spacing
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
// Hidden alias: 'experimental' -> 'init' for backwards compatibility
program
    .command('experimental', { hidden: true })
    .description('Alias for init (deprecated)')
    .option('--tool <tool-id>', 'Target AI tool (maps to --tools)')
    .option('--no-interactive', 'Disable interactive prompts')
    .action(async (options) => {
    try {
        console.log('Note: "openspec experimental" is deprecated. Use "openspec init" instead.');
        const { InitCommand } = await import('../core/init.js');
        const initCommand = new InitCommand({
            tools: options?.tool,
            interactive: options?.noInteractive === true ? false : undefined,
        });
        await initCommand.execute('.');
    }
    catch (error) {
        console.log();
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
program
    .command('update [path]')
    .description('Update OpenSpec instruction files')
    .option('--force', 'Force update even when tools are up to date')
    .action(async (targetPath = '.', options) => {
    try {
        const resolvedPath = path.resolve(targetPath);
        const updateCommand = new UpdateCommand({ force: options?.force });
        await updateCommand.execute(resolvedPath);
    }
    catch (error) {
        console.log(); // Empty line for spacing
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
program
    .command('list')
    .description('List items (changes by default). Use --specs to list specs.')
    .option('--specs', 'List specs instead of changes')
    .option('--changes', 'List changes explicitly (default)')
    .option('--sort <order>', 'Sort order: "recent" (default) or "name"', 'recent')
    .option('--json', 'Output as JSON (for programmatic use)')
    .action(async (options) => {
    try {
        const listCommand = new ListCommand();
        const mode = options?.specs ? 'specs' : 'changes';
        const sort = options?.sort === 'name' ? 'name' : 'recent';
        await listCommand.execute('.', mode, { sort, json: options?.json });
    }
    catch (error) {
        console.log(); // Empty line for spacing
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
program
    .command('view')
    .description('Display an interactive dashboard of specs and changes')
    .action(async () => {
    try {
        const viewCommand = new ViewCommand();
        await viewCommand.execute('.');
    }
    catch (error) {
        console.log(); // Empty line for spacing
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
// Change command with subcommands
const changeCmd = program
    .command('change')
    .description('Manage OpenSpec change proposals');
// Deprecation notice for noun-based commands
changeCmd.hook('preAction', () => {
    console.error('Warning: The "openspec change ..." commands are deprecated. Prefer verb-first commands (e.g., "openspec list", "openspec validate --changes").');
});
changeCmd
    .command('show [change-name]')
    .description('Show a change proposal in JSON or markdown format')
    .option('--json', 'Output as JSON')
    .option('--deltas-only', 'Show only deltas (JSON only)')
    .option('--requirements-only', 'Alias for --deltas-only (deprecated)')
    .option('--no-interactive', 'Disable interactive prompts')
    .action(async (changeName, options) => {
    try {
        const changeCommand = new ChangeCommand();
        await changeCommand.show(changeName, options);
    }
    catch (error) {
        console.error(`Error: ${error.message}`);
        process.exitCode = 1;
    }
});
changeCmd
    .command('list')
    .description('List all active changes (DEPRECATED: use "openspec list" instead)')
    .option('--json', 'Output as JSON')
    .option('--long', 'Show id and title with counts')
    .action(async (options) => {
    try {
        console.error('Warning: "openspec change list" is deprecated. Use "openspec list".');
        const changeCommand = new ChangeCommand();
        await changeCommand.list(options);
    }
    catch (error) {
        console.error(`Error: ${error.message}`);
        process.exitCode = 1;
    }
});
changeCmd
    .command('validate [change-name]')
    .description('Validate a change proposal')
    .option('--strict', 'Enable strict validation mode')
    .option('--json', 'Output validation report as JSON')
    .option('--no-interactive', 'Disable interactive prompts')
    .action(async (changeName, options) => {
    try {
        const changeCommand = new ChangeCommand();
        await changeCommand.validate(changeName, options);
        if (typeof process.exitCode === 'number' && process.exitCode !== 0) {
            process.exit(process.exitCode);
        }
    }
    catch (error) {
        console.error(`Error: ${error.message}`);
        process.exitCode = 1;
    }
});
program
    .command('archive [change-name]')
    .description('Archive a completed change and update main specs')
    .option('-y, --yes', 'Skip confirmation prompts')
    .option('--skip-specs', 'Skip spec update operations (useful for infrastructure, tooling, or doc-only changes)')
    .option('--no-validate', 'Skip validation (not recommended, requires confirmation)')
    .action(async (changeName, options) => {
    try {
        const archiveCommand = new ArchiveCommand();
        await archiveCommand.execute(changeName, options);
    }
    catch (error) {
        console.log(); // Empty line for spacing
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
registerSpecCommand(program);
registerConfigCommand(program);
registerSchemaCommand(program);
// Top-level validate command
program
    .command('validate [item-name]')
    .description('Validate changes and specs')
    .option('--all', 'Validate all changes and specs')
    .option('--changes', 'Validate all changes')
    .option('--specs', 'Validate all specs')
    .option('--type <type>', 'Specify item type when ambiguous: change|spec')
    .option('--strict', 'Enable strict validation mode')
    .option('--json', 'Output validation results as JSON')
    .option('--concurrency <n>', 'Max concurrent validations (defaults to env OPENSPEC_CONCURRENCY or 6)')
    .option('--no-interactive', 'Disable interactive prompts')
    .action(async (itemName, options) => {
    try {
        const validateCommand = new ValidateCommand();
        await validateCommand.execute(itemName, options);
    }
    catch (error) {
        console.log();
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
// Top-level show command
program
    .command('show [item-name]')
    .description('Show a change or spec')
    .option('--json', 'Output as JSON')
    .option('--type <type>', 'Specify item type when ambiguous: change|spec')
    .option('--no-interactive', 'Disable interactive prompts')
    // change-only flags
    .option('--deltas-only', 'Show only deltas (JSON only, change)')
    .option('--requirements-only', 'Alias for --deltas-only (deprecated, change)')
    // spec-only flags
    .option('--requirements', 'JSON only: Show only requirements (exclude scenarios)')
    .option('--no-scenarios', 'JSON only: Exclude scenario content')
    .option('-r, --requirement <id>', 'JSON only: Show specific requirement by ID (1-based)')
    // allow unknown options to pass-through to underlying command implementation
    .allowUnknownOption(true)
    .action(async (itemName, options) => {
    try {
        const showCommand = new ShowCommand();
        await showCommand.execute(itemName, options ?? {});
    }
    catch (error) {
        console.log();
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
// Feedback command
program
    .command('feedback <message>')
    .description('Submit feedback about OpenSpec')
    .option('--body <text>', 'Detailed description for the feedback')
    .action(async (message, options) => {
    try {
        const feedbackCommand = new FeedbackCommand();
        await feedbackCommand.execute(message, options);
    }
    catch (error) {
        console.log();
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
// Completion command with subcommands
const completionCmd = program
    .command('completion')
    .description('Manage shell completions for OpenSpec CLI');
completionCmd
    .command('generate [shell]')
    .description('Generate completion script for a shell (outputs to stdout)')
    .action(async (shell) => {
    try {
        const completionCommand = new CompletionCommand();
        await completionCommand.generate({ shell });
    }
    catch (error) {
        console.log();
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
completionCmd
    .command('install [shell]')
    .description('Install completion script for a shell')
    .option('--verbose', 'Show detailed installation output')
    .action(async (shell, options) => {
    try {
        const completionCommand = new CompletionCommand();
        await completionCommand.install({ shell, verbose: options?.verbose });
    }
    catch (error) {
        console.log();
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
completionCmd
    .command('uninstall [shell]')
    .description('Uninstall completion script for a shell')
    .option('-y, --yes', 'Skip confirmation prompts')
    .action(async (shell, options) => {
    try {
        const completionCommand = new CompletionCommand();
        await completionCommand.uninstall({ shell, yes: options?.yes });
    }
    catch (error) {
        console.log();
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
// Hidden command for machine-readable completion data
program
    .command('__complete <type>', { hidden: true })
    .description('Output completion data in machine-readable format (internal use)')
    .action(async (type) => {
    try {
        const completionCommand = new CompletionCommand();
        await completionCommand.complete({ type });
    }
    catch (error) {
        // Silently fail for graceful shell completion experience
        process.exitCode = 1;
    }
});
// ═══════════════════════════════════════════════════════════
// Workflow Commands (formerly experimental)
// ═══════════════════════════════════════════════════════════
// Status command
program
    .command('status')
    .description('Display artifact completion status for a change')
    .option('--change <id>', 'Change name to show status for')
    .option('--schema <name>', 'Schema override (auto-detected from config.yaml)')
    .option('--json', 'Output as JSON')
    .action(async (options) => {
    try {
        await statusCommand(options);
    }
    catch (error) {
        console.log();
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
// Instructions command
program
    .command('instructions [artifact]')
    .description('Output enriched instructions for creating an artifact or applying tasks')
    .option('--change <id>', 'Change name')
    .option('--schema <name>', 'Schema override (auto-detected from config.yaml)')
    .option('--json', 'Output as JSON')
    .action(async (artifactId, options) => {
    try {
        // Special case: "apply" is not an artifact, but a command to get apply instructions
        if (artifactId === 'apply') {
            await applyInstructionsCommand(options);
        }
        else {
            await instructionsCommand(artifactId, options);
        }
    }
    catch (error) {
        console.log();
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
// Templates command
program
    .command('templates')
    .description('Show resolved template paths for all artifacts in a schema')
    .option('--schema <name>', `Schema to use (default: ${DEFAULT_SCHEMA})`)
    .option('--json', 'Output as JSON mapping artifact IDs to template paths')
    .action(async (options) => {
    try {
        await templatesCommand(options);
    }
    catch (error) {
        console.log();
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
// Schemas command
program
    .command('schemas')
    .description('List available workflow schemas with descriptions')
    .option('--json', 'Output as JSON (for agent use)')
    .action(async (options) => {
    try {
        await schemasCommand(options);
    }
    catch (error) {
        console.log();
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
// New command group with change subcommand
const newCmd = program.command('new').description('Create new items');
newCmd
    .command('change <name>')
    .description('Create a new change directory')
    .option('--description <text>', 'Description to add to README.md')
    .option('--schema <name>', `Workflow schema to use (default: ${DEFAULT_SCHEMA})`)
    .action(async (name, options) => {
    try {
        await newChangeCommand(name, options);
    }
    catch (error) {
        console.log();
        ora().fail(`Error: ${error.message}`);
        process.exit(1);
    }
});
program.parse();
//# sourceMappingURL=index.js.map