import { RemoteRelayClient } from "../peers/peer-client.js";
import { PeerStore } from "../peers/peer-store.js";
import { pluginMarketplaceEntriesWithVersions } from "../plugins/plugin-marketplace.js";
import { objectRecord, optionalBooleanField, optionalStringField, readJsonBody, sendJson, } from "./web-dashboard-http.js";
export async function handleDashboardPluginRoute(req, res, url, options) {
    if (!url.pathname.startsWith("/api/plugins")) {
        return false;
    }
    const plugins = options.runtime.pluginService;
    if (req.method === "GET" && url.pathname === "/api/plugins") {
        const catalog = options.config.pluginsEnabled ? await plugins.catalog() : {
            workflowActions: [],
            webPanels: [],
            commands: [],
            agentAdapters: [],
            chatAdapters: [],
            artifactHandlers: [],
            diagnostics: [],
            collectors: [],
        };
        sendJson(res, 200, {
            enabled: options.config.pluginsEnabled,
            plugins: await plugins.list(),
            catalog,
        });
        return true;
    }
    if (req.method === "GET" && url.pathname === "/api/plugins/catalog") {
        assertPluginsWritable(options.config);
        sendJson(res, 200, await plugins.catalog());
        return true;
    }
    if (req.method === "GET" && url.pathname === "/api/plugins/marketplace") {
        const forceRefresh = url.searchParams.get("force") === "true" || url.searchParams.get("fresh") === "true";
        sendJson(res, 200, { entries: await pluginMarketplaceEntriesWithVersions({ forceRefresh }) });
        return true;
    }
    if (req.method === "POST" && url.pathname === "/api/plugins/validate") {
        const body = await readJsonBody(req);
        sendJson(res, 200, await plugins.validate(requiredString(body, "source")));
        return true;
    }
    if (req.method === "POST" && url.pathname === "/api/plugins/analyze") {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        sendJson(res, 200, await plugins.analyzeInstall({
            source: requiredString(body, "source"),
            ref: optionalStringField(body, "ref"),
            trustLevel: trustLevelField(body),
            expectedManifestHash: optionalStringField(body, "expectedManifestHash"),
            expectedPackageHash: optionalStringField(body, "expectedPackageHash"),
            signaturePublicKey: optionalStringField(body, "signaturePublicKey"),
            requireSignature: optionalBooleanField(body, "requireSignature"),
        }));
        return true;
    }
    if (req.method === "POST" && url.pathname === "/api/plugins/scaffold") {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        const created = await plugins.scaffold({
            targetDir: requiredString(body, "targetDir"),
            id: requiredString(body, "id"),
            name: optionalStringField(body, "name"),
            description: optionalStringField(body, "description"),
        });
        options.auditPluginAction("plugin_scaffold_created", `Created plugin scaffold ${created}`);
        sendJson(res, 200, { path: created });
        return true;
    }
    if (req.method === "POST" && url.pathname === "/api/plugins") {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        const source = requiredString(body, "source");
        if (!options.config.pluginGithubInstallEnabled && isGitHubSource(source)) {
            throw new Error("GitHub plugin installation is disabled by NORDRELAY_PLUGIN_GITHUB_INSTALL_ENABLED=false.");
        }
        const plugin = await plugins.install({
            source,
            ref: optionalStringField(body, "ref"),
            enable: optionalBooleanField(body, "enable") ?? false,
            approvePermissions: optionalBooleanField(body, "approvePermissions") ?? false,
            approvePermissionDiff: optionalBooleanField(body, "approvePermissionDiff") ?? false,
            force: optionalBooleanField(body, "force") ?? false,
            trustLevel: trustLevelField(body),
            expectedManifestHash: optionalStringField(body, "expectedManifestHash"),
            expectedPackageHash: optionalStringField(body, "expectedPackageHash"),
            signaturePublicKey: optionalStringField(body, "signaturePublicKey"),
            requireSignature: optionalBooleanField(body, "requireSignature"),
        });
        options.auditPluginAction("plugin_installed", `Installed plugin ${plugin.id} ${plugin.version}`);
        sendJson(res, 201, plugin);
        return true;
    }
    const id = pluginIdFromPath(url.pathname);
    if (!id) {
        return false;
    }
    if (req.method === "GET" && url.pathname === `/api/plugins/${id}`) {
        const plugin = await plugins.get(id);
        if (!plugin) {
            sendJson(res, 404, { error: "Plugin not found." });
            return true;
        }
        sendJson(res, 200, plugin);
        return true;
    }
    if (req.method === "DELETE" && url.pathname === `/api/plugins/${id}`) {
        assertPluginsWritable(options.config);
        await plugins.remove(id);
        options.auditPluginAction("plugin_removed", `Removed plugin ${id}`);
        sendJson(res, 200, { ok: true });
        return true;
    }
    if (req.method === "POST" && url.pathname === `/api/plugins/${id}/enable`) {
        assertPluginsWritable(options.config);
        const plugin = await plugins.enable(id);
        options.auditPluginAction("plugin_enabled", `Enabled plugin ${id}`);
        sendJson(res, 200, plugin);
        return true;
    }
    if (req.method === "POST" && url.pathname === `/api/plugins/${id}/disable`) {
        assertPluginsWritable(options.config);
        const plugin = await plugins.disable(id);
        options.auditPluginAction("plugin_disabled", `Disabled plugin ${id}`);
        sendJson(res, 200, plugin);
        return true;
    }
    if (req.method === "PATCH" && url.pathname === `/api/plugins/${id}/settings`) {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        const plugin = await plugins.updateSettings(id, objectRecord(body?.settings));
        options.auditPluginAction("plugin_updated", `Updated plugin settings for ${id}`);
        sendJson(res, 200, plugin);
        return true;
    }
    if (req.method === "POST" && url.pathname === `/api/plugins/${id}/manifest`) {
        assertPluginsWritable(options.config);
        const plugin = await plugins.updateManifest(id);
        options.auditPluginAction("plugin_updated", `Reloaded plugin manifest for ${id}`);
        sendJson(res, 200, plugin);
        return true;
    }
    if (req.method === "GET" && url.pathname === `/api/plugins/${id}/log`) {
        const maxBytes = Number.parseInt(url.searchParams.get("maxBytes") ?? "20000", 10);
        sendJson(res, 200, { id, log: await plugins.readLog(id, Number.isFinite(maxBytes) ? maxBytes : 20000) });
        return true;
    }
    if (req.method === "GET" && url.pathname === `/api/plugins/${id}/events`) {
        res.writeHead(200, {
            "content-type": "text/event-stream; charset=utf-8",
            "cache-control": "no-cache, no-transform",
            connection: "keep-alive",
        });
        const write = (event, eventName = "jobs") => {
            if (res.destroyed || res.writableEnded)
                return;
            res.write(`event: ${eventName}\ndata: ${JSON.stringify(event)}\n\n`);
        };
        const unsubscribe = plugins.subscribeEvents(id, (event) => write(event, event.type || "message"));
        const heartbeat = setInterval(() => {
            if (!res.destroyed && !res.writableEnded)
                res.write(": heartbeat\n\n");
        }, 25_000);
        heartbeat.unref?.();
        req.on("close", () => {
            clearInterval(heartbeat);
            unsubscribe();
        });
        return true;
    }
    if (req.method === "GET" && url.pathname === `/api/plugins/${id}/update-check`) {
        assertPluginsWritable(options.config);
        sendJson(res, 200, await plugins.checkUpdate(id));
        return true;
    }
    if (req.method === "POST" && url.pathname === `/api/plugins/${id}/update`) {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        const plugin = await plugins.update(id, {
            approvePermissionDiff: optionalBooleanField(body, "approvePermissionDiff") ?? false,
            signaturePublicKey: optionalStringField(body, "signaturePublicKey"),
            requireSignature: optionalBooleanField(body, "requireSignature"),
        });
        options.auditPluginAction("plugin_updated", `Updated plugin ${id}`);
        sendJson(res, 200, plugin);
        return true;
    }
    if (req.method === "GET" && url.pathname === `/api/plugins/${id}/jobs`) {
        sendJson(res, 200, { jobs: await plugins.listJobs(id) });
        return true;
    }
    if (req.method === "POST" && url.pathname === `/api/plugins/${id}/jobs`) {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        const job = await plugins.startCommandJob(id, requiredString(body, "command"), objectRecord(body?.input));
        options.auditPluginAction("plugin_updated", `Started plugin job ${job.id} for ${id}`);
        sendJson(res, 202, job);
        return true;
    }
    const jobMatch = new RegExp(`^/api/plugins/${escapeRegExp(id)}/jobs/([^/]+)(?:/(cancel))?$`).exec(url.pathname);
    if (jobMatch?.[1]) {
        const jobId = decodeURIComponent(jobMatch[1]);
        if (req.method === "GET" && !jobMatch[2]) {
            const job = await plugins.getJob(id, jobId);
            if (!job)
                sendJson(res, 404, { error: "Plugin job not found." });
            else
                sendJson(res, 200, job);
            return true;
        }
        if (req.method === "POST" && jobMatch[2] === "cancel") {
            assertPluginsWritable(options.config);
            sendJson(res, 200, await plugins.cancelJob(id, jobId));
            return true;
        }
    }
    if (req.method === "POST" && url.pathname === `/api/plugins/${id}/rollback`) {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        const plugin = await plugins.rollback(id, optionalStringField(body, "version"));
        options.auditPluginAction("plugin_updated", `Rolled back plugin ${id}`);
        sendJson(res, 200, plugin);
        return true;
    }
    if (req.method === "POST" && url.pathname === `/api/plugins/${id}/invoke`) {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        const result = await plugins.invokeWorkflowAction(id, requiredString(body, "actionId"), objectRecord(body?.input));
        sendJson(res, 200, result);
        return true;
    }
    if (req.method === "POST" && url.pathname === `/api/plugins/${id}/command`) {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        const result = await plugins.invokeCommand(id, requiredString(body, "command"), objectRecord(body?.input));
        sendJson(res, 200, result);
        return true;
    }
    if (req.method === "POST" && url.pathname === `/api/plugins/${id}/aggregate-command`) {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        const command = requiredString(body, "command");
        const input = objectRecord(body?.input);
        sendJson(res, 200, await invokePluginAggregateCommand(id, command, input, plugins, options));
        return true;
    }
    if (req.method === "POST" && url.pathname === `/api/plugins/${id}/panel`) {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        const result = await plugins.invokeWebPanel(id, requiredString(body, "panelId"), objectRecord(body?.input));
        sendJson(res, 200, result);
        return true;
    }
    if (req.method === "POST" && url.pathname === `/api/plugins/${id}/artifact-handler`) {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        const result = await plugins.invokeArtifactHandler(id, requiredString(body, "handlerId"), objectRecord(body?.input));
        sendJson(res, 200, result);
        return true;
    }
    if (req.method === "GET" && url.pathname === `/api/plugins/${id}/diagnostics`) {
        assertPluginsWritable(options.config);
        sendJson(res, 200, await plugins.invokeDiagnostics(id));
        return true;
    }
    if (req.method === "POST" && url.pathname === `/api/plugins/${id}/collector`) {
        assertPluginsWritable(options.config);
        const body = await readJsonBody(req);
        const result = await plugins.invokeCollector(id, requiredString(body, "collectorId"), objectRecord(body?.input));
        sendJson(res, 200, result);
        return true;
    }
    return false;
}
async function invokePluginAggregateCommand(pluginId, command, input, plugins, options) {
    const results = [];
    const localNode = {
        id: "local",
        name: "Local node",
        platform: process.platform,
    };
    try {
        const result = await plugins.invokeCommand(pluginId, command, input);
        results.push({ node: localNode, ok: result.ok !== false, result });
    }
    catch (error) {
        results.push({ node: localNode, ok: false, error: errorMessage(error) });
    }
    const store = new PeerStore(options.home);
    const client = new RemoteRelayClient(store, options.home);
    const peers = store.listPublic()
        .filter((peer) => peer.enabled !== false && canUsePluginAggregatePeer(peer, options));
    await Promise.all(peers.map(async (peer) => {
        const node = pluginAggregatePeerNode(peer);
        try {
            const result = await client.webProxy(peer.id, {
                method: "POST",
                path: `/api/plugins/${pluginId}/command`,
                query: {},
                body: { command, input },
                contextKey: `web:plugin-aggregate:${pluginId}`,
            }, options.activityActor, `web:plugin-aggregate:${pluginId}`, { timeoutMs: 12_000 });
            results.push({ node, ok: result?.ok !== false, result });
        }
        catch (error) {
            const message = errorMessage(error);
            if (!shouldSkipPluginAggregateError(message)) {
                results.push({ node, ok: false, error: message });
            }
        }
    }));
    return {
        command,
        generatedAt: new Date().toISOString(),
        results: results.sort((a, b) => String(a.node.name).localeCompare(String(b.node.name))),
    };
}
function canUsePluginAggregatePeer(peer, options) {
    if (!options.users.canUsePeerStrict(options.authUser, peer.id)) {
        return false;
    }
    return Boolean(peer.url || peer.direction === "inbound");
}
function pluginAggregatePeerNode(peer) {
    return {
        id: peer.id,
        name: peer.name || peer.id,
        platform: peer.remoteStatus ? `${peer.remoteStatus}${peer.remoteVersion ? ` · ${peer.remoteVersion}` : ""}` : undefined,
    };
}
function shouldSkipPluginAggregateError(message) {
    return /plugin not found|plugins are disabled|plugin is disabled|access denied|api key permissions/i.test(message);
}
function errorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
function assertPluginsWritable(config) {
    if (!config.pluginsEnabled) {
        throw new Error("Plugins are disabled by NORDRELAY_PLUGINS_ENABLED=false.");
    }
}
function pluginIdFromPath(pathname) {
    const match = /^\/api\/plugins\/([^/]+)(?:\/.*)?$/.exec(pathname);
    return match ? decodeURIComponent(match[1]) : null;
}
function requiredString(body, key) {
    const value = optionalStringField(objectRecord(body), key);
    if (!value) {
        throw new Error(`${key} is required.`);
    }
    return value;
}
function trustLevelField(body) {
    const value = optionalStringField(objectRecord(body), "trustLevel");
    return ["official", "verified", "community", "local", "untrusted"].includes(value ?? "") ? value : undefined;
}
function escapeRegExp(value) {
    return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function isGitHubSource(source) {
    return source.startsWith("github:") || /^https:\/\/github\.com\//i.test(source);
}
