import { EventEmitter } from 'events';
import { existsSync, readdirSync, readFileSync } from 'fs';
import { join } from 'path';
const REQUIRED_MANIFEST_FIELDS = ['name', 'version', 'type', 'entryPoint'];
const VALID_TYPES = ['channel', 'tool', 'provider', 'integration'];
export class ExtensionLoader extends EventEmitter {
    extensions = new Map();
    lifecycles = new Map();
    searchPaths;
    constructor(searchPaths) {
        super();
        this.searchPaths = searchPaths || [
            join(process.cwd(), '.codebuddy', 'extensions'),
            join(process.env.HOME || '~', '.codebuddy', 'extensions'),
        ];
    }
    static parseManifest(dir) {
        const manifestPath = join(dir, 'extension.json');
        if (!existsSync(manifestPath)) {
            return null;
        }
        try {
            const raw = readFileSync(manifestPath, 'utf-8');
            const parsed = JSON.parse(raw);
            for (const field of REQUIRED_MANIFEST_FIELDS) {
                if (!parsed[field]) {
                    return null;
                }
            }
            if (!VALID_TYPES.includes(parsed['type'])) {
                return null;
            }
            return parsed;
        }
        catch {
            return null;
        }
    }
    discover() {
        const manifests = [];
        for (const searchPath of this.searchPaths) {
            if (!existsSync(searchPath)) {
                continue;
            }
            let entries;
            try {
                entries = readdirSync(searchPath, { withFileTypes: true })
                    .filter(d => d.isDirectory())
                    .map(d => d.name);
            }
            catch {
                continue;
            }
            for (const entry of entries) {
                const dir = join(searchPath, entry);
                const manifest = ExtensionLoader.parseManifest(dir);
                if (manifest) {
                    manifests.push(manifest);
                }
            }
        }
        return manifests;
    }
    load(name) {
        // Validate extension name to prevent path traversal
        if (!/^[a-zA-Z0-9_@][a-zA-Z0-9_\-./]*$/.test(name) || name.includes('..')) {
            return { error: `Invalid extension name: "${name}"` };
        }
        for (const searchPath of this.searchPaths) {
            const dir = join(searchPath, name);
            const manifest = ExtensionLoader.parseManifest(dir);
            if (manifest) {
                const instance = {
                    manifest,
                    path: dir,
                    status: 'loaded',
                    loadedAt: Date.now(),
                };
                this.extensions.set(manifest.name, instance);
                this.emit('loaded', instance);
                return instance;
            }
        }
        return { error: `Extension "${name}" not found in search paths` };
    }
    loadAll() {
        const manifests = this.discover();
        const instances = [];
        for (const manifest of manifests) {
            const result = this.load(manifest.name);
            if ('status' in result) {
                instances.push(result);
            }
        }
        return instances;
    }
    async activate(name, config) {
        const instance = this.extensions.get(name);
        if (!instance) {
            return false;
        }
        if (config) {
            const validation = this.validateConfig(name, config);
            if (!validation.valid) {
                instance.status = 'error';
                instance.error = validation.errors.join('; ');
                return false;
            }
        }
        const deps = this.checkDependencies(name);
        if (!deps.satisfied) {
            instance.status = 'error';
            instance.error = `Missing dependencies: ${deps.missing.join(', ')}`;
            return false;
        }
        const lifecycle = this.lifecycles.get(name);
        try {
            if (lifecycle?.onActivate) {
                await lifecycle.onActivate(config || {});
            }
            instance.status = 'active';
            this.emit('activated', instance);
            return true;
        }
        catch (err) {
            instance.status = 'error';
            instance.error = err instanceof Error ? err.message : String(err);
            return false;
        }
    }
    async deactivate(name) {
        const instance = this.extensions.get(name);
        if (!instance) {
            return false;
        }
        const lifecycle = this.lifecycles.get(name);
        try {
            if (lifecycle?.onDeactivate) {
                await lifecycle.onDeactivate();
            }
            instance.status = 'disabled';
            this.emit('deactivated', instance);
            return true;
        }
        catch (err) {
            instance.status = 'error';
            instance.error = err instanceof Error ? err.message : String(err);
            return false;
        }
    }
    get(name) {
        return this.extensions.get(name);
    }
    list(type) {
        const all = Array.from(this.extensions.values());
        if (type) {
            return all.filter(ext => ext.manifest.type === type);
        }
        return all;
    }
    validateConfig(name, config) {
        const instance = this.extensions.get(name);
        if (!instance) {
            return { valid: false, errors: [`Extension "${name}" not found`] };
        }
        const schema = instance.manifest.configSchema;
        if (!schema) {
            return { valid: true, errors: [] };
        }
        const errors = [];
        for (const [key, def] of Object.entries(schema)) {
            const value = config[key];
            if (def.required && value === undefined) {
                errors.push(`Missing required config field: ${key}`);
                continue;
            }
            if (value !== undefined && typeof value !== def.type) {
                errors.push(`Field "${key}" expected type "${def.type}", got "${typeof value}"`);
            }
        }
        return { valid: errors.length === 0, errors };
    }
    checkDependencies(name) {
        const instance = this.extensions.get(name);
        if (!instance) {
            return { satisfied: false, missing: [] };
        }
        const deps = instance.manifest.dependencies || [];
        const missing = deps.filter(dep => !this.extensions.has(dep));
        return { satisfied: missing.length === 0, missing };
    }
    async dispose() {
        const names = Array.from(this.extensions.keys());
        for (const name of names) {
            const lifecycle = this.lifecycles.get(name);
            try {
                if (lifecycle?.onDispose) {
                    await lifecycle.onDispose();
                }
            }
            catch {
                // best-effort cleanup
            }
        }
        this.extensions.clear();
        this.lifecycles.clear();
        this.emit('disposed');
    }
}
//# sourceMappingURL=extension-loader.js.map