import { ConversationTurnRuntime } from "../runtime/conversation_turn_runtime.js";
import { createDefaultCoreDeps } from "../runtime/deps.js";
import { resolveRuntimePromptBootstrap } from "../runtime/runtime_bootstrap.js";
import { composeSessionPrompts } from "../runtime/session_prompt_composer.js";
import { CoreSession } from "../session/core_session.js";
import { renderDiffReviewWrapperPrompt } from "../static/index.js";
import { ToolCatalog } from "../tools/catalog.js";
import { createLocalToolExecutionBackend, } from "../tools/execution_backend.js";
import { TOOL_NAME_BASH, TOOL_NAME_VIEW_IMAGE } from "../tools/tool_names.js";
import { appendUsageLogEntry, getUsageCostTotal, getUsageTotals } from "../usage/logs.js";
import { extractAssistantText } from "../utils/messages.js";
import { extractAssistantTextForProgress, formatToolUiEventForProgress, getToolResultFirstLine, } from "../utils/subagent_utils.js";
export class DiffReviewThread {
    session;
    runtime;
    personaId;
    reasoningEffort;
    onUpdate;
    costTotal = 0;
    usage;
    lastActivityText;
    constructor(options) {
        const deps = options.deps ?? createDefaultCoreDeps();
        const backend = options.toolExecutionBackend ??
            createLocalToolExecutionBackend({
                spawn: deps.spawn,
                env: deps.env,
            });
        this.personaId = options.persona.id;
        this.reasoningEffort = options.persona.settings.reasoning ?? "none";
        this.onUpdate = options.onUpdate;
        this.usage = {
            input: 0,
            output: 0,
            cacheRead: 0,
            cacheWrite: 0,
            contextWindowUsageTokens: 0,
            contextWindow: options.persona.model.contextWindow,
        };
        const persona = createDiffReviewPersona(options.persona, options.snapshot);
        const promptBootstrap = resolveRuntimePromptBootstrap({
            persona,
            discoveredSkills: options.discoveredSkills ?? [],
            cwd: options.snapshot.cwd,
            home: deps.env.home(),
            includeAgentContext: options.includeAgentContext ?? true,
            readFile: deps.fs.readFile,
        });
        const promptComposition = composeSessionPrompts({
            persona,
            riskLevel: "read-only",
            cwd: promptBootstrap.promptContext.cwd,
            datetime: new Date(deps.clock.now()).toISOString(),
            platform: deps.env.platform(),
            nodeVersion: deps.env.nodeVersion(),
            skillsBlock: promptBootstrap.promptContext.skillsBlock,
            projectContextBlock: promptBootstrap.promptContext.projectContextBlock,
        });
        const toolRegistry = ToolCatalog.createSubagentRegistry([TOOL_NAME_BASH, TOOL_NAME_VIEW_IMAGE], options.config, backend);
        this.session = new CoreSession({
            persona,
            systemPrompt: promptComposition.baseSystemPrompt,
            subagentPrompts: promptComposition.subagentPrompts,
            riskLevel: "read-only",
            toolRegistry,
            config: options.config,
            deps,
            cwd: promptBootstrap.promptContext.cwd,
            home: promptBootstrap.promptContext.home,
            includeAgentContext: promptBootstrap.promptContext.includeAgentContext,
        });
        if (options.forkFrom) {
            this.usage = { ...options.forkFrom.usageBaseline };
            for (const entry of options.forkFrom.historyEntries) {
                this.session.addMessage(entry.message, {
                    historyEntryId: entry.id,
                });
            }
            this.emitUpdate();
        }
        this.runtime = new ConversationTurnRuntime(this.session);
    }
    async submitMessage(message) {
        this.session.addUserText(message);
        const result = await this.runtime.run({
            onEvent: (event) => this.handleEvent(event),
        });
        if (result.aborted) {
            throw new Error("diff review thread was interrupted");
        }
        if (result.blocked) {
            throw new Error(result.blocked.message);
        }
        const assistantMessage = findLastAssistantMessage(this.session);
        if (!assistantMessage) {
            throw new Error("diff review thread produced no assistant response");
        }
        const response = extractAssistantText(assistantMessage).trim();
        if (!response) {
            throw new Error("diff review thread produced an empty assistant response");
        }
        appendUsageLogEntry({
            timestamp: assistantMessage.timestamp,
            sessionId: this.session.sessionId,
            personaId: this.personaId,
            provider: assistantMessage.provider,
            model: assistantMessage.model,
            api: assistantMessage.api,
            reasoningEffort: this.reasoningEffort,
            usage: getUsageTotals(assistantMessage.usage),
            cost: { total: getUsageCostTotal(assistantMessage.usage) },
            agent: { type: "review" },
        });
        return response;
    }
    interrupt() {
        return this.runtime.interrupt();
    }
    dispose() {
        this.session.dispose();
    }
    createForkSource() {
        return {
            historyEntries: this.session.rawHistoryEntries.map((entry) => ({
                id: entry.id,
                message: structuredClone(entry.message),
            })),
            usageBaseline: {
                input: 0,
                output: 0,
                cacheRead: 0,
                cacheWrite: 0,
                contextWindowUsageTokens: this.usage.contextWindowUsageTokens,
                contextWindow: this.usage.contextWindow,
            },
        };
    }
    handleEvent(event) {
        switch (event.type) {
            case "tool_ui": {
                const progressText = formatToolUiEventForProgress(event.uiEvent);
                if (progressText) {
                    this.lastActivityText = progressText;
                    this.emitUpdate();
                }
                return;
            }
            case "tool_result": {
                if (!event.message.isError) {
                    return;
                }
                const firstLine = getToolResultFirstLine(event.message);
                this.lastActivityText = firstLine
                    ? `${event.message.toolName}: ${firstLine}`
                    : `${event.message.toolName}: tool returned an error`;
                this.emitUpdate();
                return;
            }
            case "assistant_final": {
                const usageTotals = getUsageTotals(event.message.usage);
                this.costTotal += getUsageCostTotal(event.message.usage);
                this.usage = {
                    ...this.usage,
                    input: this.usage.input + usageTotals.input,
                    output: this.usage.output + usageTotals.output,
                    cacheRead: this.usage.cacheRead + usageTotals.cacheRead,
                    cacheWrite: this.usage.cacheWrite + usageTotals.cacheWrite,
                    contextWindowUsageTokens: usageTotals.input + usageTotals.output + usageTotals.cacheRead + usageTotals.cacheWrite,
                };
                this.lastActivityText =
                    extractAssistantTextForProgress(event.message) ?? this.lastActivityText;
                this.emitUpdate();
                return;
            }
            default:
                return;
        }
    }
    emitUpdate() {
        this.onUpdate?.({
            costTotal: this.costTotal,
            usage: { ...this.usage },
            ...(this.lastActivityText ? { lastActivityText: this.lastActivityText } : {}),
        });
    }
}
export function buildDiffReviewSystemPrompt(mainPersonaSystemPrompt, snapshot) {
    return renderDiffReviewWrapperPrompt({
        inheritedInstructions: mainPersonaSystemPrompt,
        reviewContext: buildReviewContextBlock(snapshot),
    });
}
function createDiffReviewPersona(persona, snapshot) {
    return {
        ...persona,
        id: `${persona.id}-diff-review`,
        label: `${persona.label} diff review`,
        description: "Diff review assistant",
        systemPrompt: buildDiffReviewSystemPrompt(persona.systemPrompt, snapshot),
        subagents: undefined,
        skills: persona.skills,
        tools: [TOOL_NAME_BASH, TOOL_NAME_VIEW_IMAGE],
    };
}
function buildReviewContextBlock(snapshot) {
    const changedFiles = snapshot.files.length > 0
        ? snapshot.files
            .map((file) => {
            const path = file.oldPath && file.newPath && file.oldPath !== file.newPath
                ? `${file.oldPath} -> ${file.newPath}`
                : file.path;
            return `- ${path} (${file.status})`;
        })
            .join("\n")
        : "- (no changed files)";
    return [
        `Repo root: ${snapshot.repoRoot}`,
        `Cwd: ${snapshot.cwd}`,
        `Initial review scope: ${snapshot.toDiffCommand()}`,
        "",
        "Files in review scope:",
        changedFiles,
        "",
        "This review context is the exact change selection captured when /diff opened. It may be narrower than the full set of current repo changes.",
        "Treat this scoped patch as the default review target.",
        "If answering well requires code outside this scope, inspect it as needed, but use it as supporting context unless the user broadens the review target.",
        "The current repo state is authoritative. Use read-only tools to inspect relevant repo context when needed.",
    ].join("\n");
}
function findLastAssistantMessage(session) {
    for (let index = session.historyEntries.length - 1; index >= 0; index -= 1) {
        const entry = session.historyEntries[index];
        if (entry?.message.role === "assistant") {
            return entry.message;
        }
    }
    return undefined;
}
//# sourceMappingURL=review_thread.js.map