import { readFile } from "node:fs/promises";
import { isAbsolute, resolve } from "node:path";
import { AuthStorage, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createBashToolDefinition, getAgentDir, InteractiveMode, SessionManager, } from "@mariozechner/pi-coding-agent";
import { DEFAULT_BUILTIN_TOOL_NAMES, } from "./profiles.js";
import { createDefaultPiboProfile } from "../plugins/builtin.js";
import { createSubagentToolDefinitions, createSubagentToolName, } from "../subagents/tool.js";
import { createRunToolDefinitions } from "../runs/tools.js";
import { getInstalledCliToolContextFile } from "../tools/registry.js";
import { createCodexCompatToolDefinitions } from "../tools/codex-compat.js";
import { createCodexCompatExtension } from "./codex-compat.js";
import { createWebSearchProviderExtension, isWebSearchProviderTool } from "../tools/web-search.js";
import { getMcpAgentContextFile } from "../mcp/agent-context.js";
import { createPiboSystemPromptTemplateExtension } from "./system-prompt-template.js";
import { getActivePiboBasePromptPath } from "./base-prompt.js";
import { createPiboCompactionPromptExtension } from "./compaction-prompt.js";
import { getPiPackageRuntimeOptions } from "../pi-packages/runtime.js";
import { getDefaultPiboWorkspace } from "./workspace.js";
function resolveProfilePath(cwd, path) {
    return isAbsolute(path) ? path : resolve(cwd, path);
}
async function loadContextFiles(cwd, contextFiles) {
    const loaded = [];
    for (const contextFile of contextFiles) {
        if (contextFile.enabled === false)
            continue;
        const path = resolveProfilePath(cwd, contextFile.path);
        const content = await readFile(path, "utf-8");
        loaded.push({ path, content });
    }
    return loaded;
}
function mergeContextFiles(base, additional) {
    const seen = new Set();
    const merged = [];
    for (const contextFile of [...base, ...additional]) {
        if (seen.has(contextFile.path))
            continue;
        seen.add(contextFile.path);
        merged.push(contextFile);
    }
    return merged;
}
function collectResourceDiagnostics(resourceDiagnostics) {
    return resourceDiagnostics.map((diagnostic) => ({
        type: diagnostic.type === "collision" ? "warning" : diagnostic.type,
        message: diagnostic.path ? `${diagnostic.path}: ${diagnostic.message}` : diagnostic.message,
    }));
}
function getEnabledSkillPaths(cwd, profile) {
    return profile.skills
        .filter((skill) => skill.enabled !== false)
        .map((skill) => resolveProfilePath(cwd, skill.path));
}
function getEnabledToolDefinitions(profile, options, subagentRunner, runToolController) {
    const profileTools = profile.tools.filter(hasEnabledToolDefinition);
    const codexCompatEnabled = profile.toolPackages.codexCompat === true;
    const runControlEnabled = profile.toolPackages.runControl === true;
    const runControlBashTool = runControlEnabled && runToolController
        ? createBashToolDefinition(options.runtimeCwd, {
            commandPrefix: options.shellCommandPrefix,
            shellPath: options.shellPath,
        })
        : undefined;
    const subagentTools = subagentRunner
        ? createSubagentToolDefinitions(profile.subagents, subagentRunner)
        : [];
    const codexCompatTools = codexCompatEnabled
        ? createCodexCompatToolDefinitions()
        : [];
    const yieldableTools = [
        ...(runControlBashTool ? [runControlBashTool] : []),
        ...profileTools.filter((tool) => tool.yieldable !== false).map((tool) => tool.definition),
        ...subagentTools,
        ...codexCompatTools,
    ];
    const runTools = runControlEnabled && runToolController && yieldableTools.length > 0
        ? createRunToolDefinitions(yieldableTools, runToolController)
        : [];
    return [
        ...(runControlBashTool ? [runControlBashTool] : []),
        ...profileTools.map((tool) => tool.definition),
        ...subagentTools,
        ...codexCompatTools,
        ...runTools,
    ];
}
function hasEnabledToolDefinition(tool) {
    return tool.enabled !== false && tool.definition !== undefined;
}
function isGeneratedPiboTool(name) {
    return name.startsWith("pibo_subagent_") || name.startsWith("pibo_run_");
}
function getBuiltinToolAllowlist(profile, customTools) {
    if (profile.builtinTools === "disabled")
        return undefined;
    const defaultBuiltinTools = new Set(DEFAULT_BUILTIN_TOOL_NAMES);
    const selectedBuiltinTools = profile.builtinToolNames.filter((name) => defaultBuiltinTools.has(name));
    if (selectedBuiltinTools.length === DEFAULT_BUILTIN_TOOL_NAMES.length)
        return undefined;
    return [...selectedBuiltinTools, ...customTools.map((tool) => tool.name)];
}
function getProfileExtensionFactories(profile, extensionFactories) {
    const piboPromptTemplateExtension = createPiboSystemPromptTemplateExtension();
    const piboCompactionPromptExtension = createPiboCompactionPromptExtension();
    const providerToolExtensions = profile.tools
        .filter((tool) => tool.enabled !== false)
        .filter(isWebSearchProviderTool)
        .map((tool) => createWebSearchProviderExtension(tool.providerTool));
    if (profile.toolPackages.codexCompat !== true) {
        return [
            piboPromptTemplateExtension,
            piboCompactionPromptExtension,
            ...providerToolExtensions,
            ...(extensionFactories ?? []),
        ];
    }
    return [
        piboPromptTemplateExtension,
        piboCompactionPromptExtension,
        createCodexCompatExtension({
            isChildSession: profile.parentSessionId !== undefined,
        }),
        ...providerToolExtensions,
        ...(extensionFactories ?? []),
    ];
}
function createInspectionSubagentRunner() {
    return {
        async runSubagent() {
            throw new Error("Profile inspection cannot execute subagents");
        },
    };
}
function createInspectionRunToolController() {
    const fail = () => {
        throw new Error("Profile inspection cannot execute run-control tools");
    };
    return {
        startToolRun: fail,
        listRuns: () => [],
        getRunStatus: fail,
        waitForRun: fail,
        readRun: fail,
        cancelRun: fail,
        ackRun: fail,
    };
}
async function createSessionManager(cwd, profile, persistSession) {
    if (persistSession && profile.sessionId) {
        const existing = (await SessionManager.list(cwd)).find((session) => session.id === profile.sessionId);
        if (existing)
            return SessionManager.open(existing.path, undefined, cwd);
    }
    const sessionManager = persistSession ? SessionManager.create(cwd) : SessionManager.inMemory(cwd);
    if (profile.sessionId) {
        sessionManager.newSession({ id: profile.sessionId, parentSession: profile.parentSessionId });
    }
    return sessionManager;
}
export async function createPiboRuntime(options = {}) {
    const cwd = options.cwd ?? getDefaultPiboWorkspace();
    const profile = options.profile ?? createDefaultPiboProfile();
    const agentDir = getAgentDir();
    const sessionManager = await createSessionManager(cwd, profile, options.persistSession !== false);
    const authStorage = AuthStorage.create();
    const createRuntime = async ({ cwd: runtimeCwd, agentDir: runtimeAgentDir, sessionManager: runtimeSessionManager, sessionStartEvent, }) => {
        const contextFiles = await loadContextFiles(runtimeCwd, profile.contextFiles);
        const installedToolContextFile = getInstalledCliToolContextFile();
        const mcpAgentContextFile = await getMcpAgentContextFile(profile.mcpServers);
        const skillPaths = getEnabledSkillPaths(runtimeCwd, profile);
        const piPackageOptions = getPiPackageRuntimeOptions(runtimeCwd, profile);
        const services = await createAgentSessionServices({
            cwd: runtimeCwd,
            agentDir: runtimeAgentDir,
            authStorage,
            resourceLoaderOptions: {
                ...piPackageOptions.resourceLoaderOptions,
                additionalSkillPaths: skillPaths,
                extensionFactories: getProfileExtensionFactories(profile, options.extensionFactories),
                noExtensions: true,
                noSkills: true,
                noPromptTemplates: true,
                noThemes: true,
                noContextFiles: profile.autoContextFiles === false,
                systemPrompt: getActivePiboBasePromptPath(runtimeCwd),
                agentsFilesOverride: (base) => ({
                    agentsFiles: mergeContextFiles(base.agentsFiles, [
                        ...contextFiles,
                        ...(installedToolContextFile ? [installedToolContextFile] : []),
                        ...(mcpAgentContextFile ? [mcpAgentContextFile] : []),
                    ]),
                }),
            },
        });
        const customTools = getEnabledToolDefinitions(profile, {
            runtimeCwd,
            shellCommandPrefix: services.settingsManager.getShellCommandPrefix(),
            shellPath: services.settingsManager.getShellPath(),
        }, options.subagentRunner, options.runToolController);
        const created = await createAgentSessionFromServices({
            services,
            sessionManager: runtimeSessionManager,
            sessionStartEvent,
            model: resolveProfileModel(profile, services),
            thinkingLevel: options.thinkingLevel,
            customTools,
            noTools: profile.builtinTools === "disabled" ? "builtin" : undefined,
            tools: getBuiltinToolAllowlist(profile, customTools),
        });
        const resourceLoader = services.resourceLoader;
        const diagnostics = [
            ...piPackageOptions.diagnostics,
            ...services.diagnostics,
            ...collectResourceDiagnostics(resourceLoader.getSkills().diagnostics),
            ...resourceLoader.getExtensions().errors.map(({ path, error }) => ({
                type: "error",
                message: `Failed to load extension "${path}": ${error}`,
            })),
        ];
        return {
            ...created,
            services,
            diagnostics,
        };
    };
    return createAgentSessionRuntime(createRuntime, {
        cwd,
        agentDir,
        sessionManager,
    });
}
function resolveProfileModel(profile, services) {
    if (!profile.model)
        return undefined;
    const model = services.modelRegistry.find(profile.model.provider, profile.model.id);
    if (!model) {
        throw new Error(`Profile "${profile.profileName}" requests unknown model ${profile.model.provider}/${profile.model.id}.`);
    }
    if (!services.modelRegistry.hasConfiguredAuth(model)) {
        throw new Error(`Profile "${profile.profileName}" requires configured auth for ${profile.model.provider}/${profile.model.id}.`);
    }
    return model;
}
export async function inspectPiboProfile(options = {}) {
    const cwd = options.cwd ?? process.cwd();
    const profile = options.profile ?? createDefaultPiboProfile();
    const hasEnabledSubagents = profile.subagents.some((subagent) => subagent.enabled !== false);
    const hasYieldableTools = profile.toolPackages.runControl === true ||
        hasEnabledSubagents ||
        profile.tools.some((tool) => tool.enabled !== false && tool.definition !== undefined && tool.yieldable !== false);
    const runtime = await createPiboRuntime({
        cwd,
        profile,
        persistSession: false,
        subagentRunner: options.subagentRunner ?? (hasEnabledSubagents ? createInspectionSubagentRunner() : undefined),
        runToolController: options.runToolController ?? (hasYieldableTools ? createInspectionRunToolController() : undefined),
    });
    try {
        const resourceLoader = runtime.services.resourceLoader;
        const activeToolNames = new Set(runtime.session.getActiveToolNames());
        const registeredToolNames = new Set(runtime.session.getAllTools().map((tool) => tool.name));
        const profileToolNames = new Set(profile.tools.map((tool) => tool.name));
        const generatedTools = runtime.session
            .getAllTools()
            .filter((tool) => isGeneratedPiboTool(tool.name) && !profileToolNames.has(tool.name))
            .map((tool) => ({
            name: tool.name,
            hasDefinition: true,
            registered: true,
            active: activeToolNames.has(tool.name),
        }));
        return {
            profileName: profile.profileName,
            skills: resourceLoader.getSkills().skills.map((skill) => ({
                name: skill.name,
                path: skill.filePath,
            })),
            tools: profile.tools.map((tool) => ({
                name: tool.name,
                hasDefinition: Boolean(tool.definition),
                registered: registeredToolNames.has(tool.name) || tool.providerTool !== undefined,
                active: activeToolNames.has(tool.name) || tool.providerTool !== undefined,
            })).concat(generatedTools),
            subagents: profile.subagents.map((subagent) => {
                const toolName = createSubagentToolName(subagent.name);
                return {
                    name: subagent.name,
                    targetProfile: subagent.targetProfile,
                    active: activeToolNames.has(toolName),
                };
            }),
            piPackages: profile.piPackages.map((pkg) => ({
                id: pkg.id,
                active: pkg.enabled !== false,
            })),
            contextFiles: resourceLoader.getAgentsFiles().agentsFiles.map((contextFile) => ({
                path: contextFile.path,
                bytes: Buffer.byteLength(contextFile.content, "utf-8"),
            })),
            diagnostics: [...runtime.diagnostics],
        };
    }
    finally {
        await runtime.dispose();
    }
}
export async function runPiboTui(options = {}) {
    const profile = options.profile ?? createDefaultPiboProfile();
    const hasEnabledSubagents = profile.subagents.some((subagent) => subagent.enabled !== false);
    if (hasEnabledSubagents && (!options.subagentRunner || !options.runToolController)) {
        console.error(`Error: Profile "${profile.profileName}" uses subagents and requires the routed pibo runtime. ` +
            `Use "npm run tui:routed -- ${profile.profileName}" for local TUI QA.`);
        process.exitCode = 1;
        return;
    }
    const runtime = await createPiboRuntime({ ...options, profile });
    try {
        const fatal = runtime.diagnostics.find((diagnostic) => diagnostic.type === "error");
        for (const diagnostic of runtime.diagnostics) {
            const prefix = diagnostic.type === "warning" ? "Warning" : diagnostic.type === "error" ? "Error" : "Info";
            console.error(`${prefix}: ${diagnostic.message}`);
        }
        if (fatal) {
            process.exitCode = 1;
            return;
        }
        const interactiveMode = new InteractiveMode(runtime, {
            verbose: true,
            modelFallbackMessage: runtime.modelFallbackMessage,
        });
        await interactiveMode.run();
    }
    finally {
        await runtime.dispose();
    }
}
