/**
 * Init Command
 *
 * Sets up OpenSpec with Agent Skills and /opsx:* slash commands.
 * This is the unified setup command that replaces both the old init and experimental commands.
 */
import path from 'path';
import chalk from 'chalk';
import ora from 'ora';
import * as fs from 'fs';
import { createRequire } from 'module';
import { FileSystemUtils } from '../utils/file-system.js';
import { transformToHyphenCommands } from '../utils/command-references.js';
import { AI_TOOLS, OPENSPEC_DIR_NAME, } from './config.js';
import { PALETTE } from './styles/palette.js';
import { isInteractive } from '../utils/interactive.js';
import { serializeConfig } from './config-prompts.js';
import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatCleanupSummary, formatDetectionSummary, } from './legacy-cleanup.js';
import { getToolsWithSkillsDir, getToolStates, getSkillTemplates, getCommandContents, generateSkillContent, } from './shared/index.js';
const require = createRequire(import.meta.url);
const { version: OPENSPEC_VERSION } = require('../../package.json');
// -----------------------------------------------------------------------------
// Constants
// -----------------------------------------------------------------------------
const DEFAULT_SCHEMA = 'spec-driven';
const PROGRESS_SPINNER = {
    interval: 80,
    frames: ['░░░', '▒░░', '▒▒░', '▒▒▒', '▓▒▒', '▓▓▒', '▓▓▓', '▒▓▓', '░▒▓'],
};
// -----------------------------------------------------------------------------
// Init Command Class
// -----------------------------------------------------------------------------
export class InitCommand {
    toolsArg;
    force;
    interactiveOption;
    constructor(options = {}) {
        this.toolsArg = options.tools;
        this.force = options.force ?? false;
        this.interactiveOption = options.interactive;
    }
    async execute(targetPath) {
        const projectPath = path.resolve(targetPath);
        const openspecDir = OPENSPEC_DIR_NAME;
        const openspecPath = path.join(projectPath, openspecDir);
        // Validation happens silently in the background
        const extendMode = await this.validate(projectPath, openspecPath);
        // Check for legacy artifacts and handle cleanup
        await this.handleLegacyCleanup(projectPath, extendMode);
        // Show animated welcome screen (interactive mode only)
        const canPrompt = this.canPromptInteractively();
        if (canPrompt) {
            const { showWelcomeScreen } = await import('../ui/welcome-screen.js');
            await showWelcomeScreen();
        }
        // Get tool states before processing
        const toolStates = getToolStates(projectPath);
        // Get tool selection
        const selectedToolIds = await this.getSelectedTools(toolStates, extendMode);
        // Validate selected tools
        const validatedTools = this.validateTools(selectedToolIds, toolStates);
        // Create directory structure and config
        await this.createDirectoryStructure(openspecPath, extendMode);
        // Generate skills and commands for each tool
        const results = await this.generateSkillsAndCommands(projectPath, validatedTools);
        // Create config.yaml if needed
        const configStatus = await this.createConfig(openspecPath, extendMode);
        // Display success message
        this.displaySuccessMessage(projectPath, validatedTools, results, configStatus);
    }
    // ═══════════════════════════════════════════════════════════
    // VALIDATION & SETUP
    // ═══════════════════════════════════════════════════════════
    async validate(projectPath, openspecPath) {
        const extendMode = await FileSystemUtils.directoryExists(openspecPath);
        // Check write permissions
        if (!(await FileSystemUtils.ensureWritePermissions(projectPath))) {
            throw new Error(`Insufficient permissions to write to ${projectPath}`);
        }
        return extendMode;
    }
    canPromptInteractively() {
        if (this.interactiveOption === false)
            return false;
        if (this.toolsArg !== undefined)
            return false;
        return isInteractive({ interactive: this.interactiveOption });
    }
    // ═══════════════════════════════════════════════════════════
    // LEGACY CLEANUP
    // ═══════════════════════════════════════════════════════════
    async handleLegacyCleanup(projectPath, extendMode) {
        // Detect legacy artifacts
        const detection = await detectLegacyArtifacts(projectPath);
        if (!detection.hasLegacyArtifacts) {
            return; // No legacy artifacts found
        }
        // Show what was detected
        console.log();
        console.log(formatDetectionSummary(detection));
        console.log();
        const canPrompt = this.canPromptInteractively();
        if (this.force) {
            // --force flag: proceed with cleanup automatically
            await this.performLegacyCleanup(projectPath, detection);
            return;
        }
        if (!canPrompt) {
            // Non-interactive mode without --force: abort
            console.log(chalk.red('Legacy files detected in non-interactive mode.'));
            console.log(chalk.dim('Run interactively to upgrade, or use --force to auto-cleanup.'));
            process.exit(1);
        }
        // Interactive mode: prompt for confirmation
        const { confirm } = await import('@inquirer/prompts');
        const shouldCleanup = await confirm({
            message: 'Upgrade and clean up legacy files?',
            default: true,
        });
        if (!shouldCleanup) {
            console.log(chalk.dim('Initialization cancelled.'));
            console.log(chalk.dim('Run with --force to skip this prompt, or manually remove legacy files.'));
            process.exit(0);
        }
        await this.performLegacyCleanup(projectPath, detection);
    }
    async performLegacyCleanup(projectPath, detection) {
        const spinner = ora('Cleaning up legacy files...').start();
        const result = await cleanupLegacyArtifacts(projectPath, detection);
        spinner.succeed('Legacy files cleaned up');
        const summary = formatCleanupSummary(result);
        if (summary) {
            console.log();
            console.log(summary);
        }
        console.log();
    }
    // ═══════════════════════════════════════════════════════════
    // TOOL SELECTION
    // ═══════════════════════════════════════════════════════════
    async getSelectedTools(toolStates, extendMode) {
        // Check for --tools flag first
        const nonInteractiveSelection = this.resolveToolsArg();
        if (nonInteractiveSelection !== null) {
            return nonInteractiveSelection;
        }
        const validTools = getToolsWithSkillsDir();
        const canPrompt = this.canPromptInteractively();
        if (!canPrompt || validTools.length === 0) {
            throw new Error(`Missing required option --tools. Valid tools:\n  ${validTools.join('\n  ')}\n\nUse --tools all, --tools none, or --tools claude,cursor,...`);
        }
        // Interactive mode: show searchable multi-select
        const { searchableMultiSelect } = await import('../prompts/searchable-multi-select.js');
        // Build choices with configured status and sort configured tools first
        const sortedChoices = validTools
            .map((toolId) => {
            const tool = AI_TOOLS.find((t) => t.value === toolId);
            const status = toolStates.get(toolId);
            const configured = status?.configured ?? false;
            return {
                name: tool?.name || toolId,
                value: toolId,
                configured,
                preSelected: configured, // Pre-select configured tools for easy refresh
            };
        })
            .sort((a, b) => {
            // Configured tools first
            if (a.configured && !b.configured)
                return -1;
            if (!a.configured && b.configured)
                return 1;
            return 0;
        });
        const selectedTools = await searchableMultiSelect({
            message: `Select tools to set up (${validTools.length} available)`,
            pageSize: 15,
            choices: sortedChoices,
            validate: (selected) => selected.length > 0 || 'Select at least one tool',
        });
        if (selectedTools.length === 0) {
            throw new Error('At least one tool must be selected');
        }
        return selectedTools;
    }
    resolveToolsArg() {
        if (typeof this.toolsArg === 'undefined') {
            return null;
        }
        const raw = this.toolsArg.trim();
        if (raw.length === 0) {
            throw new Error('The --tools option requires a value. Use "all", "none", or a comma-separated list of tool IDs.');
        }
        const availableTools = getToolsWithSkillsDir();
        const availableSet = new Set(availableTools);
        const availableList = ['all', 'none', ...availableTools].join(', ');
        const lowerRaw = raw.toLowerCase();
        if (lowerRaw === 'all') {
            return availableTools;
        }
        if (lowerRaw === 'none') {
            return [];
        }
        const tokens = raw
            .split(',')
            .map((token) => token.trim())
            .filter((token) => token.length > 0);
        if (tokens.length === 0) {
            throw new Error('The --tools option requires at least one tool ID when not using "all" or "none".');
        }
        const normalizedTokens = tokens.map((token) => token.toLowerCase());
        if (normalizedTokens.some((token) => token === 'all' || token === 'none')) {
            throw new Error('Cannot combine reserved values "all" or "none" with specific tool IDs.');
        }
        const invalidTokens = tokens.filter((_token, index) => !availableSet.has(normalizedTokens[index]));
        if (invalidTokens.length > 0) {
            throw new Error(`Invalid tool(s): ${invalidTokens.join(', ')}. Available values: ${availableList}`);
        }
        // Deduplicate while preserving order
        const deduped = [];
        for (const token of normalizedTokens) {
            if (!deduped.includes(token)) {
                deduped.push(token);
            }
        }
        return deduped;
    }
    validateTools(toolIds, toolStates) {
        const validatedTools = [];
        for (const toolId of toolIds) {
            const tool = AI_TOOLS.find((t) => t.value === toolId);
            if (!tool) {
                const validToolIds = getToolsWithSkillsDir();
                throw new Error(`Unknown tool '${toolId}'. Valid tools:\n  ${validToolIds.join('\n  ')}`);
            }
            if (!tool.skillsDir) {
                const validToolsWithSkills = getToolsWithSkillsDir();
                throw new Error(`Tool '${toolId}' does not support skill generation.\nTools with skill generation support:\n  ${validToolsWithSkills.join('\n  ')}`);
            }
            const preState = toolStates.get(tool.value);
            validatedTools.push({
                value: tool.value,
                name: tool.name,
                skillsDir: tool.skillsDir,
                wasConfigured: preState?.configured ?? false,
            });
        }
        return validatedTools;
    }
    // ═══════════════════════════════════════════════════════════
    // DIRECTORY STRUCTURE
    // ═══════════════════════════════════════════════════════════
    async createDirectoryStructure(openspecPath, extendMode) {
        if (extendMode) {
            // In extend mode, just ensure directories exist without spinner
            const directories = [
                openspecPath,
                path.join(openspecPath, 'specs'),
                path.join(openspecPath, 'changes'),
                path.join(openspecPath, 'changes', 'archive'),
            ];
            for (const dir of directories) {
                await FileSystemUtils.createDirectory(dir);
            }
            return;
        }
        const spinner = this.startSpinner('Creating OpenSpec structure...');
        const directories = [
            openspecPath,
            path.join(openspecPath, 'specs'),
            path.join(openspecPath, 'changes'),
            path.join(openspecPath, 'changes', 'archive'),
        ];
        for (const dir of directories) {
            await FileSystemUtils.createDirectory(dir);
        }
        spinner.stopAndPersist({
            symbol: PALETTE.white('▌'),
            text: PALETTE.white('OpenSpec structure created'),
        });
    }
    // ═══════════════════════════════════════════════════════════
    // SKILL & COMMAND GENERATION
    // ═══════════════════════════════════════════════════════════
    async generateSkillsAndCommands(projectPath, tools) {
        const createdTools = [];
        const refreshedTools = [];
        const failedTools = [];
        const commandsSkipped = [];
        // Get skill and command templates once (shared across all tools)
        const skillTemplates = getSkillTemplates();
        const commandContents = getCommandContents();
        // Process each tool
        for (const tool of tools) {
            const spinner = ora(`Setting up ${tool.name}...`).start();
            try {
                // Use tool-specific skillsDir
                const skillsDir = path.join(projectPath, tool.skillsDir, 'skills');
                // Create skill directories and SKILL.md files
                for (const { template, dirName } of skillTemplates) {
                    const skillDir = path.join(skillsDir, dirName);
                    const skillFile = path.join(skillDir, 'SKILL.md');
                    // Generate SKILL.md content with YAML frontmatter including generatedBy
                    // Use hyphen-based command references for OpenCode
                    const transformer = tool.value === 'opencode' ? transformToHyphenCommands : undefined;
                    const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
                    // Write the skill file
                    await FileSystemUtils.writeFile(skillFile, skillContent);
                }
                // Generate commands using the adapter system
                const adapter = CommandAdapterRegistry.get(tool.value);
                if (adapter) {
                    const generatedCommands = generateCommands(commandContents, adapter);
                    for (const cmd of generatedCommands) {
                        const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(projectPath, cmd.path);
                        await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
                    }
                }
                else {
                    commandsSkipped.push(tool.value);
                }
                spinner.succeed(`Setup complete for ${tool.name}`);
                if (tool.wasConfigured) {
                    refreshedTools.push(tool);
                }
                else {
                    createdTools.push(tool);
                }
            }
            catch (error) {
                spinner.fail(`Failed for ${tool.name}`);
                failedTools.push({ name: tool.name, error: error });
            }
        }
        return { createdTools, refreshedTools, failedTools, commandsSkipped };
    }
    // ═══════════════════════════════════════════════════════════
    // CONFIG FILE
    // ═══════════════════════════════════════════════════════════
    async createConfig(openspecPath, extendMode) {
        const configPath = path.join(openspecPath, 'config.yaml');
        const configYmlPath = path.join(openspecPath, 'config.yml');
        const configYamlExists = fs.existsSync(configPath);
        const configYmlExists = fs.existsSync(configYmlPath);
        if (configYamlExists || configYmlExists) {
            return 'exists';
        }
        // In non-interactive mode without --force, skip config creation
        if (!this.canPromptInteractively() && !this.force) {
            return 'skipped';
        }
        try {
            const yamlContent = serializeConfig({ schema: DEFAULT_SCHEMA });
            await FileSystemUtils.writeFile(configPath, yamlContent);
            return 'created';
        }
        catch {
            return 'skipped';
        }
    }
    // ═══════════════════════════════════════════════════════════
    // UI & OUTPUT
    // ═══════════════════════════════════════════════════════════
    displaySuccessMessage(projectPath, tools, results, configStatus) {
        console.log();
        console.log(chalk.bold('OpenSpec Setup Complete'));
        console.log();
        // Show created vs refreshed tools
        if (results.createdTools.length > 0) {
            console.log(`Created: ${results.createdTools.map((t) => t.name).join(', ')}`);
        }
        if (results.refreshedTools.length > 0) {
            console.log(`Refreshed: ${results.refreshedTools.map((t) => t.name).join(', ')}`);
        }
        // Show counts
        const successfulTools = [...results.createdTools, ...results.refreshedTools];
        if (successfulTools.length > 0) {
            const toolDirs = [...new Set(successfulTools.map((t) => t.skillsDir))].join(', ');
            const hasCommands = results.commandsSkipped.length < successfulTools.length;
            if (hasCommands) {
                console.log(`${getSkillTemplates().length} skills and ${getCommandContents().length} commands in ${toolDirs}/`);
            }
            else {
                console.log(`${getSkillTemplates().length} skills in ${toolDirs}/`);
            }
        }
        // Show failures
        if (results.failedTools.length > 0) {
            console.log(chalk.red(`Failed: ${results.failedTools.map((f) => `${f.name} (${f.error.message})`).join(', ')}`));
        }
        // Show skipped commands
        if (results.commandsSkipped.length > 0) {
            console.log(chalk.dim(`Commands skipped for: ${results.commandsSkipped.join(', ')} (no adapter)`));
        }
        // Config status
        if (configStatus === 'created') {
            console.log(`Config: openspec/config.yaml (schema: ${DEFAULT_SCHEMA})`);
        }
        else if (configStatus === 'exists') {
            // Show actual filename (config.yaml or config.yml)
            const configYaml = path.join(projectPath, OPENSPEC_DIR_NAME, 'config.yaml');
            const configYml = path.join(projectPath, OPENSPEC_DIR_NAME, 'config.yml');
            const configName = fs.existsSync(configYaml) ? 'config.yaml' : fs.existsSync(configYml) ? 'config.yml' : 'config.yaml';
            console.log(`Config: openspec/${configName} (exists)`);
        }
        else {
            console.log(chalk.dim(`Config: skipped (non-interactive mode)`));
        }
        // Getting started
        console.log();
        console.log(chalk.bold('Getting started:'));
        console.log('  /opsx:new       Start a new change');
        console.log('  /opsx:continue  Create the next artifact');
        console.log('  /opsx:apply     Implement tasks');
        // Links
        console.log();
        console.log(`Learn more: ${chalk.cyan('https://github.com/Fission-AI/OpenSpec')}`);
        console.log(`Feedback:   ${chalk.cyan('https://github.com/Fission-AI/OpenSpec/issues')}`);
        // Restart instruction if any tools were configured
        if (results.createdTools.length > 0 || results.refreshedTools.length > 0) {
            console.log();
            console.log(chalk.white('Restart your IDE for slash commands to take effect.'));
        }
        console.log();
    }
    startSpinner(text) {
        return ora({
            text,
            stream: process.stdout,
            color: 'gray',
            spinner: PROGRESS_SPINNER,
        }).start();
    }
}
//# sourceMappingURL=init.js.map