import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync, readFileSync, writeFileSync, unlinkSync, rmdirSync, appendFileSync } from 'fs';
import { join, dirname, basename } from 'path';
import { homedir, platform } from 'os';
import { fileURLToPath } from 'url';
import { execSync } from 'child_process';
import inquirer from 'inquirer';
import chalk from 'chalk';
import { showHeader, createSpinner, info, warning, error, summaryBox, divider } from '../utils/ui.js';
import { createManifest, addFileEntry, addDirectoryEntry, saveManifest, findManifest, getAllManifests } from '../core/manifest.js';
import { validatePath } from '../utils/path-resolver.js';
// Git Bash fix markers
const GITBASH_FIX_START = '# >>> ccw gitbash fix';
const GITBASH_FIX_END = '# <<< ccw gitbash fix';
// Supported shell configuration files
const SHELL_CONFIG_FILES = [
    { name: '.bashrc', description: 'Bash configuration (recommended for Git Bash)' },
    { name: '.bash_profile', description: 'Bash login shell configuration' },
    { name: '.profile', description: 'Generic shell profile' },
    { name: '.zshrc', description: 'Zsh configuration' }
];
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Source directories to install (includes .codex with prompts folder)
const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen'];
// Subdirectories that should always be installed to global (~/.claude/)
const GLOBAL_SUBDIRS = ['workflows', 'scripts', 'templates'];
// Files that should be excluded from cleanup (user-specific settings)
const EXCLUDED_FILES = ['settings.json', 'settings.local.json'];
// Get package root directory (ccw/src/commands -> ccw)
function getPackageRoot() {
    return join(__dirname, '..', '..');
}
// Get source installation directory (parent of ccw)
function getSourceDir() {
    return join(getPackageRoot(), '..');
}
/**
 * Install command handler
 * @param {Object} options - Command options
 */
export async function installCommand(options) {
    const version = getVersion();
    // Show beautiful header
    showHeader(version);
    // Check for existing installations
    const existingManifests = getAllManifests();
    if (existingManifests.length > 0 && !options.force) {
        info('Existing installations detected:');
        console.log('');
        existingManifests.forEach((m, i) => {
            console.log(chalk.gray(`  ${i + 1}. ${m.installation_mode} - ${m.installation_path}`));
            console.log(chalk.gray(`     Installed: ${new Date(m.installation_date).toLocaleDateString()}`));
        });
        console.log('');
        const { proceed } = await inquirer.prompt([{
                type: 'confirm',
                name: 'proceed',
                message: 'Continue with new installation?',
                default: true
            }]);
        if (!proceed) {
            info('Installation cancelled');
            return;
        }
    }
    // Local installation from package source
    const sourceDir = getSourceDir();
    // Interactive mode selection
    const mode = options.mode || await selectMode();
    let installPath;
    if (mode === 'Global') {
        installPath = homedir();
        info(`Global installation to: ${installPath}`);
    }
    else {
        const inputPath = options.path || await selectPath();
        // Validate the installation path
        const pathValidation = validatePath(inputPath, { mustExist: true });
        if (!pathValidation.valid || !pathValidation.path) {
            error(`Invalid installation path: ${pathValidation.error}`);
            process.exit(1);
        }
        installPath = pathValidation.path;
        info(`Path installation to: ${installPath}`);
    }
    // Validate source directories exist
    const availableDirs = SOURCE_DIRS.filter(dir => existsSync(join(sourceDir, dir)));
    if (availableDirs.length === 0) {
        error('No source directories found to install.');
        error(`Expected directories in: ${sourceDir}`);
        process.exit(1);
    }
    console.log('');
    info(`Found ${availableDirs.length} directories to install: ${availableDirs.join(', ')}`);
    // Show what will be installed including .codex subdirectories
    if (availableDirs.includes('.codex')) {
        const codexPath = join(sourceDir, '.codex');
        // Show prompts info
        const promptsPath = join(codexPath, 'prompts');
        if (existsSync(promptsPath)) {
            const promptFiles = readdirSync(promptsPath, { recursive: true }).filter(f => statSync(join(promptsPath, f.toString())).isFile());
            info(`  └─ .codex/prompts: ${promptFiles.length} files`);
        }
        // Show agents info
        const agentsPath = join(codexPath, 'agents');
        if (existsSync(agentsPath)) {
            const agentFiles = readdirSync(agentsPath).filter(f => f.endsWith('.md'));
            info(`  └─ .codex/agents: ${agentFiles.length} agent definitions`);
        }
        // Show skills info
        const skillsPath = join(codexPath, 'skills');
        if (existsSync(skillsPath)) {
            const skillDirs = readdirSync(skillsPath).filter(f => statSync(join(skillsPath, f)).isDirectory());
            info(`  └─ .codex/skills: ${skillDirs.length} skills`);
        }
    }
    divider();
    // Check for existing installation manifest
    const existingManifest = findManifest(installPath, mode);
    let cleanStats = { removed: 0, skipped: 0 };
    if (existingManifest) {
        // Has manifest - clean based on manifest records
        warning('Existing installation found at this location');
        info(`  Files in manifest: ${existingManifest.files?.length || 0}`);
        info(`  Installed: ${new Date(existingManifest.installation_date).toLocaleDateString()}`);
        const { backup } = await inquirer.prompt([{
                type: 'confirm',
                name: 'backup',
                message: 'Create backup before reinstalling?',
                default: true
            }]);
        if (backup) {
            await createBackup(existingManifest);
        }
        // Clean based on manifest records
        console.log('');
        const cleanSpinner = createSpinner('Cleaning previous installation...').start();
        try {
            cleanSpinner.text = 'Removing manifest files...';
            cleanStats = await cleanManifestFiles(existingManifest, cleanSpinner);
            if (cleanStats.removed > 0 || cleanStats.skipped > 0) {
                cleanSpinner.succeed(`Cleanup: ${cleanStats.removed} files removed, ${cleanStats.skipped} settings preserved`);
            }
            else {
                cleanSpinner.succeed('Cleanup: no files to remove');
            }
        }
        catch (err) {
            const errMsg = err;
            cleanSpinner.warn(`Cleanup warning: ${errMsg.message}`);
        }
    }
    else {
        // No manifest - first install or manual install, just overwrite
        const existingDirs = SOURCE_DIRS.filter(dir => existsSync(join(installPath, dir)));
        if (existingDirs.length > 0) {
            info('No installation manifest found, files will be overwritten');
            info(`  Existing directories: ${existingDirs.join(', ')}`);
        }
    }
    // Create manifest
    const manifest = createManifest(mode, installPath);
    // Perform installation
    console.log('');
    const spinner = createSpinner('Installing files...').start();
    let totalFiles = 0;
    let totalDirs = 0;
    try {
        // For Path mode, install workflows to global first
        if (mode === 'Path') {
            const globalPath = homedir();
            for (const subdir of GLOBAL_SUBDIRS) {
                const srcWorkflows = join(sourceDir, '.claude', subdir);
                if (existsSync(srcWorkflows)) {
                    const destWorkflows = join(globalPath, '.claude', subdir);
                    spinner.text = `Installing ${subdir} to global...`;
                    const { files, directories } = await copyDirectory(srcWorkflows, destWorkflows, manifest);
                    totalFiles += files;
                    totalDirs += directories;
                }
            }
        }
        for (const dir of availableDirs) {
            const srcPath = join(sourceDir, dir);
            const destPath = join(installPath, dir);
            spinner.text = `Installing ${dir}...`;
            // For Path mode on .claude, exclude global subdirs (they're already installed to global)
            const excludeDirs = (mode === 'Path' && dir === '.claude') ? GLOBAL_SUBDIRS : [];
            const { files, directories } = await copyDirectory(srcPath, destPath, manifest, excludeDirs);
            totalFiles += files;
            totalDirs += directories;
        }
        // Create version.json
        const versionPath = join(installPath, '.claude', 'version.json');
        if (existsSync(dirname(versionPath))) {
            const versionData = {
                version: version,
                installedAt: new Date().toISOString(),
                mode: mode,
                installer: 'ccw'
            };
            writeFileSync(versionPath, JSON.stringify(versionData, null, 2), 'utf8');
            addFileEntry(manifest, versionPath);
            totalFiles++;
        }
        spinner.succeed('Installation complete!');
    }
    catch (err) {
        spinner.fail('Installation failed');
        const errMsg = err;
        error(errMsg.message);
        process.exit(1);
    }
    // Save manifest
    const manifestPath = saveManifest(manifest);
    // Show summary
    console.log('');
    const summaryLines = [
        chalk.green.bold('✓ Installation Successful'),
        '',
        chalk.white(`Mode: ${chalk.cyan(mode)}`),
        chalk.white(`Path: ${chalk.cyan(installPath)}`),
        chalk.white(`Version: ${chalk.cyan(version)}`),
        '',
        chalk.gray(`Files installed: ${totalFiles}`),
        chalk.gray(`Directories created: ${totalDirs}`)
    ];
    // Add cleanup stats if any files were processed
    if (cleanStats.removed > 0 || cleanStats.skipped > 0) {
        summaryLines.push(chalk.gray(`Old files removed: ${cleanStats.removed}`));
        if (cleanStats.skipped > 0) {
            summaryLines.push(chalk.gray(`Settings preserved: ${cleanStats.skipped}`));
        }
    }
    summaryLines.push('');
    summaryLines.push(chalk.gray(`Manifest: ${basename(manifestPath)}`));
    // Add codex components info if installed
    if (availableDirs.includes('.codex')) {
        const codexPath = join(installPath, '.codex');
        summaryLines.push('');
        summaryLines.push(chalk.cyan('Codex Components:'));
        // Prompts
        const promptsPath = join(codexPath, 'prompts');
        if (existsSync(promptsPath)) {
            summaryLines.push(chalk.gray(`  ✓ prompts: ${promptsPath}`));
        }
        // Agents
        const agentsPath = join(codexPath, 'agents');
        if (existsSync(agentsPath)) {
            const agentCount = readdirSync(agentsPath).filter(f => f.endsWith('.md')).length;
            summaryLines.push(chalk.gray(`  ✓ agents: ${agentCount} definitions`));
        }
        // Skills
        const skillsPath = join(codexPath, 'skills');
        if (existsSync(skillsPath)) {
            const skillCount = readdirSync(skillsPath).filter(f => statSync(join(skillsPath, f)).isDirectory()).length;
            summaryLines.push(chalk.gray(`  ✓ skills: ${skillCount} skills`));
        }
    }
    summaryBox({
        title: ' Installation Summary ',
        lines: summaryLines,
        borderColor: 'green'
    });
    // Install Git Bash fix on Windows
    if (platform() === 'win32') {
        console.log('');
        const { installFix } = await inquirer.prompt([{
                type: 'confirm',
                name: 'installFix',
                message: 'Install Git Bash multi-line prompt fix? (recommended for Git Bash users)',
                default: false
            }]);
        if (installFix) {
            // Let user select shell config file
            const selectedConfig = await selectShellConfig();
            const fixResult = await installGitBashFix(selectedConfig);
            if (fixResult.installed) {
                info(`Git Bash fix: ${fixResult.message}`);
                info(`  Run: source ${selectedConfig}  (to apply immediately)`);
            }
            else {
                warning(`Git Bash fix skipped: ${fixResult.message}`);
            }
        }
    }
    // Show next steps
    console.log('');
    info('Next steps:');
    console.log(chalk.gray('  1. Restart Claude Code or your IDE'));
    console.log(chalk.gray('  2. Run: ccw view - to open the workflow dashboard'));
    console.log(chalk.gray('  3. Run: ccw uninstall - to remove this installation'));
    console.log('');
}
/**
 * Interactive mode selection
 * @returns {Promise<string>} - Selected mode
 */
async function selectMode() {
    const { mode } = await inquirer.prompt([{
            type: 'list',
            name: 'mode',
            message: 'Select installation mode:',
            choices: [
                {
                    name: `${chalk.cyan('Global')} - Install to home directory (recommended)`,
                    value: 'Global'
                },
                {
                    name: `${chalk.yellow('Path')} - Install to specific project path`,
                    value: 'Path'
                }
            ]
        }]);
    return mode;
}
/**
 * Interactive path selection
 * @returns {Promise<string>} - Selected path
 */
async function selectPath() {
    const { path } = await inquirer.prompt([{
            type: 'input',
            name: 'path',
            message: 'Enter installation path:',
            default: process.cwd(),
            validate: (input) => {
                if (!input)
                    return 'Path is required';
                if (!existsSync(input)) {
                    return `Path does not exist: ${input}`;
                }
                return true;
            }
        }]);
    return path;
}
/**
 * Interactive shell configuration file selection
 * @returns {Promise<string>} - Selected config file path
 */
async function selectShellConfig() {
    const home = homedir();
    // Build choices with status indicators
    const choices = SHELL_CONFIG_FILES.map(config => {
        const fullPath = join(home, config.name);
        const exists = existsSync(fullPath);
        const status = exists ? chalk.green('exists') : chalk.gray('will be created');
        const recommended = config.name === '.bashrc' ? chalk.yellow(' (recommended)') : '';
        return {
            name: `${chalk.cyan(config.name)}${recommended} - ${config.description} [${status}]`,
            value: fullPath
        };
    });
    // Add custom path option
    choices.push({
        name: chalk.yellow('Custom path...'),
        value: 'custom'
    });
    const { configPath } = await inquirer.prompt([{
            type: 'list',
            name: 'configPath',
            message: 'Select shell configuration file to install Git Bash fix:',
            choices
        }]);
    if (configPath === 'custom') {
        const { customPath } = await inquirer.prompt([{
                type: 'input',
                name: 'customPath',
                message: 'Enter custom shell config path:',
                default: join(home, '.bashrc'),
                validate: (input) => {
                    if (!input)
                        return 'Path is required';
                    return true;
                }
            }]);
        return customPath;
    }
    return configPath;
}
/**
 * Clean files based on manifest record
 * Only removes files that were installed by the previous installation
 * @param manifest - Existing manifest with file records
 * @param spinner - Spinner for progress display
 * @returns Count of removed files and skipped files
 */
async function cleanManifestFiles(manifest, spinner) {
    let removed = 0;
    let skipped = 0;
    const files = manifest.files || [];
    const directories = manifest.directories || [];
    // Remove files in reverse order (process deepest paths first)
    const sortedFiles = [...files].sort((a, b) => b.path.length - a.path.length);
    for (const fileEntry of sortedFiles) {
        const filePath = fileEntry.path;
        const fileName = basename(filePath);
        // Skip excluded files (user settings)
        if (EXCLUDED_FILES.includes(fileName)) {
            skipped++;
            continue;
        }
        try {
            if (existsSync(filePath)) {
                spinner.text = `Removing: ${fileName}`;
                unlinkSync(filePath);
                removed++;
            }
        }
        catch {
            // Ignore errors during cleanup
        }
    }
    // Clean up empty directories (deepest first)
    const sortedDirs = [...directories].sort((a, b) => b.path.length - a.path.length);
    for (const dirEntry of sortedDirs) {
        const dirPath = dirEntry.path;
        try {
            if (existsSync(dirPath)) {
                const contents = readdirSync(dirPath);
                if (contents.length === 0) {
                    rmdirSync(dirPath);
                }
            }
        }
        catch {
            // Ignore errors during cleanup
        }
    }
    return { removed, skipped };
}
/**
 * Create backup of files recorded in manifest
 * @param manifest - Existing manifest with file records
 */
async function createBackup(manifest) {
    const spinner = createSpinner('Creating backup...').start();
    try {
        const installPath = manifest.installation_path;
        const timestamp = new Date().toISOString().replace(/[-:]/g, '').replace('T', '-').split('.')[0];
        const backupDir = join(installPath, `.claude-backup-${timestamp}`);
        const files = manifest.files || [];
        let backedUp = 0;
        for (const fileEntry of files) {
            const filePath = fileEntry.path;
            if (!existsSync(filePath))
                continue;
            // Calculate relative path from install path
            const relativePath = filePath.replace(installPath, '').replace(/^[\\/]/, '');
            const backupPath = join(backupDir, relativePath);
            // Create directory structure
            const backupFileDir = dirname(backupPath);
            if (!existsSync(backupFileDir)) {
                mkdirSync(backupFileDir, { recursive: true });
            }
            // Copy file
            try {
                spinner.text = `Backing up: ${basename(filePath)}`;
                copyFileSync(filePath, backupPath);
                backedUp++;
            }
            catch {
                // Ignore individual file errors
            }
        }
        if (backedUp > 0) {
            spinner.succeed(`Backup created: ${backupDir} (${backedUp} files)`);
        }
        else {
            spinner.info('No files to backup');
            // Remove empty backup dir
            try {
                rmdirSync(backupDir);
            }
            catch { /* ignore */ }
        }
    }
    catch (err) {
        const errMsg = err;
        spinner.warn(`Backup failed: ${errMsg.message}`);
    }
}
/**
 * Copy directory recursively
 * @param {string} src - Source directory
 * @param {string} dest - Destination directory
 * @param {Object} manifest - Manifest to track files (optional)
 * @param {string[]} excludeDirs - Directory names to exclude (optional)
 * @returns {Object} - Count of files and directories
 */
async function copyDirectory(src, dest, manifest = null, excludeDirs = [], excludeFiles = EXCLUDED_FILES) {
    let files = 0;
    let directories = 0;
    // Create destination directory
    if (!existsSync(dest)) {
        mkdirSync(dest, { recursive: true });
        directories++;
        if (manifest)
            addDirectoryEntry(manifest, dest);
    }
    const entries = readdirSync(src);
    for (const entry of entries) {
        // Skip excluded directories
        if (excludeDirs.includes(entry)) {
            continue;
        }
        // Skip excluded files
        if (excludeFiles.includes(entry)) {
            continue;
        }
        const srcPath = join(src, entry);
        const destPath = join(dest, entry);
        const stat = statSync(srcPath);
        if (stat.isDirectory()) {
            const result = await copyDirectory(srcPath, destPath, manifest, [], excludeFiles);
            files += result.files;
            directories += result.directories;
        }
        else {
            copyFileSync(srcPath, destPath);
            files++;
            if (manifest)
                addFileEntry(manifest, destPath);
        }
    }
    return { files, directories };
}
/**
 * Check if running in Git Bash on Windows
 */
function isGitBashOnWindows() {
    if (platform() !== 'win32')
        return false;
    // Check for MSYSTEM env var (set by Git Bash)
    const msystem = process.env.MSYSTEM;
    if (msystem && ['MINGW64', 'MINGW32', 'MSYS'].includes(msystem)) {
        return true;
    }
    // Check for typical Git Bash shell path
    const shell = process.env.SHELL || '';
    if (shell.includes('bash') || shell.includes('sh')) {
        return true;
    }
    return false;
}
/**
 * Get the ccw.js path for Git Bash fix
 */
function getCcwJsPath() {
    try {
        const npmPrefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
        const ccwJsPath = join(npmPrefix, 'node_modules', 'claude-code-workflow', 'ccw', 'bin', 'ccw.js');
        if (existsSync(ccwJsPath)) {
            return ccwJsPath;
        }
        return null;
    }
    catch {
        return null;
    }
}
/**
 * Generate Git Bash fix content for .bashrc
 */
function generateGitBashFix(ccwJsPath) {
    // Use Windows path format for node (works in Git Bash)
    const windowsPath = ccwJsPath.replace(/\//g, '/');
    return `
${GITBASH_FIX_START}
# Fix for multi-line prompt arguments in Git Bash + Windows
# npm's shell wrapper cannot handle multi-line quoted strings correctly
ccw() {
    node "${windowsPath}" "$@"
}
${GITBASH_FIX_END}
`;
}
/**
 * Install Git Bash fix to user's shell config
 * @param targetConfigPath - Optional target config file path. If not provided, auto-detects.
 * @returns true if fix was installed, false otherwise
 */
export async function installGitBashFix(targetConfigPath) {
    // Only applicable on Windows
    if (platform() !== 'win32') {
        return { installed: false, message: 'Not Windows platform' };
    }
    const ccwJsPath = getCcwJsPath();
    if (!ccwJsPath) {
        return { installed: false, message: 'ccw not found in npm global modules' };
    }
    let targetConfig;
    if (targetConfigPath) {
        // Use provided path
        targetConfig = targetConfigPath;
    }
    else {
        // Auto-detect: find existing shell config file
        const home = homedir();
        const configFiles = [
            join(home, '.bashrc'),
            join(home, '.bash_profile'),
            join(home, '.profile')
        ];
        let foundConfig = null;
        for (const configFile of configFiles) {
            if (existsSync(configFile)) {
                foundConfig = configFile;
                break;
            }
        }
        // If no config exists, default to .bashrc
        targetConfig = foundConfig || join(home, '.bashrc');
    }
    // Check if fix already exists in target file
    if (existsSync(targetConfig)) {
        const content = readFileSync(targetConfig, 'utf8');
        if (content.includes(GITBASH_FIX_START)) {
            // Update existing fix
            const fixContent = generateGitBashFix(ccwJsPath);
            const regex = new RegExp(`${GITBASH_FIX_START}[\\s\\S]*?${GITBASH_FIX_END}`, 'g');
            const newContent = content.replace(regex, fixContent.trim());
            writeFileSync(targetConfig, newContent, 'utf8');
            return { installed: true, message: `Updated in ${basename(targetConfig)}` };
        }
    }
    // Append fix to config file (creates file if not exists)
    const fixContent = generateGitBashFix(ccwJsPath);
    appendFileSync(targetConfig, fixContent, 'utf8');
    return { installed: true, message: `Added to ${basename(targetConfig)}` };
}
/**
 * Remove Git Bash fix from user's shell config
 * @returns true if fix was removed, false otherwise
 */
export function removeGitBashFix() {
    const home = homedir();
    // Check all supported shell config files
    const configFiles = SHELL_CONFIG_FILES.map(config => join(home, config.name));
    let removed = false;
    let targetFile = '';
    for (const configFile of configFiles) {
        if (!existsSync(configFile))
            continue;
        const content = readFileSync(configFile, 'utf8');
        if (content.includes(GITBASH_FIX_START)) {
            // Remove the fix block
            const regex = new RegExp(`\\n?${GITBASH_FIX_START}[\\s\\S]*?${GITBASH_FIX_END}\\n?`, 'g');
            const newContent = content.replace(regex, '\n');
            writeFileSync(configFile, newContent, 'utf8');
            removed = true;
            targetFile = basename(configFile);
        }
    }
    if (removed) {
        return { removed: true, message: `Removed from ${targetFile}` };
    }
    return { removed: false, message: 'No fix found to remove' };
}
/**
 * Get package version
 * @returns {string} - Version string
 */
function getVersion() {
    try {
        // First try root package.json (parent of ccw)
        const rootPkgPath = join(getSourceDir(), 'package.json');
        if (existsSync(rootPkgPath)) {
            const pkg = JSON.parse(readFileSync(rootPkgPath, 'utf8'));
            if (pkg.version)
                return pkg.version;
        }
        // Fallback to ccw package.json
        const pkgPath = join(getPackageRoot(), 'package.json');
        const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
        return pkg.version || '1.0.0';
    }
    catch {
        return '1.0.0';
    }
}
//# sourceMappingURL=install.js.map