/**
 * Plugin Marketplace System
 *
 * Features:
 * - Plugin discovery and search
 * - Installation and updates
 * - Plugin lifecycle management
 * - Sandboxed execution
 * - Version compatibility checking
 * - Plugin ratings and reviews
 *
 * Allows extending Grok CLI with community plugins.
 */
import { EventEmitter } from 'events';
import fs from 'fs-extra';
import * as path from 'path';
import * as os from 'os';
import * as crypto from 'crypto';
import axios from 'axios';
import * as semver from 'semver';
import { createPluginSandbox } from './sandbox-worker.js';
const DEFAULT_CONFIG = {
    registryUrl: 'https://plugins.code-buddy.dev',
    autoUpdate: true,
    checkUpdatesInterval: 86400000, // 24 hours
    allowUntrusted: false,
    sandboxPlugins: true,
    maxPlugins: 50,
};
const GROK_VERSION = '1.0.0'; // Should be imported from package.json
/**
 * Plugin Marketplace
 */
export class PluginMarketplace extends EventEmitter {
    config;
    pluginsDir;
    installedPlugins = new Map();
    loadedPlugins = new Map();
    commands = new Map();
    tools = new Map();
    providers = new Map();
    hooks = new Map();
    updateCheckerIntervalId = null;
    constructor(config = {}) {
        super();
        this.config = { ...DEFAULT_CONFIG, ...config };
        this.pluginsDir = path.join(os.homedir(), '.codebuddy', 'plugins');
        this.initialize();
    }
    /**
     * Initialize marketplace
     */
    async initialize() {
        await fs.ensureDir(this.pluginsDir);
        await fs.ensureDir(path.join(this.pluginsDir, 'installed'));
        await fs.ensureDir(path.join(this.pluginsDir, 'cache'));
        await this.loadInstalledPlugins();
        if (this.config.autoUpdate) {
            this.startUpdateChecker();
        }
    }
    /**
     * Load installed plugins from disk
     */
    async loadInstalledPlugins() {
        const _installedDir = path.join(this.pluginsDir, 'installed'); // Reserved for future use
        const manifestPath = path.join(this.pluginsDir, 'manifest.json');
        if (await fs.pathExists(manifestPath)) {
            try {
                const manifest = await fs.readJSON(manifestPath);
                for (const plugin of manifest.plugins) {
                    this.installedPlugins.set(plugin.id, plugin);
                    if (plugin.enabled) {
                        await this.loadPlugin(plugin.id);
                    }
                }
            }
            catch {
                // Start fresh
            }
        }
    }
    /**
     * Save installed plugins manifest
     */
    async saveManifest() {
        const manifestPath = path.join(this.pluginsDir, 'manifest.json');
        await fs.writeJSON(manifestPath, {
            version: 1,
            updatedAt: new Date().toISOString(),
            plugins: Array.from(this.installedPlugins.values()),
        }, { spaces: 2 });
    }
    /**
     * Search plugins in marketplace
     */
    async search(query, options = {}) {
        try {
            const response = await axios.get(`${this.config.registryUrl}/api/plugins/search`, {
                params: {
                    q: query,
                    category: options.category,
                    limit: options.limit || 20,
                    offset: options.offset || 0,
                    sort: options.sort || 'downloads',
                },
                timeout: 10000,
            });
            return response.data.plugins || [];
        }
        catch (error) {
            this.emit('error', new Error(`Search failed: ${error}`));
            return [];
        }
    }
    /**
     * Get plugin details
     */
    async getPluginDetails(pluginId) {
        try {
            const response = await axios.get(`${this.config.registryUrl}/api/plugins/${pluginId}`, { timeout: 10000 });
            return response.data;
        }
        catch {
            return null;
        }
    }
    /**
     * Install a plugin
     */
    async install(pluginId, version) {
        if (this.installedPlugins.size >= this.config.maxPlugins) {
            throw new Error(`Maximum plugins limit (${this.config.maxPlugins}) reached`);
        }
        this.emit('install:start', { pluginId });
        try {
            // Get plugin info
            const plugin = await this.getPluginDetails(pluginId);
            if (!plugin) {
                throw new Error(`Plugin "${pluginId}" not found in the registry. Check the plugin ID or try refreshing the plugin list.`);
            }
            // Check version compatibility
            if (!this.isCompatible(plugin)) {
                throw new Error(`Plugin requires Grok ${plugin.engines.grok}, but you have ${GROK_VERSION}`);
            }
            // Download plugin
            const downloadUrl = `${this.config.registryUrl}/api/plugins/${pluginId}/download`;
            const response = await axios.get(downloadUrl, {
                params: { version: version || plugin.version },
                responseType: 'arraybuffer',
                timeout: 60000,
            });
            // Verify checksum
            const checksum = response.headers['x-checksum'];
            if (checksum) {
                const hash = crypto.createHash('sha256').update(response.data).digest('hex');
                if (hash !== checksum) {
                    throw new Error('Plugin checksum verification failed. The download may be corrupted. Please try again.');
                }
            }
            // Extract to plugins directory
            const installPath = path.join(this.pluginsDir, 'installed', pluginId);
            await fs.ensureDir(installPath);
            // Write plugin data (in real implementation, would extract tarball)
            await fs.writeFile(path.join(installPath, 'plugin.tgz'), response.data);
            // For now, simulate extraction by creating a simple module
            await this.extractPlugin(installPath, response.data);
            // Create installed plugin record
            const installed = {
                ...plugin,
                installedAt: new Date(),
                updatedAt: new Date(),
                enabled: true,
                configValues: this.getDefaultConfig(plugin),
                installPath,
            };
            this.installedPlugins.set(pluginId, installed);
            await this.saveManifest();
            // Load the plugin
            await this.loadPlugin(pluginId);
            this.emit('install:complete', { plugin: installed });
            return installed;
        }
        catch (error) {
            this.emit('install:error', { pluginId, error });
            throw error;
        }
    }
    /**
     * Extract plugin archive
     */
    async extractPlugin(installPath, _data) {
        // In a real implementation, this would extract a tarball
        // For now, create a placeholder module
        const indexPath = path.join(installPath, 'index.js');
        await fs.writeFile(indexPath, `
      module.exports = {
        activate: function(api) {
          api.log('info', 'Plugin activated');
        },
        deactivate: function() {
          // Cleanup
        }
      };
    `);
    }
    /**
     * Uninstall a plugin
     */
    async uninstall(pluginId) {
        const plugin = this.installedPlugins.get(pluginId);
        if (!plugin) {
            throw new Error('Plugin is not installed. Use "plugins list" to see installed plugins.');
        }
        this.emit('uninstall:start', { pluginId });
        try {
            // Unload if loaded
            await this.unloadPlugin(pluginId);
            // Remove files
            await fs.remove(plugin.installPath);
            // Remove from manifest
            this.installedPlugins.delete(pluginId);
            await this.saveManifest();
            this.emit('uninstall:complete', { pluginId });
        }
        catch (error) {
            this.emit('uninstall:error', { pluginId, error });
            throw error;
        }
    }
    /**
     * Update a plugin
     */
    async update(pluginId) {
        const installed = this.installedPlugins.get(pluginId);
        if (!installed) {
            throw new Error('Plugin is not installed. Use "plugins list" to see installed plugins.');
        }
        const latest = await this.getPluginDetails(pluginId);
        if (!latest) {
            throw new Error('Plugin not found in registry');
        }
        if (!semver.gt(latest.version, installed.version)) {
            return installed; // Already up to date
        }
        // Uninstall old version
        await this.uninstall(pluginId);
        // Install new version
        return this.install(pluginId, latest.version);
    }
    /**
     * Check for updates
     */
    async checkUpdates() {
        const updates = [];
        for (const plugin of this.installedPlugins.values()) {
            try {
                const latest = await this.getPluginDetails(plugin.id);
                if (latest && semver.gt(latest.version, plugin.version)) {
                    updates.push({ plugin, latestVersion: latest.version });
                }
            }
            catch {
                // Skip failed checks
            }
        }
        if (updates.length > 0) {
            this.emit('updates:available', { updates });
        }
        return updates;
    }
    /**
     * Start update checker
     */
    startUpdateChecker() {
        this.updateCheckerIntervalId = setInterval(() => {
            this.checkUpdates();
        }, this.config.checkUpdatesInterval);
    }
    /**
     * Validate plugin path is within allowed directory
     */
    validatePluginPath(modulePath) {
        const normalizedPath = path.normalize(path.resolve(modulePath));
        const normalizedPluginsDir = path.normalize(path.resolve(this.pluginsDir));
        // Ensure the module path is within the plugins directory
        if (!normalizedPath.startsWith(normalizedPluginsDir)) {
            return false;
        }
        // Block path traversal attempts
        if (modulePath.includes('..') || modulePath.includes('\0')) {
            return false;
        }
        return true;
    }
    /**
     * Load a plugin
     */
    async loadPlugin(pluginId) {
        const plugin = this.installedPlugins.get(pluginId);
        if (!plugin) {
            throw new Error('Plugin is not installed. Use "plugins list" to see installed plugins.');
        }
        if (this.loadedPlugins.has(pluginId)) {
            return; // Already loaded
        }
        try {
            const modulePath = path.join(plugin.installPath, plugin.main || 'index.js');
            // SECURITY: Validate plugin path before loading
            if (!this.validatePluginPath(modulePath)) {
                throw new Error(`Invalid plugin path: ${modulePath}`);
            }
            // Verify the module file exists
            if (!await fs.pathExists(modulePath)) {
                throw new Error(`Plugin module not found: ${modulePath}`);
            }
            // Create plugin API
            const api = this.createPluginAPI(pluginId, plugin);
            // Create instance
            let instance;
            if (this.config.sandboxPlugins) {
                // SECURITY: Load plugin in sandboxed worker thread
                const sandboxPermissions = plugin.permissions.map(p => ({
                    type: p.type,
                    scope: p.scope,
                }));
                const sandbox = await createPluginSandbox(modulePath, pluginId, sandboxPermissions, api, {
                    timeout: 30000,
                    memoryLimit: 128 * 1024 * 1024,
                    onLog: (level, message) => {
                        this.emit('plugin:log', { pluginId, level, message });
                    },
                });
                instance = {
                    plugin,
                    module: null,
                    api,
                    sandbox,
                };
            }
            else {
                // Non-sandboxed mode (for trusted plugins only)
                // Use dynamic import() for ESM compatibility
                const pluginModule = await import(modulePath);
                instance = {
                    plugin,
                    module: pluginModule,
                    api,
                };
                // Activate plugin
                if (typeof pluginModule.activate === 'function') {
                    await pluginModule.activate(api);
                }
            }
            this.loadedPlugins.set(pluginId, instance);
            this.emit('plugin:loaded', { pluginId });
        }
        catch (error) {
            this.emit('plugin:error', { pluginId, error });
            throw error;
        }
    }
    /**
     * Unload a plugin
     */
    async unloadPlugin(pluginId) {
        const instance = this.loadedPlugins.get(pluginId);
        if (!instance) {
            return;
        }
        try {
            if (instance.sandbox) {
                // Terminate sandboxed plugin
                await instance.sandbox.terminate();
            }
            else if (instance.module) {
                // Deactivate non-sandboxed plugin
                const mod = instance.module;
                if (typeof mod.deactivate === 'function') {
                    await mod.deactivate();
                }
                // Clear from cache
                const modulePath = path.join(instance.plugin.installPath, instance.plugin.main || 'index.js');
                delete require.cache[require.resolve(modulePath)];
            }
            // Remove registered items
            this.removePluginRegistrations(pluginId);
            this.loadedPlugins.delete(pluginId);
            this.emit('plugin:unloaded', { pluginId });
        }
        catch (error) {
            this.emit('plugin:error', { pluginId, error });
        }
    }
    /**
     * Remove plugin registrations
     */
    removePluginRegistrations(pluginId) {
        // Remove commands
        for (const [name, cmd] of this.commands) {
            if (cmd.pluginId === pluginId) {
                this.commands.delete(name);
            }
        }
        // Remove tools
        for (const [name, tool] of this.tools) {
            if (tool.pluginId === pluginId) {
                this.tools.delete(name);
            }
        }
        // Remove providers
        for (const [name, provider] of this.providers) {
            if (provider.pluginId === pluginId) {
                this.providers.delete(name);
            }
        }
        // Remove hooks
        for (const [event, handlers] of this.hooks) {
            this.hooks.set(event, handlers.filter(h => h.pluginId !== pluginId));
        }
    }
    /**
     * Create plugin API
     */
    createPluginAPI(pluginId, plugin) {
        const storagePath = path.join(plugin.installPath, 'storage.json');
        return {
            registerCommand: (name, handler) => {
                this.commands.set(name, { pluginId, handler });
                this.emit('command:registered', { name, pluginId });
            },
            registerTool: (name, tool) => {
                this.tools.set(name, { pluginId, tool });
                this.emit('tool:registered', { name, pluginId });
            },
            registerProvider: (name, provider) => {
                this.providers.set(name, { pluginId, provider });
                this.emit('provider:registered', { name, pluginId });
            },
            registerHook: (event, handler) => {
                if (!this.hooks.has(event)) {
                    this.hooks.set(event, []);
                }
                this.hooks.get(event).push({ pluginId, handler });
            },
            getConfig: () => ({ ...plugin.configValues }),
            setConfig: (key, value) => {
                plugin.configValues[key] = value;
                this.saveManifest();
            },
            log: (level, message) => {
                this.emit('plugin:log', { pluginId, level, message });
            },
            storage: {
                get: async (key) => {
                    try {
                        const data = await fs.readJSON(storagePath);
                        return data[key];
                    }
                    catch {
                        return undefined;
                    }
                },
                set: async (key, value) => {
                    let data = {};
                    try {
                        data = await fs.readJSON(storagePath);
                    }
                    catch {
                        // Start fresh
                    }
                    data[key] = value;
                    await fs.writeJSON(storagePath, data, { spaces: 2 });
                },
                delete: async (key) => {
                    try {
                        const data = await fs.readJSON(storagePath);
                        delete data[key];
                        await fs.writeJSON(storagePath, data, { spaces: 2 });
                    }
                    catch {
                        // Ignore
                    }
                },
            },
        };
    }
    /**
     * Execute a plugin command
     */
    async executeCommand(name, args, context) {
        const cmd = this.commands.get(name);
        if (!cmd) {
            throw new Error(`Command not found: ${name}`);
        }
        return cmd.handler(args, context);
    }
    /**
     * Execute a plugin tool
     */
    async executeTool(name, params) {
        const tool = this.tools.get(name);
        if (!tool) {
            throw new Error(`Tool not found: ${name}`);
        }
        return tool.tool.execute(params);
    }
    /**
     * Execute hooks
     */
    async executeHooks(event, data) {
        const handlers = this.hooks.get(event) || [];
        let result = data;
        for (const handler of handlers) {
            try {
                result = await handler.handler(result) || result;
            }
            catch (error) {
                this.emit('hook:error', { event, pluginId: handler.pluginId, error });
            }
        }
        return result;
    }
    /**
     * Check version compatibility
     */
    isCompatible(plugin) {
        try {
            return semver.satisfies(GROK_VERSION, plugin.engines.grok);
        }
        catch {
            return false;
        }
    }
    /**
     * Get default config values
     */
    getDefaultConfig(plugin) {
        const config = {};
        if (plugin.config?.properties) {
            for (const [key, prop] of Object.entries(plugin.config.properties)) {
                if (prop.default !== undefined) {
                    config[key] = prop.default;
                }
            }
        }
        return config;
    }
    /**
     * Enable a plugin
     */
    async enable(pluginId) {
        const plugin = this.installedPlugins.get(pluginId);
        if (!plugin) {
            throw new Error('Plugin is not installed. Use "plugins list" to see installed plugins.');
        }
        plugin.enabled = true;
        await this.saveManifest();
        await this.loadPlugin(pluginId);
        this.emit('plugin:enabled', { pluginId });
    }
    /**
     * Disable a plugin
     */
    async disable(pluginId) {
        const plugin = this.installedPlugins.get(pluginId);
        if (!plugin) {
            throw new Error('Plugin is not installed. Use "plugins list" to see installed plugins.');
        }
        await this.unloadPlugin(pluginId);
        plugin.enabled = false;
        await this.saveManifest();
        this.emit('plugin:disabled', { pluginId });
    }
    /**
     * Get installed plugins
     */
    getInstalled() {
        return Array.from(this.installedPlugins.values());
    }
    /**
     * Get loaded plugins
     */
    getLoaded() {
        return Array.from(this.loadedPlugins.keys());
    }
    /**
     * Get registered commands
     */
    getCommands() {
        return Array.from(this.commands.keys());
    }
    /**
     * Get registered tools
     */
    getTools() {
        return Array.from(this.tools.keys());
    }
    /**
     * Get tool definition by name
     */
    getToolDefinition(name) {
        return this.tools.get(name)?.tool;
    }
    /**
     * Format marketplace status
     */
    formatStatus() {
        const installed = this.getInstalled();
        const loaded = this.getLoaded();
        const lines = [
            '╔══════════════════════════════════════════════════════════════╗',
            '║                    🧩 PLUGIN MARKETPLACE                     ║',
            '╠══════════════════════════════════════════════════════════════╣',
            `║ Installed: ${installed.length}/${this.config.maxPlugins}${''.padEnd(45)}║`,
            `║ Loaded:    ${loaded.length}${''.padEnd(48)}║`,
            `║ Commands:  ${this.commands.size}${''.padEnd(48)}║`,
            `║ Tools:     ${this.tools.size}${''.padEnd(48)}║`,
            '╠══════════════════════════════════════════════════════════════╣',
        ];
        if (installed.length > 0) {
            lines.push('║ INSTALLED PLUGINS                                            ║');
            for (const plugin of installed.slice(0, 5)) {
                const status = plugin.enabled ? '✅' : '❌';
                lines.push(`║ ${status} ${plugin.name.padEnd(25)} v${plugin.version.padEnd(10)} [${plugin.category}] ║`);
            }
            if (installed.length > 5) {
                lines.push(`║ ... and ${installed.length - 5} more                                          ║`);
            }
        }
        else {
            lines.push('║ No plugins installed                                         ║');
        }
        lines.push('╠══════════════════════════════════════════════════════════════╣');
        lines.push('║ /plugin search <query> | /plugin install <id>                ║');
        lines.push('║ /plugin update | /plugin remove <id>                         ║');
        lines.push('╚══════════════════════════════════════════════════════════════╝');
        return lines.join('\n');
    }
    /**
     * Dispose
     */
    async dispose() {
        if (this.updateCheckerIntervalId) {
            clearInterval(this.updateCheckerIntervalId);
            this.updateCheckerIntervalId = null;
        }
        for (const pluginId of this.loadedPlugins.keys()) {
            await this.unloadPlugin(pluginId);
        }
        this.removeAllListeners();
    }
}
// Singleton
let marketplaceInstance = null;
export function getPluginMarketplace(config) {
    if (!marketplaceInstance) {
        marketplaceInstance = new PluginMarketplace(config);
    }
    return marketplaceInstance;
}
export function resetPluginMarketplace() {
    if (marketplaceInstance) {
        marketplaceInstance.dispose();
    }
    marketplaceInstance = null;
}
//# sourceMappingURL=marketplace.js.map