import { createHash } from "node:crypto";
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { AuthStorage, createAgentSessionServices, createSyntheticSourceInfo, ModelRegistry, SettingsManager, } from "@sheason/pi-coding-agent";
import { getWorkspaceDir } from "../config.js";
import { remoteMcpResourceToken } from "../mcp/remote-mcp-tools.js";
import { mergeConfigLayers } from "./merge-config.js";
export const D_PI_PEER_RESOURCES_SKILL_NAME = "d-pi-peer-resources";
export function materializeAggregatedModelsConfig(options) {
    const merged = mergeConfigLayers(options.layers);
    const mergedModelsFile = options.mergedModelsFile ?? join(getWorkspaceDir(options.cwd), "merged-models.json");
    mkdirSync(getWorkspaceDir(options.cwd), { recursive: true });
    writeFileSync(mergedModelsFile, `${JSON.stringify(merged.models, null, 2)}\n`, "utf8");
    return mergedModelsFile;
}
function createReadOnlySettingsManager(settings) {
    const content = JSON.stringify(settings && typeof settings === "object" ? settings : {});
    return SettingsManager.fromStorage({
        withLock(_scope, fn) {
            return fn(content);
        },
    });
}
function safePathToken(value) {
    const safe = value.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
    const hash = createHash("sha256").update(value).digest("hex").slice(0, 8);
    if (safe.length === 0) {
        return `x_${hash}`;
    }
    return safe.length <= 40 ? safe : `${safe.slice(0, 40)}_${hash}`;
}
function materializePeerResources(cwd, layers, peerMcpSnapshots = []) {
    const root = join(getWorkspaceDir(cwd), "peer-resources");
    const metaDir = join(root, D_PI_PEER_RESOURCES_SKILL_NAME);
    const metaFilePath = join(metaDir, "SKILL.md");
    mkdirSync(metaDir, { recursive: true });
    const promptPaths = [];
    const themePaths = [];
    const extensionPaths = [];
    const lines = [
        "---",
        `name: ${D_PI_PEER_RESOURCES_SKILL_NAME}`,
        "description: Discover peer-provided D-Pi skills, context, and MCP capabilities.",
        "---",
        "",
        "# D-Pi Peer Resources",
        "",
        "Use this meta skill when hub-local skills and MCP tools do not cover the task.",
        "Peer resources change dynamically; this file is regenerated from the current peer configuration.",
        "",
    ];
    let hasPeerResources = false;
    for (const layer of layers) {
        if (layer.source?.kind !== "peer") {
            continue;
        }
        const peerId = layer.source.peerId;
        const peerToken = safePathToken(peerId);
        if ((layer.skills?.length ?? 0) > 0) {
            hasPeerResources = true;
            lines.push(`## Peer ${peerId} (${layer.source.scope})`, "");
        }
        const skills = (layer.skills ?? []).slice().sort((a, b) => a.name.localeCompare(b.name));
        for (const skill of skills) {
            const sourceKey = [peerId, layer.source.scope, skill.filePath, skill.name].join("\0");
            const sourceToken = createHash("sha256").update(sourceKey).digest("hex").slice(0, 12);
            const dir = join(root, "peers", peerToken, layer.source.scope, "skills", sourceToken, skill.name);
            mkdirSync(dir, { recursive: true });
            const filePath = join(dir, "SKILL.md");
            writeFileSync(filePath, skill.content, "utf8");
            lines.push(`### Skill: ${skill.name}`, "", `Description: ${skill.description}`, `Location: ${filePath}`, "");
            if (skill.disableModelInvocation === true) {
                lines.push("This skill is explicit-invocation only.", "");
            }
        }
        const sortedPrompts = (layer.prompts ?? []).slice().sort((a, b) => a.name.localeCompare(b.name));
        for (const prompt of sortedPrompts) {
            const sourceKey = [peerId, layer.source.scope, prompt.filePath, prompt.name].join("\0");
            const sourceToken = createHash("sha256").update(sourceKey).digest("hex").slice(0, 12);
            const dir = join(root, "peers", peerToken, layer.source.scope, "prompts", sourceToken);
            mkdirSync(dir, { recursive: true });
            const filePath = join(dir, `${prompt.name}.md`);
            writeFileSync(filePath, prompt.content, "utf8");
            promptPaths.push(filePath);
            lines.push(`### Prompt: ${prompt.name}`, "", `Location: ${filePath}`, "");
        }
        const sortedThemes = (layer.themes ?? []).slice().sort((a, b) => a.name.localeCompare(b.name));
        for (const theme of sortedThemes) {
            const sourceKey = [peerId, layer.source.scope, theme.filePath, theme.name].join("\0");
            const sourceToken = createHash("sha256").update(sourceKey).digest("hex").slice(0, 12);
            const dir = join(root, "peers", peerToken, layer.source.scope, "themes", sourceToken);
            mkdirSync(dir, { recursive: true });
            const filePath = join(dir, `${theme.name}.json`);
            writeFileSync(filePath, theme.content, "utf8");
            themePaths.push(filePath);
            lines.push(`### Theme: ${theme.name}`, "", `Location: ${filePath}`, "");
        }
        const sortedExtensions = (layer.extensions ?? []).slice().sort((a, b) => a.name.localeCompare(b.name));
        for (const ext of sortedExtensions) {
            const sourceKey = [peerId, layer.source.scope, ext.filePath, ext.name].join("\0");
            const sourceToken = createHash("sha256").update(sourceKey).digest("hex").slice(0, 12);
            const dir = join(root, "peers", peerToken, layer.source.scope, "extensions", sourceToken);
            mkdirSync(dir, { recursive: true });
            const extSuffix = ext.filePath.endsWith(".js") ? ".js" : ".ts";
            const filePath = join(dir, `${ext.name}${extSuffix}`);
            writeFileSync(filePath, ext.content, "utf8");
            extensionPaths.push(filePath);
            lines.push(`### Extension: ${ext.name}`, "", `Location: ${filePath}`, "");
        }
    }
    if (!hasPeerResources) {
        lines.push("No peer-provided skills or context files are currently available.", "");
    }
    lines.push("## Peer MCP capabilities", "");
    lines.push("Call `peer_mcp` with `peer-id`, exact `tool-name`, and `args` when a listed peer MCP tool is needed.", "");
    let hasPeerMcpTools = false;
    for (const peer of [...peerMcpSnapshots].sort((a, b) => a.peerId.localeCompare(b.peerId))) {
        const runningServers = peer.servers.filter((server) => server.status === "running");
        if (runningServers.length === 0) {
            continue;
        }
        lines.push(`### Peer MCP: ${peer.peerId}`, "");
        for (const server of [...runningServers].sort((a, b) => a.name.localeCompare(b.name))) {
            const resourceId = server.resourceId ?? server.name;
            lines.push(`- Server: ${server.name}`);
            lines.push(`  Resource ID: ${resourceId}`);
            for (const tool of [...server.capabilities.tools].sort((a, b) => a.name.localeCompare(b.name))) {
                hasPeerMcpTools = true;
                const remoteToolName = `mcp__${remoteMcpResourceToken(peer.peerId, resourceId)}__${tool.name}`;
                lines.push(`  - Tool: ${remoteToolName}`);
                if (tool.description) {
                    lines.push(`    Description: ${tool.description}`);
                }
            }
            lines.push("");
        }
    }
    if (!hasPeerMcpTools) {
        lines.push("No peer-provided MCP tools are currently available.", "");
    }
    writeFileSync(metaFilePath, `${lines.join("\n").trimEnd()}\n`, "utf8");
    const baseDir = dirname(metaFilePath);
    return {
        metaSkill: {
            name: D_PI_PEER_RESOURCES_SKILL_NAME,
            description: "Discover peer-provided D-Pi skills, context, and MCP capabilities.",
            filePath: metaFilePath,
            baseDir,
            sourceInfo: createSyntheticSourceInfo(metaFilePath, {
                source: "d-pi",
                scope: "temporary",
                baseDir,
            }),
            disableModelInvocation: false,
        },
        promptPaths,
        themePaths,
        extensionPaths,
    };
}
export async function createAggregatedAgentSessionServices(options) {
    const merged = mergeConfigLayers(options.layers);
    const _peerResources = materializePeerResources(options.cwd, options.layers, options.peerMcpSnapshots);
    const mergedModelsFile = materializeAggregatedModelsConfig({
        cwd: options.cwd,
        layers: options.layers,
        mergedModelsFile: options.mergedModelsFile,
    });
    const authStorage = AuthStorage.inMemory(merged.auth);
    const settingsManager = createReadOnlySettingsManager(merged.settings);
    const upstreamResourceOptions = options.resourceLoaderOptions;
    const upstreamSkillsOverride = upstreamResourceOptions?.skillsOverride;
    // Extract hub-only systemPrompt to avoid peer systemPrompt leaking into the
    // system prompt override. Peer systemPrompt changes must not affect the LLM cache.
    let hubSystemPrompt;
    for (const layer of options.layers) {
        if (layer.source?.kind === "hub" && layer.systemPrompt !== undefined) {
            hubSystemPrompt = layer.systemPrompt;
        }
    }
    const services = await createAgentSessionServices({
        cwd: options.cwd,
        agentDir: options.agentDir,
        authStorage,
        settingsManager,
        modelRegistry: ModelRegistry.create(authStorage, mergedModelsFile),
        resourceLoaderOptions: {
            ...(upstreamResourceOptions ?? {}),
            // Peer skills are NOT injected into the system prompt to preserve LLM cache.
            // Agents discover peer resources via the discover_peer_resources tool instead.
            skillsOverride: upstreamSkillsOverride ?? undefined,
            // Peer contextFiles are NOT injected into the system prompt to preserve LLM cache.
            // Agents discover peer context via the discover_peer_resources tool instead.
            // Only hub-layer systemPrompt is used; peer systemPrompt is excluded to preserve LLM cache.
            systemPromptOverride: (base) => base ?? hubSystemPrompt,
            appendSystemPromptOverride: (base) => [
                ...base,
                // Fixed paragraph directing agent to discover peer resources on demand.
                // This text is static and does not change when peers connect/disconnect,
                // so it does not invalidate the LLM prompt cache.
                "Peer agents may provide additional skills, context files, MCP tools, extensions, prompts, and themes. Use the discover_peer_resources tool to list available peer resources. Use peer_mcp to call peer MCP tools by exact name. If a peer capability is needed long-term, move it to the hub instead of relying on the peer.",
            ],
            // Peer prompts, themes, and extensions are NOT injected to preserve LLM cache
            // and ensure peer connect/disconnect does not affect agent behavior.
            // Agents discover peer resources via the discover_peer_resources tool instead.
            // Long-lived capabilities should be moved to the hub.
        },
    });
    return { services, mergedModelsFile };
}
//# sourceMappingURL=agent-config-services.js.map