// engine/plugins/loader.ts — Plugin discovery and loading (SPEC-085 AC-02)
import { readFile, readdir, stat } from 'node:fs/promises';
import { join, resolve } from 'node:path';
import { validateManifestStructure } from './validator.js';
const MANIFEST_FILENAME = 'plugin.json';
const DEFAULT_PLUGINS_DIR = 'planu-plugins';
/**
 * Returns the plugins directory path, honoring PLANU_PLUGINS_DIR env var.
 */
export function getPluginsDir(projectPath) {
    const envDir = process.env.PLANU_PLUGINS_DIR;
    if (envDir) {
        return resolve(envDir);
    }
    return join(projectPath, DEFAULT_PLUGINS_DIR);
}
/**
 * Discovers plugins in the plugins directory.
 * Reads plugin.json from each subdirectory and validates the manifest.
 * Returns PluginRegistration[] with state 'installed' or 'error'.
 */
export async function discoverPlugins(projectPath) {
    const pluginsDir = getPluginsDir(projectPath);
    const registrations = [];
    let entries;
    try {
        entries = await readdir(pluginsDir);
    }
    catch {
        // Directory doesn't exist — no plugins
        return [];
    }
    for (const entry of entries) {
        const pluginDir = join(pluginsDir, entry);
        try {
            const stats = await stat(pluginDir);
            if (!stats.isDirectory()) {
                continue;
            }
            const manifestPath = join(pluginDir, MANIFEST_FILENAME);
            const raw = await readFile(manifestPath, 'utf8');
            const parsed = JSON.parse(raw);
            const validation = validateManifestStructure(parsed);
            if (!validation.valid) {
                registrations.push({
                    manifest: {
                        id: entry,
                        name: entry,
                        version: '0.0.0',
                        author: '',
                        description: '',
                        license: '',
                        planuVersion: '*',
                        entryPoint: '',
                        permissions: [],
                    },
                    state: 'error',
                    loadedAt: new Date().toISOString(),
                    error: `Invalid manifest: ${validation.errors.join('; ')}`,
                });
                continue;
            }
            registrations.push({
                manifest: parsed,
                state: 'installed',
                loadedAt: new Date().toISOString(),
            });
        }
        catch (err) {
            /* v8 ignore next -- err is always Error from fs/JSON operations */
            const message = err instanceof Error ? err.message : String(err);
            registrations.push({
                manifest: {
                    id: entry,
                    name: entry,
                    version: '0.0.0',
                    author: '',
                    description: '',
                    license: '',
                    planuVersion: '*',
                    entryPoint: '',
                    permissions: [],
                },
                state: 'error',
                loadedAt: new Date().toISOString(),
                error: `Failed to load plugin: ${message}`,
            });
        }
    }
    return registrations;
}
/**
 * Dynamically imports a plugin's entry point module.
 * Returns the module's exports or throws on failure.
 */
export async function loadPluginModule(projectPath, manifest) {
    const pluginsDir = getPluginsDir(projectPath);
    const entryPath = join(pluginsDir, manifest.id, manifest.entryPoint);
    // Validate entryPoint doesn't escape
    const resolvedEntry = resolve(entryPath);
    const resolvedPluginDir = resolve(join(pluginsDir, manifest.id));
    /* v8 ignore next 3 -- path traversal guard, requires crafted manifest */
    if (!resolvedEntry.startsWith(resolvedPluginDir)) {
        throw new Error(`Plugin "${manifest.id}" entryPoint escapes plugin directory`);
    }
    try {
        const mod = (await import(resolvedEntry));
        return mod;
    }
    catch (err) {
        /* v8 ignore next -- err is always Error from import() */
        const message = err instanceof Error ? err.message : String(err);
        throw new Error(`Failed to load plugin "${manifest.id}" module: ${message}`, { cause: err });
    }
}
//# sourceMappingURL=loader.js.map