import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
import { FileSystemUtils } from '../../../utils/file-system.js';
/**
 * Installer for Zsh completion scripts.
 * Supports both Oh My Zsh and standard Zsh configurations.
 */
export class ZshInstaller {
    homeDir;
    /**
     * Markers for .zshrc configuration management
     */
    ZSHRC_MARKERS = {
        start: '# OPENSPEC:START',
        end: '# OPENSPEC:END',
    };
    constructor(homeDir = os.homedir()) {
        this.homeDir = homeDir;
    }
    /**
     * Check if Oh My Zsh is installed
     *
     * @returns true if Oh My Zsh is detected via $ZSH env var or directory exists
     */
    async isOhMyZshInstalled() {
        // First check for $ZSH environment variable (standard OMZ setup)
        if (process.env.ZSH) {
            return true;
        }
        // Fall back to checking for ~/.oh-my-zsh directory
        const ohMyZshPath = path.join(this.homeDir, '.oh-my-zsh');
        try {
            const stat = await fs.stat(ohMyZshPath);
            return stat.isDirectory();
        }
        catch {
            return false;
        }
    }
    /**
     * Get the appropriate installation path for the completion script
     *
     * @returns Object with installation path and whether it's Oh My Zsh
     */
    async getInstallationPath() {
        const isOhMyZsh = await this.isOhMyZshInstalled();
        if (isOhMyZsh) {
            // Oh My Zsh custom completions directory
            return {
                path: path.join(this.homeDir, '.oh-my-zsh', 'custom', 'completions', '_openspec'),
                isOhMyZsh: true,
            };
        }
        else {
            // Standard Zsh completions directory
            return {
                path: path.join(this.homeDir, '.zsh', 'completions', '_openspec'),
                isOhMyZsh: false,
            };
        }
    }
    /**
     * Backup an existing completion file if it exists
     *
     * @param targetPath - Path to the file to backup
     * @returns Path to the backup file, or undefined if no backup was needed
     */
    async backupExistingFile(targetPath) {
        try {
            await fs.access(targetPath);
            // File exists, create a backup
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
            const backupPath = `${targetPath}.backup-${timestamp}`;
            await fs.copyFile(targetPath, backupPath);
            return backupPath;
        }
        catch {
            // File doesn't exist, no backup needed
            return undefined;
        }
    }
    /**
     * Get the path to .zshrc file
     *
     * @returns Path to .zshrc
     */
    getZshrcPath() {
        return path.join(this.homeDir, '.zshrc');
    }
    /**
     * Generate .zshrc configuration content
     *
     * @param completionsDir - Directory containing completion scripts
     * @returns Configuration content
     */
    generateZshrcConfig(completionsDir) {
        return [
            '# OpenSpec shell completions configuration',
            `fpath=("${completionsDir}" $fpath)`,
            'autoload -Uz compinit',
            'compinit',
        ].join('\n');
    }
    /**
     * Configure .zshrc to enable completions
     * Only applies to standard Zsh (not Oh My Zsh)
     *
     * @param completionsDir - Directory containing completion scripts
     * @returns true if configured successfully, false otherwise
     */
    async configureZshrc(completionsDir) {
        // Check if auto-configuration is disabled
        if (process.env.OPENSPEC_NO_AUTO_CONFIG === '1') {
            return false;
        }
        try {
            const zshrcPath = this.getZshrcPath();
            const config = this.generateZshrcConfig(completionsDir);
            // Check write permissions
            const canWrite = await FileSystemUtils.canWriteFile(zshrcPath);
            if (!canWrite) {
                return false;
            }
            // Use marker-based update
            await FileSystemUtils.updateFileWithMarkers(zshrcPath, config, this.ZSHRC_MARKERS.start, this.ZSHRC_MARKERS.end);
            return true;
        }
        catch (error) {
            // Fail gracefully - don't break installation
            console.debug(`Unable to configure .zshrc for completions: ${error.message}`);
            return false;
        }
    }
    /**
     * Check if .zshrc has OpenSpec configuration markers
     *
     * @returns true if .zshrc exists and has markers
     */
    async hasZshrcConfig() {
        try {
            const zshrcPath = this.getZshrcPath();
            const content = await fs.readFile(zshrcPath, 'utf-8');
            return content.includes(this.ZSHRC_MARKERS.start) && content.includes(this.ZSHRC_MARKERS.end);
        }
        catch {
            return false;
        }
    }
    /**
     * Check if fpath configuration is needed for a given directory
     * Used to verify if Oh My Zsh (or other) completions directory is already in fpath
     *
     * @param completionsDir - Directory to check for in fpath
     * @returns true if configuration is needed, false if directory is already referenced
     */
    async needsFpathConfig(completionsDir) {
        try {
            const zshrcPath = this.getZshrcPath();
            const content = await fs.readFile(zshrcPath, 'utf-8');
            // Check if fpath already includes this directory
            return !content.includes(completionsDir);
        }
        catch (error) {
            // If we can't read .zshrc, assume config is needed
            console.debug(`Unable to read .zshrc to check fpath config: ${error instanceof Error ? error.message : String(error)}`);
            return true;
        }
    }
    /**
     * Remove .zshrc configuration
     * Used during uninstallation
     *
     * @returns true if removed successfully, false otherwise
     */
    async removeZshrcConfig() {
        try {
            const zshrcPath = this.getZshrcPath();
            // Check if file exists
            try {
                await fs.access(zshrcPath);
            }
            catch {
                // File doesn't exist, nothing to remove
                return true;
            }
            // Read file content
            const content = await fs.readFile(zshrcPath, 'utf-8');
            // Check if markers exist
            if (!content.includes(this.ZSHRC_MARKERS.start) || !content.includes(this.ZSHRC_MARKERS.end)) {
                // Markers don't exist, nothing to remove
                return true;
            }
            // Remove content between markers (including markers)
            const lines = content.split('\n');
            const startIndex = lines.findIndex((line) => line.trim() === this.ZSHRC_MARKERS.start);
            const endIndex = lines.findIndex((line) => line.trim() === this.ZSHRC_MARKERS.end);
            if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
                // Invalid marker placement
                return false;
            }
            // Remove lines between markers (inclusive)
            lines.splice(startIndex, endIndex - startIndex + 1);
            // Remove trailing empty lines at the start if the markers were at the top
            while (lines.length > 0 && lines[0].trim() === '') {
                lines.shift();
            }
            // Write back
            await fs.writeFile(zshrcPath, lines.join('\n'), 'utf-8');
            return true;
        }
        catch (error) {
            // Fail gracefully
            console.debug(`Unable to remove .zshrc configuration: ${error.message}`);
            return false;
        }
    }
    /**
     * Install the completion script
     *
     * @param completionScript - The completion script content to install
     * @returns Installation result with status and instructions
     */
    async install(completionScript) {
        try {
            const { path: targetPath, isOhMyZsh } = await this.getInstallationPath();
            // Check if already installed with same content
            let isUpdate = false;
            try {
                const existingContent = await fs.readFile(targetPath, 'utf-8');
                if (existingContent === completionScript) {
                    // Already installed and up to date
                    return {
                        success: true,
                        installedPath: targetPath,
                        isOhMyZsh,
                        message: 'Completion script is already installed (up to date)',
                        instructions: [
                            'The completion script is already installed and up to date.',
                            'If completions are not working, try: exec zsh',
                        ],
                    };
                }
                // File exists but content is different - this is an update
                isUpdate = true;
            }
            catch (error) {
                // File doesn't exist or can't be read, proceed with installation
                console.debug(`Unable to read existing completion file at ${targetPath}: ${error.message}`);
            }
            // Ensure the directory exists
            const targetDir = path.dirname(targetPath);
            await fs.mkdir(targetDir, { recursive: true });
            // Backup existing file if updating
            const backupPath = isUpdate ? await this.backupExistingFile(targetPath) : undefined;
            // Write the completion script
            await fs.writeFile(targetPath, completionScript, 'utf-8');
            // Auto-configure .zshrc
            let zshrcConfigured = false;
            if (isOhMyZsh) {
                // For Oh My Zsh, verify that custom/completions is in fpath
                // If not, add it to .zshrc
                const needsConfig = await this.needsFpathConfig(targetDir);
                if (needsConfig) {
                    zshrcConfigured = await this.configureZshrc(targetDir);
                }
            }
            else {
                // Standard Zsh always needs .zshrc configuration
                zshrcConfigured = await this.configureZshrc(targetDir);
            }
            // Generate instructions (only if .zshrc wasn't auto-configured)
            let instructions = zshrcConfigured ? undefined : this.generateInstructions(isOhMyZsh, targetPath);
            // Add fpath guidance for Oh My Zsh installations
            if (isOhMyZsh) {
                const fpathGuidance = this.generateOhMyZshFpathGuidance(targetDir);
                if (fpathGuidance) {
                    instructions = instructions ? [...instructions, '', ...fpathGuidance] : fpathGuidance;
                }
            }
            // Determine appropriate message based on update status
            let message;
            if (isUpdate) {
                message = backupPath
                    ? 'Completion script updated successfully (previous version backed up)'
                    : 'Completion script updated successfully';
            }
            else {
                message = isOhMyZsh
                    ? 'Completion script installed successfully for Oh My Zsh'
                    : zshrcConfigured
                        ? 'Completion script installed and .zshrc configured successfully'
                        : 'Completion script installed successfully for Zsh';
            }
            return {
                success: true,
                installedPath: targetPath,
                backupPath,
                isOhMyZsh,
                zshrcConfigured,
                message,
                instructions,
            };
        }
        catch (error) {
            return {
                success: false,
                isOhMyZsh: false,
                message: `Failed to install completion script: ${error instanceof Error ? error.message : String(error)}`,
            };
        }
    }
    /**
     * Generate Oh My Zsh fpath verification guidance
     *
     * @param completionsDir - Custom completions directory path
     * @returns Array of guidance strings, or undefined if not needed
     */
    generateOhMyZshFpathGuidance(completionsDir) {
        return [
            'Note: Oh My Zsh typically auto-loads completions from custom/completions.',
            `Verify that ${completionsDir} is in your fpath by running:`,
            '  echo $fpath | grep "custom/completions"',
            '',
            'If not found, completions may not work. Restart your shell to ensure changes take effect.',
        ];
    }
    /**
     * Generate user instructions for enabling completions
     *
     * @param isOhMyZsh - Whether Oh My Zsh is being used
     * @param installedPath - Path where the script was installed
     * @returns Array of instruction strings
     */
    generateInstructions(isOhMyZsh, installedPath) {
        if (isOhMyZsh) {
            return [
                'Completion script installed to Oh My Zsh completions directory.',
                'Restart your shell or run: exec zsh',
                'Completions should activate automatically.',
            ];
        }
        else {
            const completionsDir = path.dirname(installedPath);
            const zshrcPath = path.join(this.homeDir, '.zshrc');
            return [
                'Completion script installed to ~/.zsh/completions/',
                '',
                'To enable completions, add the following to your ~/.zshrc file:',
                '',
                `  # Add completions directory to fpath`,
                `  fpath=(${completionsDir} $fpath)`,
                '',
                '  # Initialize completion system',
                '  autoload -Uz compinit',
                '  compinit',
                '',
                'Then restart your shell or run: exec zsh',
                '',
                `Check if these lines already exist in ${zshrcPath} before adding.`,
            ];
        }
    }
    /**
     * Uninstall the completion script
     *
     * @returns true if uninstalled successfully, false otherwise
     */
    async uninstall() {
        try {
            const { path: targetPath, isOhMyZsh } = await this.getInstallationPath();
            // Try to remove completion script
            let scriptRemoved = false;
            try {
                await fs.access(targetPath);
                await fs.unlink(targetPath);
                scriptRemoved = true;
            }
            catch {
                // Script not installed
            }
            // Try to remove .zshrc configuration (only for standard Zsh)
            let zshrcWasPresent = false;
            let zshrcCleaned = false;
            if (!isOhMyZsh) {
                zshrcWasPresent = await this.hasZshrcConfig();
                if (zshrcWasPresent) {
                    zshrcCleaned = await this.removeZshrcConfig();
                }
            }
            if (!scriptRemoved && !zshrcWasPresent) {
                return {
                    success: false,
                    message: 'Completion script is not installed',
                };
            }
            const messages = [];
            if (scriptRemoved) {
                messages.push(`Completion script removed from ${targetPath}`);
            }
            if (zshrcCleaned && !isOhMyZsh) {
                messages.push('Removed OpenSpec configuration from ~/.zshrc');
            }
            return {
                success: true,
                message: messages.join('. '),
            };
        }
        catch (error) {
            return {
                success: false,
                message: `Failed to uninstall completion script: ${error instanceof Error ? error.message : String(error)}`,
            };
        }
    }
    /**
     * Check if completion script is currently installed
     *
     * @returns true if the completion script exists
     */
    async isInstalled() {
        try {
            const { path: targetPath } = await this.getInstallationPath();
            await fs.access(targetPath);
            return true;
        }
        catch {
            return false;
        }
    }
    /**
     * Get information about the current installation
     *
     * @returns Installation status information
     */
    async getInstallationInfo() {
        const installed = await this.isInstalled();
        if (!installed) {
            return { installed: false };
        }
        const { path: targetPath, isOhMyZsh } = await this.getInstallationPath();
        return {
            installed: true,
            path: targetPath,
            isOhMyZsh,
        };
    }
}
//# sourceMappingURL=zsh-installer.js.map