/**
 * Plugin System for Code Buddy
 *
 * Enables extensibility through:
 * - Custom tool plugins
 * - Middleware plugins
 * - Theme plugins
 * - Integration plugins
 */
import { EventEmitter } from 'events';
import fs from 'fs';
import path from 'path';
import os from 'os';
/**
 * Plugin Manager
 */
export class PluginManager extends EventEmitter {
    plugins = new Map();
    pluginDirs = [];
    constructor(pluginDirs) {
        super();
        this.pluginDirs = pluginDirs || this.getDefaultPluginDirs();
    }
    /**
     * Get default plugin directories
     */
    getDefaultPluginDirs() {
        const homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir();
        return [
            path.join(homeDir, '.codebuddy', 'plugins'),
            path.join(process.cwd(), '.codebuddy', 'plugins'),
        ];
    }
    /**
     * Discover plugins in plugin directories
     */
    async discoverPlugins() {
        const manifests = [];
        for (const dir of this.pluginDirs) {
            if (!fs.existsSync(dir))
                continue;
            const entries = fs.readdirSync(dir, { withFileTypes: true });
            for (const entry of entries) {
                if (!entry.isDirectory())
                    continue;
                const manifestPath = path.join(dir, entry.name, 'manifest.json');
                if (!fs.existsSync(manifestPath))
                    continue;
                try {
                    const manifestContent = fs.readFileSync(manifestPath, 'utf-8');
                    const manifest = JSON.parse(manifestContent);
                    manifests.push(manifest);
                }
                catch {
                    // Skip invalid manifests
                }
            }
        }
        return manifests;
    }
    /**
     * Load a plugin by name
     */
    async loadPlugin(name) {
        // Find plugin directory
        let pluginDir = null;
        for (const dir of this.pluginDirs) {
            const candidate = path.join(dir, name);
            if (fs.existsSync(path.join(candidate, 'manifest.json'))) {
                pluginDir = candidate;
                break;
            }
        }
        if (!pluginDir) {
            this.emit('error', { plugin: name, error: 'Plugin not found' });
            return false;
        }
        try {
            // Read manifest
            const manifestPath = path.join(pluginDir, 'manifest.json');
            const manifestContent = fs.readFileSync(manifestPath, 'utf-8');
            const manifest = JSON.parse(manifestContent);
            // Validate manifest
            if (!this.validateManifest(manifest)) {
                throw new Error('Invalid manifest');
            }
            // Check if already loaded
            if (this.plugins.has(manifest.name)) {
                throw new Error('Plugin already loaded');
            }
            // Load the plugin module (validate path stays inside plugin directory)
            const mainPath = path.resolve(pluginDir, manifest.main);
            if (!mainPath.startsWith(path.resolve(pluginDir))) {
                throw new Error(`Plugin main path traversal blocked: ${manifest.main}`);
            }
            const pluginModule = await import(mainPath);
            const instance = pluginModule.default || pluginModule;
            // Create context
            const context = {
                pluginDir,
                config: manifest.config || {},
                log: (message, level = 'info') => {
                    this.emit('log', { plugin: manifest.name, level, message });
                },
                emit: (event, data) => {
                    this.emit(`plugin:${manifest.name}:${event}`, data);
                },
            };
            // Call onLoad if defined
            if (instance.onLoad) {
                await instance.onLoad(context);
            }
            // Store loaded plugin
            const loadedPlugin = {
                manifest,
                instance,
                enabled: true,
                loadedAt: new Date(),
                path: pluginDir,
            };
            this.plugins.set(manifest.name, loadedPlugin);
            this.emit('loaded', { plugin: manifest.name });
            return true;
        }
        catch (error) {
            this.emit('error', {
                plugin: name,
                error: error instanceof Error ? error.message : String(error),
            });
            return false;
        }
    }
    /**
     * Unload a plugin
     */
    async unloadPlugin(name) {
        const plugin = this.plugins.get(name);
        if (!plugin)
            return false;
        try {
            if (plugin.instance.onUnload) {
                await plugin.instance.onUnload();
            }
            this.plugins.delete(name);
            this.emit('unloaded', { plugin: name });
            return true;
        }
        catch (error) {
            this.emit('error', {
                plugin: name,
                error: error instanceof Error ? error.message : String(error),
            });
            return false;
        }
    }
    /**
     * Enable a plugin
     */
    async enablePlugin(name) {
        const plugin = this.plugins.get(name);
        if (!plugin)
            return false;
        try {
            if (plugin.instance.onEnable) {
                await plugin.instance.onEnable();
            }
            plugin.enabled = true;
            this.emit('enabled', { plugin: name });
            return true;
        }
        catch (error) {
            this.emit('error', {
                plugin: name,
                error: error instanceof Error ? error.message : String(error),
            });
            return false;
        }
    }
    /**
     * Disable a plugin
     */
    async disablePlugin(name) {
        const plugin = this.plugins.get(name);
        if (!plugin)
            return false;
        try {
            if (plugin.instance.onDisable) {
                await plugin.instance.onDisable();
            }
            plugin.enabled = false;
            this.emit('disabled', { plugin: name });
            return true;
        }
        catch (error) {
            this.emit('error', {
                plugin: name,
                error: error instanceof Error ? error.message : String(error),
            });
            return false;
        }
    }
    /**
     * Get all loaded plugins
     */
    getLoadedPlugins() {
        return Array.from(this.plugins.values());
    }
    /**
     * Get plugins by type
     */
    getPluginsByType(type) {
        return this.getLoadedPlugins().filter((p) => p.manifest.type === type);
    }
    /**
     * Get enabled tool plugins
     */
    getToolPlugins() {
        return this.getPluginsByType('tool')
            .filter((p) => p.enabled)
            .map((p) => p.instance);
    }
    /**
     * Get enabled middleware plugins (sorted by priority)
     */
    getMiddlewarePlugins() {
        return this.getPluginsByType('middleware')
            .filter((p) => p.enabled)
            .map((p) => p.instance)
            .sort((a, b) => a.priority - b.priority);
    }
    /**
     * Validate plugin manifest
     */
    validateManifest(manifest) {
        return !!(manifest.name &&
            manifest.version &&
            manifest.type &&
            manifest.main &&
            ['tool', 'middleware', 'theme', 'integration'].includes(manifest.type));
    }
    /**
     * Load all discovered plugins
     */
    async loadAllPlugins() {
        const manifests = await this.discoverPlugins();
        // Load all plugins in parallel for faster startup
        const results = await Promise.allSettled(manifests.map(async (manifest) => {
            const success = await this.loadPlugin(manifest.name);
            return { name: manifest.name, success };
        }));
        const loaded = [];
        const failed = [];
        for (const result of results) {
            if (result.status === 'fulfilled') {
                if (result.value.success) {
                    loaded.push(result.value.name);
                }
                else {
                    failed.push(result.value.name);
                }
            }
            else {
                // Find the corresponding manifest name for rejected promises
                const index = results.indexOf(result);
                failed.push(manifests[index]?.name || 'unknown');
            }
        }
        return { loaded, failed };
    }
    /**
     * Unload all plugins
     */
    async unloadAllPlugins() {
        // Unload all plugins in parallel for faster shutdown
        const pluginNames = Array.from(this.plugins.keys());
        await Promise.allSettled(pluginNames.map(name => this.unloadPlugin(name)));
    }
    /**
     * Format plugins list for display
     */
    formatPluginList() {
        const plugins = this.getLoadedPlugins();
        if (plugins.length === 0) {
            return 'No plugins loaded.\n\nPlugin directories:\n' +
                this.pluginDirs.map((d) => `  - ${d}`).join('\n');
        }
        const lines = ['Loaded Plugins:', ''];
        for (const plugin of plugins) {
            const status = plugin.enabled ? '[ON]' : '[OFF]';
            lines.push(`${status} ${plugin.manifest.name} v${plugin.manifest.version}`);
            lines.push(`    Type: ${plugin.manifest.type}`);
            if (plugin.manifest.description) {
                lines.push(`    ${plugin.manifest.description}`);
            }
            lines.push('');
        }
        return lines.join('\n');
    }
    /**
     * Dispose and cleanup
     */
    async dispose() {
        await this.unloadAllPlugins();
        this.removeAllListeners();
    }
}
// Singleton instance
let pluginManager = null;
/**
 * Get or create the plugin manager
 */
export function getPluginManager() {
    if (!pluginManager) {
        pluginManager = new PluginManager();
    }
    return pluginManager;
}
/**
 * Reset the plugin manager
 */
export async function resetPluginManager() {
    if (pluginManager) {
        await pluginManager.dispose();
        pluginManager = null;
    }
}
//# sourceMappingURL=plugin-system.js.map