import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { readFile, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { completeSimple } from "@mariozechner/pi-ai";
import { buildSessionContext, convertToLlm, serializeConversation, } from "@mariozechner/pi-coding-agent";
const PROJECT_ROOT = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
export const PIBO_LIBRARY_COMPACTION_PROMPT_PATH = resolve(PROJECT_ROOT, "context/pibo-compaction-prompt.md");
function getCompactionPromptStatePath(cwd) {
    return resolve(cwd, ".pibo/compaction-prompt.json");
}
function getCustomCompactionPromptPath(cwd) {
    return resolve(cwd, ".pibo/compaction-prompt.md");
}
function normalizeMode(value) {
    return value === "custom" ? "custom" : "library";
}
function readCompactionPromptState(cwd) {
    const path = getCompactionPromptStatePath(cwd);
    if (!existsSync(path))
        return { mode: "library" };
    try {
        const parsed = JSON.parse(readFileSync(path, "utf-8"));
        return {
            mode: normalizeMode(parsed.mode),
            updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : undefined,
        };
    }
    catch {
        return { mode: "library" };
    }
}
function writeCompactionPromptState(cwd, state) {
    const path = getCompactionPromptStatePath(cwd);
    mkdirSync(dirname(path), { recursive: true });
    writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`);
}
function extractPromptSection(markdown, tag) {
    const pattern = new RegExp(`<${tag}>\\s*([\\s\\S]*?)\\s*</${tag}>`, "i");
    const match = markdown.match(pattern);
    if (!match)
        throw new Error(`Compaction prompt is missing <${tag}> section`);
    return match[1].trim();
}
export function parsePiboCompactionPrompt(markdown) {
    return {
        systemPrompt: extractPromptSection(markdown, "system-prompt"),
        summaryPrompt: extractPromptSection(markdown, "summary-prompt"),
        updateSummaryPrompt: extractPromptSection(markdown, "update-summary-prompt"),
        turnPrefixSummaryPrompt: extractPromptSection(markdown, "turn-prefix-summary-prompt"),
    };
}
export function getActivePiboCompactionPromptPath(cwd = process.cwd()) {
    const customPath = getCustomCompactionPromptPath(cwd);
    const state = readCompactionPromptState(cwd);
    if (state.mode === "custom" && existsSync(customPath))
        return customPath;
    return PIBO_LIBRARY_COMPACTION_PROMPT_PATH;
}
export async function readActivePiboCompactionPromptSpec(cwd = process.cwd()) {
    return parsePiboCompactionPrompt(await readFile(getActivePiboCompactionPromptPath(cwd), "utf-8"));
}
export async function readPiboCompactionPrompt(cwd = process.cwd()) {
    const state = readCompactionPromptState(cwd);
    const customPath = getCustomCompactionPromptPath(cwd);
    const customExists = existsSync(customPath);
    const [libraryMarkdown, customMarkdown] = await Promise.all([
        readFile(PIBO_LIBRARY_COMPACTION_PROMPT_PATH, "utf-8"),
        customExists ? readFile(customPath, "utf-8") : Promise.resolve(""),
    ]);
    return {
        mode: state.mode,
        effectiveMode: state.mode === "custom" && customExists ? "custom" : "library",
        library: {
            path: PIBO_LIBRARY_COMPACTION_PROMPT_PATH,
            markdown: libraryMarkdown,
        },
        custom: {
            path: customPath,
            markdown: customMarkdown,
            exists: customExists,
            updatedAt: customExists ? state.updatedAt : undefined,
        },
    };
}
export async function savePiboCustomCompactionPrompt(markdown, cwd = process.cwd()) {
    parsePiboCompactionPrompt(markdown);
    const path = getCustomCompactionPromptPath(cwd);
    mkdirSync(dirname(path), { recursive: true });
    const updatedAt = new Date().toISOString();
    await writeFile(path, markdown);
    writeCompactionPromptState(cwd, { mode: "custom", updatedAt });
    return readPiboCompactionPrompt(cwd);
}
export function setPiboCompactionPromptMode(mode, cwd = process.cwd()) {
    const existing = readCompactionPromptState(cwd);
    const customPath = getCustomCompactionPromptPath(cwd);
    if (mode === "custom" && !existsSync(customPath)) {
        mkdirSync(dirname(customPath), { recursive: true });
        writeFileSync(customPath, readFileSync(PIBO_LIBRARY_COMPACTION_PROMPT_PATH, "utf-8"));
    }
    writeCompactionPromptState(cwd, {
        mode,
        updatedAt: mode === "custom" ? existing.updatedAt ?? new Date().toISOString() : existing.updatedAt,
    });
    const state = readCompactionPromptState(cwd);
    const customExists = existsSync(customPath);
    return {
        mode: state.mode,
        effectiveMode: state.mode === "custom" && customExists ? "custom" : "library",
        library: {
            path: PIBO_LIBRARY_COMPACTION_PROMPT_PATH,
            markdown: readFileSync(PIBO_LIBRARY_COMPACTION_PROMPT_PATH, "utf-8"),
        },
        custom: {
            path: customPath,
            markdown: customExists ? readFileSync(customPath, "utf-8") : "",
            exists: customExists,
            updatedAt: customExists ? state.updatedAt : undefined,
        },
    };
}
function buildSummaryPrompt(spec, messages, customInstructions, previousSummary) {
    const conversationText = serializeConversation(convertToLlm(messages));
    const basePrompt = previousSummary ? spec.updateSummaryPrompt : spec.summaryPrompt;
    const focusedPrompt = customInstructions ? `${basePrompt}\n\nAdditional focus: ${customInstructions}` : basePrompt;
    return [
        `<conversation>\n${conversationText}\n</conversation>`,
        previousSummary ? `<previous-summary>\n${previousSummary}\n</previous-summary>` : undefined,
        focusedPrompt,
    ].filter((part) => part !== undefined).join("\n\n");
}
function buildTurnPrefixPrompt(spec, messages) {
    const conversationText = serializeConversation(convertToLlm(messages));
    return `<conversation>\n${conversationText}\n</conversation>\n\n${spec.turnPrefixSummaryPrompt}`;
}
async function completeSummary(input) {
    const completionOptions = input.model.reasoning && input.thinkingLevel && input.thinkingLevel !== "off"
        ? {
            maxTokens: input.maxTokens,
            signal: input.signal,
            apiKey: input.apiKey,
            headers: input.headers,
            reasoning: input.thinkingLevel,
        }
        : {
            maxTokens: input.maxTokens,
            signal: input.signal,
            apiKey: input.apiKey,
            headers: input.headers,
        };
    const response = await completeSimple(input.model, {
        systemPrompt: input.systemPrompt,
        messages: [
            {
                role: "user",
                content: [{ type: "text", text: input.promptText }],
                timestamp: Date.now(),
            },
        ],
    }, completionOptions);
    if (response.stopReason === "error") {
        throw new Error(`Summarization failed: ${response.errorMessage || "Unknown error"}`);
    }
    return response.content
        .filter((content) => content.type === "text")
        .map((content) => content.text)
        .join("\n");
}
function computeFileLists(fileOps) {
    const modified = new Set([...fileOps.written, ...fileOps.edited]);
    const readFiles = [...fileOps.read].filter((path) => !modified.has(path)).sort();
    const modifiedFiles = [...modified].sort();
    return { readFiles, modifiedFiles };
}
function formatFileOperations(readFiles, modifiedFiles) {
    const sections = [];
    if (readFiles.length > 0)
        sections.push(`<read-files>\n${readFiles.join("\n")}\n</read-files>`);
    if (modifiedFiles.length > 0)
        sections.push(`<modified-files>\n${modifiedFiles.join("\n")}\n</modified-files>`);
    return sections.length > 0 ? `\n\n${sections.join("\n\n")}` : "";
}
async function generatePiboCompaction(input) {
    const { preparation, spec } = input;
    let summary;
    if (preparation.isSplitTurn && preparation.turnPrefixMessages.length > 0) {
        const [historySummary, turnPrefixSummary] = await Promise.all([
            preparation.messagesToSummarize.length > 0
                ? completeSummary({
                    model: input.model,
                    systemPrompt: spec.systemPrompt,
                    promptText: buildSummaryPrompt(spec, preparation.messagesToSummarize, input.customInstructions, preparation.previousSummary),
                    maxTokens: Math.floor(0.8 * preparation.settings.reserveTokens),
                    apiKey: input.apiKey,
                    headers: input.headers,
                    signal: input.signal,
                    thinkingLevel: input.thinkingLevel,
                })
                : Promise.resolve("No prior history."),
            completeSummary({
                model: input.model,
                systemPrompt: spec.systemPrompt,
                promptText: buildTurnPrefixPrompt(spec, preparation.turnPrefixMessages),
                maxTokens: Math.floor(0.5 * preparation.settings.reserveTokens),
                apiKey: input.apiKey,
                headers: input.headers,
                signal: input.signal,
                thinkingLevel: input.thinkingLevel,
            }),
        ]);
        summary = `${historySummary}\n\n---\n\n**Turn Context (split turn):**\n\n${turnPrefixSummary}`;
    }
    else {
        summary = await completeSummary({
            model: input.model,
            systemPrompt: spec.systemPrompt,
            promptText: buildSummaryPrompt(spec, preparation.messagesToSummarize, input.customInstructions, preparation.previousSummary),
            maxTokens: Math.floor(0.8 * preparation.settings.reserveTokens),
            apiKey: input.apiKey,
            headers: input.headers,
            signal: input.signal,
            thinkingLevel: input.thinkingLevel,
        });
    }
    const { readFiles, modifiedFiles } = computeFileLists(preparation.fileOps);
    summary += formatFileOperations(readFiles, modifiedFiles);
    return {
        summary,
        firstKeptEntryId: preparation.firstKeptEntryId,
        tokensBefore: preparation.tokensBefore,
        details: { readFiles, modifiedFiles },
    };
}
export function createPiboCompactionPromptExtension() {
    return (pi) => {
        pi.on("session_before_compact", async (event, ctx) => {
            if (!ctx.model)
                return undefined;
            const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model);
            if (!auth.ok || !auth.apiKey)
                return undefined;
            const sessionContext = buildSessionContext(ctx.sessionManager.getEntries(), ctx.sessionManager.getLeafId());
            return {
                compaction: await generatePiboCompaction({
                    preparation: event.preparation,
                    spec: await readActivePiboCompactionPromptSpec(ctx.cwd),
                    model: ctx.model,
                    apiKey: auth.apiKey,
                    headers: auth.headers,
                    customInstructions: event.customInstructions,
                    signal: event.signal,
                    thinkingLevel: sessionContext.thinkingLevel,
                }),
            };
        });
    };
}
