import { randomUUID } from "node:crypto";
import { CODEX_AGENT_CAPABILITIES, agentLabel, } from "../../agents/shared/agent.js";
import { friendlyErrorText } from "../../core/error-messages.js";
import { displayMetaForPromptEnvelope, displayTextForPromptEnvelope, } from "../../state/prompt-store.js";
export class ChannelTurnService {
    options;
    constructor(options) {
        this.options = options;
    }
    async run(session, envelope) {
        const actor = envelope.activityActor;
        const source = sourceForActor(actor, this.options.source);
        await this.options.ensureActiveThread(session);
        const sync = session.syncFromAgentState({ reattach: true });
        if (sync.changed || sync.reattached) {
            this.options.updateSession(session);
        }
        const info = session.getInfo();
        if ((info.capabilities ?? CODEX_AGENT_CAPABILITIES).auth) {
            const auth = await this.options.checkAuth(info);
            if (!auth.authenticated) {
                throw new Error(`${agentLabel(info.agentId)} is not authenticated: ${auth.detail}`);
            }
        }
        const turnId = randomUUID().slice(0, 12);
        const correlationId = envelope.correlationId ?? turnId;
        const startedMs = Date.now();
        this.options.setCurrentTurn(turnId, startedMs, "");
        this.options.setCurrentProgress({
            id: turnId,
            source,
            status: "running",
            correlationId,
            prompt: envelope.description,
            agentId: info.agentId,
            agentLabel: info.agentLabel,
            threadId: info.threadId,
            workspace: info.workspace,
            startedAt: new Date(startedMs).toISOString(),
            updatedAt: new Date(startedMs).toISOString(),
            durationMs: 0,
            outputChars: 0,
            tools: [],
        });
        this.options.setLastPrompt(envelope);
        const startedDate = new Date();
        const startedAt = startedDate.toISOString();
        const displayText = displayTextForPromptEnvelope(envelope);
        const displayMeta = displayMetaForPromptEnvelope(envelope);
        const eventContext = this.eventContext(info);
        const userMessage = this.options.chatStore.append({
            threadId: info.threadId ?? "pending",
            role: "user",
            text: displayText,
            meta: displayMeta,
            attachments: envelope.attachments,
            source,
            correlationId,
            turnId,
            timestamp: startedAt,
        });
        this.options.appendActivity({
            source,
            status: "running",
            type: "prompt_started",
            threadId: info.threadId,
            workspace: info.workspace,
            agentId: info.agentId,
            actor,
            correlationId,
            prompt: envelope.description,
        });
        this.options.appendAudit({
            action: "prompt_started",
            status: "ok",
            contextKey: this.options.contextKey,
            agentId: info.agentId,
            threadId: info.threadId,
            workspace: info.workspace,
            actor,
            correlationId,
            description: envelope.description,
        });
        this.options.broadcast({
            type: "turn_start",
            id: turnId,
            messageId: userMessage.id,
            prompt: envelope.description,
            text: displayText,
            meta: displayMeta,
            attachments: envelope.attachments,
            at: startedAt,
            source,
            correlationId,
            ...eventContext,
        });
        void this.options.chatHistory().then((messages) => this.options.broadcast({ type: "chat_history", messages })).catch(() => { });
        try {
            await session.prompt(envelope.input, this.callbacks(turnId, info, envelope, actor, source));
            this.options.updateSession(session);
            await this.options.artifactService.persistWorkspaceArtifactsForTurn(info.workspace, turnId, startedDate, {
                source,
                agentId: info.agentId,
                threadId: info.threadId,
                workspace: info.workspace,
                contextKey: this.options.contextKey,
                correlationId,
                prompt: envelope.description,
                actor,
                turnStartedAt: startedAt,
            });
            const text = this.options.getAccumulatedText();
            if (text.trim()) {
                this.options.chatStore.append({
                    threadId: info.threadId ?? "pending",
                    role: "agent",
                    text,
                    source,
                    correlationId,
                    turnId,
                });
            }
            this.options.appendActivity({
                source,
                status: "completed",
                type: "prompt_completed",
                threadId: info.threadId,
                workspace: info.workspace,
                agentId: info.agentId,
                actor,
                correlationId,
                prompt: envelope.description,
                durationMs: Date.now() - this.options.getCurrentTurnStartedAt(),
            });
            this.options.appendAudit({
                action: "prompt_completed",
                status: "ok",
                contextKey: this.options.contextKey,
                agentId: info.agentId,
                threadId: info.threadId,
                workspace: info.workspace,
                actor,
                correlationId,
                description: envelope.description,
            });
            this.updateCurrentProgress({ status: "completed" });
            this.options.broadcast({ type: "turn_complete", id: turnId, at: new Date().toISOString(), correlationId, ...eventContext });
            this.options.broadcast({ type: "chat_history", messages: await this.options.chatHistory() });
        }
        catch (error) {
            const errorText = friendlyErrorText(error);
            this.options.chatStore.append({
                threadId: info.threadId ?? "pending",
                role: "system",
                text: `Error: ${errorText}`,
                source,
                correlationId,
                turnId,
            });
            this.options.appendActivity({
                source,
                status: "failed",
                type: "prompt_failed",
                threadId: info.threadId,
                workspace: info.workspace,
                agentId: info.agentId,
                actor,
                correlationId,
                prompt: envelope.description,
                detail: errorText,
                durationMs: Date.now() - this.options.getCurrentTurnStartedAt(),
            });
            this.options.appendAudit({
                action: "prompt_failed",
                status: "failed",
                contextKey: this.options.contextKey,
                agentId: info.agentId,
                threadId: info.threadId,
                workspace: info.workspace,
                actor,
                correlationId,
                description: envelope.description,
                detail: errorText,
            });
            this.updateCurrentProgress({ status: "failed", detail: errorText });
            this.options.broadcast({ type: "turn_error", id: turnId, error: errorText, at: new Date().toISOString(), correlationId, ...eventContext });
            this.options.broadcast({ type: "chat_history", messages: await this.options.chatHistory() });
            throw error;
        }
        finally {
            const progress = this.options.getCurrentProgress();
            if (progress) {
                progress.durationMs = Date.now() - this.options.getCurrentTurnStartedAt();
                progress.updatedAt = new Date().toISOString();
            }
            this.options.setCurrentTurn(null);
        }
    }
    callbacks(turnId, info, envelope, actor, source) {
        const correlationId = envelope.correlationId ?? turnId;
        const eventContext = this.eventContext(info);
        return {
            onTextDelta: (delta) => {
                const nextText = this.options.getAccumulatedText() + delta;
                this.options.setAccumulatedText(nextText);
                this.updateCurrentProgress({ outputChars: nextText.length });
                this.options.broadcast({ type: "text_delta", id: turnId, delta, correlationId, ...eventContext });
            },
            onAssistantMessageComplete: () => {
                this.options.broadcast({ type: "assistant_message_complete", id: turnId, at: new Date().toISOString(), correlationId, ...eventContext });
            },
            onToolStart: (toolName, toolCallId) => {
                this.addCurrentTool(toolName);
                this.options.appendActivity({
                    source,
                    status: "running",
                    type: "tool_started",
                    threadId: info.threadId,
                    workspace: info.workspace,
                    agentId: info.agentId,
                    actor,
                    correlationId,
                    prompt: envelope.description,
                    detail: toolName,
                });
                this.options.broadcast({ type: "tool_start", id: turnId, toolCallId, toolName, correlationId, ...eventContext });
            },
            onToolUpdate: (toolCallId, partialResult) => {
                this.updateCurrentProgress();
                this.options.broadcast({ type: "tool_update", id: turnId, toolCallId, partialResult, correlationId, ...eventContext });
            },
            onToolEnd: (toolCallId, isError) => {
                const progress = this.options.getCurrentProgress();
                const toolName = progress?.currentTool ?? progress?.lastTool ?? toolCallId;
                this.updateCurrentProgress({ currentTool: undefined });
                this.options.appendActivity({
                    source,
                    status: isError ? "failed" : "completed",
                    type: isError ? "tool_failed" : "tool_completed",
                    threadId: info.threadId,
                    workspace: info.workspace,
                    agentId: info.agentId,
                    actor,
                    correlationId,
                    prompt: envelope.description,
                    detail: toolName,
                });
                this.options.broadcast({ type: "tool_end", id: turnId, toolCallId, isError, correlationId, ...eventContext });
            },
            onTodoUpdate: (items) => {
                this.updateCurrentProgress({ detail: `Plan: ${items.filter((item) => item.completed).length}/${items.length} done` });
                this.options.broadcast({ type: "todo_update", id: turnId, items, correlationId, ...eventContext });
            },
            onTurnComplete: () => { },
            onAgentEnd: () => this.options.broadcast({ type: "turn_complete", id: turnId, at: new Date().toISOString(), correlationId, ...eventContext }),
        };
    }
    eventContext(info) {
        return {
            contextKey: this.options.contextKey,
            agentId: info.agentId,
            threadId: info.threadId,
            workspace: info.workspace,
        };
    }
    updateCurrentProgress(patch = {}) {
        const progress = this.options.getCurrentProgress();
        if (!progress) {
            return;
        }
        if ("currentTool" in patch) {
            progress.currentTool = patch.currentTool;
            const { currentTool: _currentTool, ...rest } = patch;
            Object.assign(progress, rest);
        }
        else {
            Object.assign(progress, patch);
        }
        progress.durationMs = Date.now() - this.options.getCurrentTurnStartedAt();
        progress.updatedAt = new Date().toISOString();
        this.options.setCurrentProgress(progress);
    }
    addCurrentTool(toolName) {
        const progress = this.options.getCurrentProgress();
        if (!progress) {
            return;
        }
        const existing = progress.tools.find((tool) => tool.name === toolName);
        if (existing) {
            existing.count += 1;
        }
        else {
            progress.tools.push({ name: toolName, count: 1 });
        }
        this.updateCurrentProgress({ currentTool: toolName, lastTool: toolName });
    }
}
function sourceForActor(actor, fallback) {
    const channel = actor?.channel;
    return channel === "web" ||
        channel === "telegram" ||
        channel === "discord" ||
        channel === "slack" ||
        channel === "matrix" ||
        channel === "cli"
        ? channel
        : fallback;
}
