import { randomUUID } from "node:crypto";
import { listInstalledCliToolAgentContexts } from "../tools/registry.js";
import { listPiPackages } from "../pi-packages/store.js";
function getWebAppRoutes(app) {
    return [
        { label: "mountPath", prefix: app.mountPath },
        { label: "apiPrefix", prefix: app.apiPrefix },
    ];
}
function validateWebRoute(appName, label, prefix) {
    if (!prefix.startsWith("/")) {
        throw new Error(`Web app "${appName}" ${label} must start with "/"`);
    }
    if (prefix.length > 1 && prefix.endsWith("/")) {
        throw new Error(`Web app "${appName}" ${label} must not end with "/"`);
    }
}
function webRoutesOverlap(left, right) {
    return left === right || left === "/" || right === "/" || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
}
export class PiboPluginRegistry {
    tools = new Map();
    subagents = new Map();
    skills = new Map();
    contextFiles = new Map();
    profiles = new Map();
    profileAliases = new Map();
    gatewayActions = new Map();
    gatewaySlashCommands = new Map();
    channels = new Map();
    authService;
    webApps = new Map();
    eventListeners = new Set();
    productEventListeners = new Set();
    pluginIds = new Set();
    pluginNames = new Map();
    eventErrors = [];
    static create(options = {}) {
        const registry = new PiboPluginRegistry();
        for (const plugin of options.plugins ?? []) {
            registry.registerPlugin(plugin);
        }
        return registry;
    }
    registerPlugin(plugin) {
        if (this.pluginIds.has(plugin.id)) {
            throw new Error(`Plugin "${plugin.id}" is already registered`);
        }
        this.pluginIds.add(plugin.id);
        this.pluginNames.set(plugin.id, plugin.name ?? plugin.id);
        plugin.register(this.createApi(plugin.id));
    }
    registerTool(tool) {
        this.addUnique(this.tools, tool.name, tool, "tool");
    }
    registerTools(tools) {
        for (const tool of tools) {
            this.registerTool(tool);
        }
    }
    registerSubagent(subagent) {
        this.addUnique(this.subagents, subagent.name, subagent, "subagent");
    }
    registerSubagents(subagents) {
        for (const subagent of subagents) {
            this.registerSubagent(subagent);
        }
    }
    registerSkill(skill) {
        this.addUnique(this.skills, skill.name, skill, "skill");
    }
    registerContextFile(contextFile) {
        this.addUnique(this.contextFiles, contextFileKey(contextFile), contextFile, "context file");
    }
    upsertContextFile(contextFile) {
        this.contextFiles.set(contextFileKey(contextFile), contextFile);
    }
    removeContextFile(key) {
        this.contextFiles.delete(key);
    }
    registerProfile(profile) {
        this.addUnique(this.profiles, profile.name, profile, "profile");
        this.registerProfileAliases(profile);
    }
    upsertProfile(profile) {
        this.profiles.set(profile.name, profile);
        for (const [alias, profileName] of this.profileAliases.entries()) {
            if (profileName === profile.name)
                this.profileAliases.delete(alias);
        }
        this.registerProfileAliases(profile);
    }
    removeProfile(name) {
        const resolvedName = this.profileAliases.get(name) ?? name;
        if (!this.profiles.delete(resolvedName))
            return;
        for (const [alias, profileName] of this.profileAliases.entries()) {
            if (profileName === resolvedName)
                this.profileAliases.delete(alias);
        }
    }
    registerGatewayAction(action) {
        const slashCommands = this.getGatewaySlashCommandsToRegister(action);
        this.addUnique(this.gatewayActions, action.name, action, "gateway action");
        for (const slashCommand of slashCommands) {
            this.gatewaySlashCommands.set(slashCommand, action.name);
        }
    }
    registerChannel(channel) {
        this.addUnique(this.channels, channel.name, channel, "channel");
    }
    registerAuthService(service) {
        if (this.authService) {
            throw new Error(`Auth service "${this.authService.name}" is already registered`);
        }
        this.authService = service;
    }
    registerWebApp(app) {
        if (this.webApps.has(app.name)) {
            throw new Error(`Duplicate web app "${app.name}"`);
        }
        this.validateWebAppRoutes(app);
        this.webApps.set(app.name, app);
    }
    onEvent(listener) {
        this.eventListeners.add(listener);
    }
    onProductEvent(listener) {
        this.productEventListeners.add(listener);
        return () => {
            this.productEventListeners.delete(listener);
        };
    }
    createProfile(name) {
        const resolvedName = this.resolveProfileName(name);
        const profile = this.profiles.get(resolvedName);
        if (!profile)
            throw new Error(`Unknown profile "${name}"`);
        return profile.create(this.createProfileBuildContext());
    }
    getProfileNames() {
        return [...this.profiles.keys()];
    }
    getProfileInfos() {
        const context = this.createProfileBuildContext();
        return [...this.profiles.values()].map((profile) => {
            const sessionContext = profile.create(context);
            return {
                name: profile.name,
                description: profile.description,
                aliases: [...(profile.aliases ?? [])],
                nativeTools: sessionContext.tools.filter((tool) => tool.enabled !== false).map((tool) => tool.name),
                skills: sessionContext.skills.filter((skill) => skill.enabled !== false).map((skill) => skill.name),
                contextFiles: sessionContext.contextFiles.filter((contextFile) => contextFile.enabled !== false).map(contextFileKey),
                subagents: sessionContext.subagents.filter((subagent) => subagent.enabled !== false),
                mcpServers: [...sessionContext.mcpServers],
                piPackages: sessionContext.piPackages.filter((pkg) => pkg.enabled !== false).map((pkg) => pkg.id),
                builtinTools: sessionContext.builtinTools,
                builtinToolNames: [...sessionContext.builtinToolNames],
                autoContextFiles: sessionContext.autoContextFiles,
                runControl: sessionContext.toolPackages.runControl === true,
            };
        });
    }
    getCapabilityCatalog() {
        return {
            nativeTools: [...this.tools.values()].map((tool) => ({
                name: tool.name,
                description: tool.description,
                yieldable: tool.yieldable !== false,
                hasDefinition: tool.definition !== undefined,
                pluginId: tool.pluginId,
                pluginName: tool.pluginId ? this.pluginNames.get(tool.pluginId) : undefined,
                ...(tool.providerTool ? { providerTool: tool.providerTool } : {}),
            })),
            skills: [...this.skills.values()].map((skill) => ({
                name: skill.name,
                path: skill.path,
            })),
            subagents: [...this.subagents.values()].map((subagent) => ({
                name: subagent.name,
                description: subagent.description,
                targetProfile: subagent.targetProfile,
                timeoutMs: subagent.timeoutMs,
                maxDepth: subagent.maxDepth,
            })),
            contextFiles: [...this.contextFiles.entries()].map(([key, contextFile]) => ({
                key,
                label: contextFile.label,
                path: contextFile.path,
                scope: contextFile.scope ?? "global",
                source: contextFile.source ?? "plugin",
                pluginId: contextFile.pluginId,
                pluginName: contextFile.pluginId ? this.pluginNames.get(contextFile.pluginId) : undefined,
                agentProfileName: contextFile.agentProfileName,
            })),
            packages: [
                {
                    name: "pibo-run-control",
                    description: "Expose pibo_run_* tools as one package for yielded native tools and subagents.",
                    toolNames: [
                        "pibo_run_start",
                        "pibo_run_list",
                        "pibo_run_status",
                        "pibo_run_wait",
                        "pibo_run_read",
                        "pibo_run_cancel",
                        "pibo_run_ack",
                    ],
                },
            ],
            piboTools: listInstalledCliToolAgentContexts(),
            mcpServers: [],
            piPackages: listPiPackages(),
        };
    }
    resolveProfileName(name) {
        const resolvedName = this.profileAliases.get(name) ?? name;
        if (!this.profiles.has(resolvedName)) {
            throw new Error(`Unknown profile "${name}". Available profiles: ${this.getProfileNames().join(", ")}`);
        }
        return resolvedName;
    }
    getGatewayAction(name) {
        return this.gatewayActions.get(name);
    }
    getGatewayActionInfos() {
        return [...this.gatewayActions.values()]
            .filter((action) => action.hidden !== true)
            .map((action) => ({
            name: action.name,
            description: action.description,
            slashCommands: [...(action.slashCommands ?? [])],
        }));
    }
    getChannels() {
        return [...this.channels.values()];
    }
    getAuthService() {
        return this.authService;
    }
    getWebApps() {
        return [...this.webApps.values()];
    }
    getEventErrors() {
        return [...this.eventErrors];
    }
    notifyEvent(event) {
        for (const listener of this.eventListeners) {
            try {
                listener(event);
            }
            catch (error) {
                this.eventErrors.push(error instanceof Error ? error.message : String(error));
            }
        }
    }
    emitProductEvent(input) {
        const event = {
            ...input,
            id: input.id ?? randomUUID(),
            createdAt: input.createdAt ?? new Date().toISOString(),
        };
        for (const listener of this.productEventListeners) {
            try {
                listener(event);
            }
            catch (error) {
                this.eventErrors.push(error instanceof Error ? error.message : String(error));
            }
        }
        return event;
    }
    createApi(pluginId) {
        const withPluginToolContext = (tool) => ({ ...tool, pluginId: tool.pluginId ?? pluginId });
        const withPluginContext = (contextFile) => (contextFile.source === "managed" ? contextFile : { ...contextFile, pluginId: contextFile.pluginId ?? pluginId });
        return {
            registerTool: (tool) => this.registerTool(withPluginToolContext(tool)),
            registerTools: (tools) => this.registerTools(tools.map(withPluginToolContext)),
            registerSubagent: (subagent) => this.registerSubagent(subagent),
            registerSubagents: (subagents) => this.registerSubagents(subagents),
            registerSkill: (skill) => this.registerSkill(skill),
            registerContextFile: (contextFile) => this.registerContextFile(withPluginContext(contextFile)),
            upsertContextFile: (contextFile) => this.upsertContextFile(withPluginContext(contextFile)),
            removeContextFile: (key) => this.removeContextFile(key),
            registerProfile: (profile) => this.registerProfile(profile),
            registerGatewayAction: (action) => this.registerGatewayAction(action),
            registerChannel: (channel) => this.registerChannel(channel),
            registerAuthService: (service) => this.registerAuthService(service),
            registerWebApp: (app) => this.registerWebApp(app),
            onEvent: (listener) => this.onEvent(listener),
            emitProductEvent: (event) => this.emitProductEvent(event),
            onProductEvent: (listener) => this.onProductEvent(listener),
        };
    }
    createProfileBuildContext() {
        return {
            getTool: (name) => this.getRequired(this.tools, name, "tool"),
            getTools: (names) => names.map((name) => this.getRequired(this.tools, name, "tool")),
            getSkill: (name) => this.getRequired(this.skills, name, "skill"),
            getContextFile: (key) => this.getRequired(this.contextFiles, key, "context file"),
            getSubagent: (name) => this.getRequired(this.subagents, name, "subagent"),
            getSubagents: (names) => names.map((name) => this.getRequired(this.subagents, name, "subagent")),
        };
    }
    getRequired(map, key, label) {
        const value = map.get(key);
        if (!value) {
            throw new Error(`Unknown ${label} "${key}"`);
        }
        return value;
    }
    registerProfileAliases(profile) {
        for (const alias of profile.aliases ?? []) {
            const existingAliasProfile = this.profileAliases.get(alias);
            if (this.profiles.has(alias) || (existingAliasProfile && existingAliasProfile !== profile.name)) {
                throw new Error(`Profile alias "${alias}" is already registered`);
            }
            this.profileAliases.set(alias, profile.name);
        }
    }
    addUnique(map, key, value, label) {
        if (map.has(key)) {
            throw new Error(`Duplicate ${label} "${key}"`);
        }
        map.set(key, value);
    }
    validateWebAppRoutes(app) {
        const routes = getWebAppRoutes(app);
        for (const route of routes) {
            validateWebRoute(app.name, route.label, route.prefix);
        }
        for (const existing of this.webApps.values()) {
            for (const route of routes) {
                for (const existingRoute of getWebAppRoutes(existing)) {
                    if (webRoutesOverlap(route.prefix, existingRoute.prefix)) {
                        throw new Error(`Web app route "${route.prefix}" for "${app.name}" overlaps ${existingRoute.label} "${existingRoute.prefix}" from web app "${existing.name}"`);
                    }
                }
            }
        }
    }
    getGatewaySlashCommandsToRegister(action) {
        if (action.hidden === true)
            return [];
        const slashCommands = [];
        for (const slashCommand of action.slashCommands ?? []) {
            if (!slashCommand || slashCommand.startsWith("/") || /\s/.test(slashCommand)) {
                throw new Error(`Invalid slash command "${slashCommand}" for gateway action "${action.name}"`);
            }
            const existingAction = this.gatewaySlashCommands.get(slashCommand);
            if (existingAction) {
                throw new Error(`Duplicate slash command "${slashCommand}" for gateway actions "${existingAction}" and "${action.name}"`);
            }
            if (slashCommands.includes(slashCommand)) {
                throw new Error(`Duplicate slash command "${slashCommand}" for gateway action "${action.name}"`);
            }
            slashCommands.push(slashCommand);
        }
        return slashCommands;
    }
}
function contextFileKey(contextFile) {
    return contextFile.key ?? contextFile.label ?? contextFile.path;
}
export function definePiboPlugin(plugin) {
    return plugin;
}
