import {} from "../agents/shared/agent.js";
import { getExternalSnapshotForSession } from "../agents/shared/agent-activity.js";
import { renderExternalMirrorEvent, renderExternalMirrorStatus, renderExternalApprovalRequest, trimLine, } from "../channels/shared/bot-rendering.js";
import { friendlyErrorText } from "../core/error-messages.js";
import {} from "../web/web-state.js";
import { isExternalSnapshotSuppressedByManagedAbort } from "./relay-runtime-helpers.js";
const CLI_ACTIVITY_ACTOR = {
    channel: "cli",
    label: "CLI",
};
const MANAGED_TURN_ACTIVITY_MATCH_MS = 10 * 60 * 1000;
export class RelayExternalActivityMonitor {
    options;
    mirror = null;
    running = false;
    ignoredTurns = new Set();
    constructor(options) {
        this.options = options;
    }
    snapshot() {
        return this.mirror ? { ...this.mirror } : null;
    }
    reset() {
        this.mirror = null;
    }
    task() {
        if (!this.mirror) {
            return null;
        }
        const startedAt = this.mirror.startedAt instanceof Date
            ? this.mirror.startedAt.toISOString()
            : this.mirror.startedAt ?? new Date().toISOString();
        const startedMs = new Date(startedAt).getTime();
        return {
            id: this.mirror.turnId ?? "cli",
            source: "cli",
            status: this.mirror.latestStatus?.includes("failed")
                ? "failed"
                : this.mirror.latestStatus?.includes("aborted")
                    ? "aborted"
                    : this.mirror.latestStatus?.includes("finished") || this.mirror.latestStatus?.includes("completed")
                        ? "completed"
                        : "running",
            threadId: this.mirror.threadId,
            startedAt,
            updatedAt: new Date().toISOString(),
            durationMs: Number.isFinite(startedMs) ? Math.max(0, Date.now() - startedMs) : 0,
            outputChars: 0,
            tools: [],
            detail: this.mirror.latestStatus ?? this.mirror.rolloutPath,
        };
    }
    async monitorSafe() {
        if (this.running) {
            return false;
        }
        this.running = true;
        try {
            return await this.monitor();
        }
        catch (error) {
            this.options.broadcastStatus(friendlyErrorText(error), "error");
            return false;
        }
        finally {
            this.running = false;
        }
    }
    async monitor() {
        const session = await this.options.getSession();
        const info = this.options.publicInfo(session);
        if (!info.capabilities.externalActivity || !info.threadId || session.isProcessing()) {
            return false;
        }
        const snapshot = getExternalSnapshotForSession(session, this.options.config, {
            afterLine: this.mirror?.threadId === info.threadId ? this.mirror.lastLine : Number.MAX_SAFE_INTEGER,
        }) ?? getExternalSnapshotForSession(session, this.options.config, {
            maxEvents: 0,
        });
        if (!snapshot) {
            return false;
        }
        if (!this.mirror || this.mirror.threadId !== snapshot.threadId || this.mirror.rolloutPath !== snapshot.sourcePath) {
            this.mirror = {
                threadId: snapshot.threadId,
                rolloutPath: snapshot.sourcePath,
                lastLine: snapshot.lineCount,
                turnId: snapshot.activity.turnId,
                startedAt: snapshot.activity.startedAt?.toISOString() ?? null,
            };
            if (snapshot.activity.active) {
                if (await this.shouldIgnoreExternalTurn(snapshot)) {
                    this.ignoredTurns.add(externalTurnKey(snapshot));
                    this.mirror.lastLine = Math.max(this.mirror.lastLine, snapshot.lineCount);
                    return true;
                }
                await this.startExternalTurn(snapshot, info);
                await this.handlePendingApprovals(snapshot, info, this.mirror);
            }
            return snapshot.activity.active;
        }
        const mirror = this.mirror;
        if (snapshot.activity.active) {
            if (mirror.turnId !== snapshot.activity.turnId) {
                mirror.turnId = snapshot.activity.turnId;
                mirror.startedAt = snapshot.activity.startedAt?.toISOString() ?? null;
                mirror.latestAgentLine = undefined;
                mirror.latestStatusAt = undefined;
                mirror.latestMirroredEventLine = undefined;
                if (await this.shouldIgnoreExternalTurn(snapshot)) {
                    this.ignoredTurns.add(externalTurnKey(snapshot));
                    mirror.lastLine = Math.max(mirror.lastLine, snapshot.lineCount);
                    return true;
                }
                await this.startExternalTurn(snapshot, info);
            }
            if (this.ignoredTurns.has(externalTurnKey(snapshot))) {
                mirror.lastLine = Math.max(mirror.lastLine, snapshot.lineCount);
                return true;
            }
            const mirrorMode = this.options.mirrorMode();
            const newEvents = snapshot.events.filter((event) => event.lineNumber > mirror.lastLine);
            this.broadcastExternalEvents(snapshot, newEvents, info, mirrorMode === "full");
            if (mirrorMode === "status" || mirrorMode === "full") {
                await this.appendExternalConversationMessages(snapshot, newEvents, mirror);
            }
            await this.handlePendingApprovals(snapshot, info, mirror);
            if (mirrorMode === "full") {
                await this.appendExternalEventMessages(snapshot, newEvents, mirror);
            }
            mirror.lastLine = Math.max(mirror.lastLine, snapshot.lineCount);
            mirror.latestStatus = externalStatusLine(snapshot, this.options.queueLength());
            if (mirrorMode === "status" || mirrorMode === "full") {
                await this.updateExternalStatusMessage(snapshot, mirror);
            }
            if (mirrorMode !== "off") {
                this.options.broadcastStatus(mirror.latestStatus, "info");
            }
            this.options.scheduleActiveSessionsBroadcast();
            return true;
        }
        const terminalEvent = [...snapshot.events].reverse().find((event) => event.kind === "task" && event.status && event.status !== "started");
        if (this.ignoredTurns.has(externalTurnKey(snapshot))) {
            if (terminalEvent && terminalEvent.lineNumber > mirror.lastLine) {
                this.ignoredTurns.delete(externalTurnKey(snapshot));
                this.options.scheduleActiveSessionsBroadcast();
                await this.options.drainQueue();
            }
            mirror.lastLine = Math.max(mirror.lastLine, snapshot.lineCount);
            return false;
        }
        if (terminalEvent && terminalEvent.lineNumber > mirror.lastLine) {
            const mirrorMode = this.options.mirrorMode();
            const finalAgent = snapshot.events.filter((event) => event.kind === "agent" && event.text).at(-1);
            const finalText = finalAgent?.text ?? snapshot.latestAgentMessage;
            const finalLine = finalAgent?.lineNumber ?? snapshot.lineCount;
            const shouldSkipFinalMirror = await this.shouldIgnoreExternalTurn(snapshot);
            if ((mirrorMode === "final" || mirrorMode === "full") && finalText && finalLine !== mirror.latestAgentLine && !shouldSkipFinalMirror) {
                this.options.chatStore.appendWithResult({
                    threadId: snapshot.threadId,
                    role: "agent",
                    text: finalText,
                    source: "cli",
                    correlationId: externalCorrelationId(snapshot),
                    turnId: terminalEvent.turnId ?? undefined,
                    key: externalMessageKey("final", snapshot, terminalEvent.lineNumber),
                });
                mirror.latestAgentLine = finalLine;
            }
            const externalStartedAt = mirror.startedAt ? new Date(mirror.startedAt) : snapshot.activity.startedAt;
            if (mirrorMode !== "off" && !shouldSkipFinalMirror) {
                this.options.broadcast({
                    type: "turn_complete",
                    id: terminalEvent.turnId ?? "cli",
                    at: terminalEvent.timestamp?.toISOString() ?? new Date().toISOString(),
                    correlationId: externalCorrelationId(snapshot),
                    ...externalRelayEventContext(snapshot, info),
                });
            }
            this.options.appendActivity({
                source: "cli",
                status: terminalEvent.status === "aborted" ? "aborted" : terminalEvent.status === "failed" ? "failed" : "completed",
                type: "cli_turn_finished",
                threadId: snapshot.threadId,
                workspace: info.workspace,
                agentId: info.agentId,
                actor: CLI_ACTIVITY_ACTOR,
                correlationId: externalCorrelationId(snapshot),
                prompt: snapshot.latestUserMessage ?? undefined,
                detail: `${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`,
                durationMs: durationFromDates(externalStartedAt, terminalEvent.timestamp),
            });
            if (externalStartedAt && terminalEvent.turnId) {
                await this.options.persistWorkspaceArtifactsForTurn(info.workspace, terminalEvent.turnId, externalStartedAt, {
                    source: "cli",
                    agentId: info.agentId,
                    threadId: snapshot.threadId,
                    workspace: info.workspace,
                    contextKey: `cli:${snapshot.threadId}`,
                    correlationId: externalCorrelationId(snapshot),
                    prompt: snapshot.latestUserMessage ?? undefined,
                    actor: CLI_ACTIVITY_ACTOR,
                    turnStartedAt: externalStartedAt.toISOString(),
                });
            }
            mirror.latestStatus = `${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`;
            if ((mirrorMode === "status" || mirrorMode === "full") && !shouldSkipFinalMirror) {
                await this.updateExternalStatusMessage(snapshot, mirror, mirror.latestStatus);
            }
            if (mirrorMode !== "off" && !shouldSkipFinalMirror) {
                this.options.broadcastStatus(`${snapshot.agentLabel} CLI task ${terminalEvent.status ?? "finished"}.`, terminalEvent.status === "failed" ? "error" : terminalEvent.status === "aborted" ? "warn" : "info");
            }
            await this.broadcastChatHistory();
            this.options.scheduleActiveSessionsBroadcast();
            await this.options.drainQueue();
        }
        mirror.lastLine = Math.max(mirror.lastLine, snapshot.lineCount);
        return false;
    }
    async startExternalTurn(snapshot, info) {
        const prompt = snapshot.latestUserMessage ?? `${snapshot.agentLabel} CLI task`;
        const mode = this.options.mirrorMode();
        let broadcastedChatHistory = false;
        if (mode === "final" || mode === "full") {
            this.options.chatStore.appendWithResult({
                threadId: snapshot.threadId,
                role: "system",
                text: `Working on ${trimLine(prompt, 500)}`,
                source: "cli",
                correlationId: externalCorrelationId(snapshot),
                turnId: snapshot.activity.turnId ?? undefined,
                timestamp: snapshot.activity.startedAt?.toISOString(),
                key: externalMessageKey("working", snapshot),
            });
            await this.broadcastChatHistory();
            broadcastedChatHistory = true;
        }
        if (!broadcastedChatHistory) {
            await this.broadcastChatHistory();
        }
        if ((mode === "status" || mode === "full") && this.mirror) {
            await this.updateExternalStatusMessage(snapshot, this.mirror);
        }
        this.options.appendActivity({
            source: "cli",
            status: "running",
            type: "cli_turn_started",
            threadId: snapshot.threadId,
            workspace: info.workspace,
            agentId: info.agentId,
            actor: CLI_ACTIVITY_ACTOR,
            correlationId: externalCorrelationId(snapshot),
            prompt,
            detail: `${snapshot.sourceLabel}: ${snapshot.sourcePath}`,
        });
    }
    async shouldIgnoreExternalTurn(snapshot) {
        if (this.ignoredTurns.has(externalTurnKey(snapshot))) {
            return true;
        }
        if (isExternalSnapshotSuppressedByManagedAbort(snapshot, this.options.activity(snapshot.threadId))) {
            return true;
        }
        const startedAtMs = snapshot.activity.startedAt?.getTime();
        if (!Number.isFinite(startedAtMs)) {
            return false;
        }
        const recentChannelUserMessage = (await this.options.chatHistory()).some((message) => {
            if (message.threadId !== snapshot.threadId || message.role !== "user" || message.source === "cli") {
                return false;
            }
            const messageAtMs = Date.parse(message.timestamp);
            return Number.isFinite(messageAtMs) && Math.abs(messageAtMs - startedAtMs) <= 45_000;
        });
        if (recentChannelUserMessage) {
            return true;
        }
        const prompt = snapshot.latestUserMessage;
        return this.options.activity(snapshot.threadId).some((event) => {
            if (event.source === "cli" || event.threadId !== snapshot.threadId || (event.type !== "prompt_started" && event.type !== "prompt_completed")) {
                return false;
            }
            const eventAtMs = Date.parse(event.timestamp);
            return Number.isFinite(eventAtMs) &&
                Math.abs(eventAtMs - startedAtMs) <= MANAGED_TURN_ACTIVITY_MATCH_MS &&
                promptsOverlap(prompt, event.prompt);
        });
    }
    broadcastExternalEvents(snapshot, events, info, broadcastTools) {
        for (const event of events) {
            if (event.kind === "tool" && event.status === "started") {
                if (broadcastTools) {
                    this.options.broadcast({
                        type: "tool_start",
                        id: snapshot.activity.turnId ?? "cli",
                        toolCallId: `cli-${event.lineNumber}`,
                        toolName: event.toolName ?? "tool",
                        correlationId: externalCorrelationId(snapshot),
                        ...externalRelayEventContext(snapshot, info),
                    });
                }
                this.options.appendActivity({
                    source: "cli",
                    status: "running",
                    type: "cli_tool_started",
                    threadId: snapshot.threadId,
                    workspace: info.workspace,
                    agentId: info.agentId,
                    actor: CLI_ACTIVITY_ACTOR,
                    correlationId: externalCorrelationId(snapshot),
                    detail: event.toolName ?? "tool",
                });
            }
            if (event.kind === "tool" && event.status === "finished") {
                if (broadcastTools) {
                    this.options.broadcast({
                        type: "tool_end",
                        id: snapshot.activity.turnId ?? "cli",
                        toolCallId: `cli-${event.lineNumber}`,
                        isError: false,
                        correlationId: externalCorrelationId(snapshot),
                        ...externalRelayEventContext(snapshot, info),
                    });
                }
                this.options.appendActivity({
                    source: "cli",
                    status: "completed",
                    type: "cli_tool_completed",
                    threadId: snapshot.threadId,
                    workspace: info.workspace,
                    agentId: info.agentId,
                    actor: CLI_ACTIVITY_ACTOR,
                    correlationId: externalCorrelationId(snapshot),
                    detail: event.toolName ?? "tool",
                });
            }
            if (event.kind === "tool" && event.status === "failed") {
                if (broadcastTools) {
                    this.options.broadcast({
                        type: "tool_end",
                        id: snapshot.activity.turnId ?? "cli",
                        toolCallId: `cli-${event.lineNumber}`,
                        isError: true,
                        correlationId: externalCorrelationId(snapshot),
                        ...externalRelayEventContext(snapshot, info),
                    });
                }
                this.options.appendActivity({
                    source: "cli",
                    status: "failed",
                    type: "cli_tool_failed",
                    threadId: snapshot.threadId,
                    workspace: info.workspace,
                    agentId: info.agentId,
                    actor: CLI_ACTIVITY_ACTOR,
                    detail: event.toolName ?? "tool",
                });
            }
        }
    }
    async appendExternalConversationMessages(snapshot, events, mirror) {
        let changed = false;
        for (const event of events) {
            if (event.lineNumber <= mirror.lastLine || (event.kind !== "user" && event.kind !== "agent") || !event.text?.trim()) {
                continue;
            }
            const stored = this.options.chatStore.appendWithResult({
                threadId: snapshot.threadId,
                role: event.kind === "user" ? "user" : "agent",
                text: event.text,
                source: "cli",
                correlationId: externalCorrelationId(snapshot),
                turnId: event.turnId ?? snapshot.activity.turnId ?? undefined,
                timestamp: event.timestamp?.toISOString(),
                key: externalConversationMessageKey(snapshot, event.lineNumber),
            });
            changed = changed || stored.inserted;
        }
        if (changed) {
            await this.broadcastChatHistory();
        }
    }
    async appendExternalEventMessages(snapshot, events, mirror) {
        let changed = false;
        for (const event of events) {
            if (event.lineNumber <= (mirror.latestMirroredEventLine ?? mirror.lastLine)) {
                continue;
            }
            const rendered = renderExternalMirrorEvent(event);
            if (!rendered) {
                continue;
            }
            const stored = this.options.chatStore.appendWithResult({
                threadId: snapshot.threadId,
                role: event.kind === "tool" ? "tool" : "system",
                text: rendered.plain,
                source: "cli",
                correlationId: externalCorrelationId(snapshot),
                turnId: event.turnId ?? snapshot.activity.turnId ?? undefined,
                timestamp: event.timestamp?.toISOString(),
                key: externalMessageKey("event", snapshot, event.lineNumber),
            });
            changed = changed || stored.inserted;
            mirror.latestMirroredEventLine = event.lineNumber;
        }
        if (changed) {
            await this.broadcastChatHistory();
        }
    }
    async updateExternalStatusMessage(snapshot, mirror, text) {
        const now = Date.now();
        const minInterval = this.options.mirrorMinUpdateMs();
        if (!text && mirror.latestStatusAt && now - mirror.latestStatusAt < minInterval) {
            return;
        }
        const stored = this.options.chatStore.appendWithResult({
            threadId: snapshot.threadId,
            role: "system",
            text: text ?? renderExternalMirrorStatus(snapshot, this.options.queueLength()).plain,
            source: "cli",
            correlationId: externalCorrelationId(snapshot),
            turnId: snapshot.activity.turnId ?? undefined,
            key: externalMessageKey(text ? "status-terminal" : "status", snapshot, snapshot.lineCount),
        });
        mirror.latestStatusAt = now;
        if (stored.inserted) {
            await this.broadcastChatHistory();
        }
    }
    async handlePendingApprovals(snapshot, info, mirror) {
        const approvals = snapshot.pendingApprovals ?? [];
        if (!approvals.length) {
            return;
        }
        if (!mirror) {
            return;
        }
        const seen = new Set(mirror.approvalRequestIds ?? []);
        let changed = false;
        for (const approval of approvals) {
            const rendered = renderExternalApprovalRequest(snapshot.agentLabel, approval);
            const result = this.options.chatStore.upsertByKey({
                threadId: snapshot.threadId,
                role: "system",
                text: rendered.plain,
                source: "cli",
                correlationId: externalCorrelationId(snapshot),
                turnId: approval.turnId ?? snapshot.activity.turnId ?? undefined,
                timestamp: approval.requestedAt?.toISOString(),
                key: externalMessageKey("approval", snapshot, approval.lineNumber),
                actions: approvalActions(approval),
            });
            changed = changed || result.inserted || result.updated;
            if (!seen.has(approval.id)) {
                this.options.appendActivity({
                    source: "cli",
                    status: "running",
                    type: "cli_action_required",
                    threadId: snapshot.threadId,
                    workspace: info.workspace,
                    agentId: info.agentId,
                    actor: CLI_ACTIVITY_ACTOR,
                    correlationId: externalCorrelationId(snapshot),
                    prompt: snapshot.latestUserMessage ?? undefined,
                    detail: `${approval.toolName}: ${approval.command}`,
                });
                seen.add(approval.id);
            }
        }
        mirror.approvalRequestIds = [...seen].slice(-50);
        if (changed) {
            await this.broadcastChatHistory();
        }
    }
    async broadcastChatHistory() {
        this.options.broadcast({ type: "chat_history", messages: await this.options.chatHistory() });
    }
}
function externalMessageKey(kind, snapshot, lineNumber) {
    return [
        "external",
        kind,
        snapshot.agentId,
        snapshot.threadId,
        snapshot.activity.turnId ?? "turn",
        lineNumber ?? "",
    ].join(":");
}
function externalRelayEventContext(snapshot, info) {
    return {
        contextKey: `cli:${snapshot.threadId}`,
        agentId: info.agentId,
        threadId: snapshot.threadId,
        workspace: info.workspace,
    };
}
function promptsOverlap(left, right) {
    const normalizedLeft = normalizePromptForManagedTurnMatch(left);
    const normalizedRight = normalizePromptForManagedTurnMatch(right);
    if (!normalizedLeft || !normalizedRight) {
        return true;
    }
    return normalizedLeft === normalizedRight || normalizedLeft.includes(normalizedRight) || normalizedRight.includes(normalizedLeft);
}
function normalizePromptForManagedTurnMatch(value) {
    return String(value ?? "").replace(/\s+/g, " ").trim().toLowerCase();
}
function externalConversationMessageKey(snapshot, lineNumber) {
    return `external:${snapshot.sourcePath}:${lineNumber}`;
}
function externalTurnKey(snapshot) {
    return [
        snapshot.agentId,
        snapshot.threadId,
        snapshot.activity.turnId ?? "turn",
    ].join(":");
}
function externalCorrelationId(snapshot) {
    return `cli:${snapshot.agentId}:${snapshot.activity.turnId ?? snapshot.threadId}`;
}
function externalStatusLine(snapshot, queueLength) {
    const approval = snapshot.pendingApprovals?.[0];
    if (approval) {
        return `${snapshot.agentLabel} action required · ${trimLine(approval.command, 120)} · ${queueLength} queued`;
    }
    const elapsed = snapshot.activity.startedAt
        ? formatDuration((Date.now() - snapshot.activity.startedAt.getTime()) / 1000)
        : "-";
    const tool = snapshot.latestToolName ?? "-";
    return `${snapshot.agentLabel} CLI running · ${elapsed} · tool ${tool} · ${queueLength} queued`;
}
function approvalActions(approval) {
    const actions = [
        { label: "Proceed", action: `approval:yes:${approval.id}`, style: "primary" },
    ];
    if (approval.prefixRule.length > 0) {
        actions.push({
            label: "Proceed and remember",
            action: `approval:persist:${approval.id}`,
            style: "secondary",
            title: `Remember ${approval.prefixRule.join(" ")}`,
        });
    }
    actions.push({ label: "Deny", action: `approval:no:${approval.id}`, style: "danger" });
    return actions;
}
function durationFromDates(start, end) {
    if (!start || !end) {
        return undefined;
    }
    return Math.max(0, end.getTime() - start.getTime());
}
function formatDuration(seconds) {
    if (!Number.isFinite(seconds) || seconds < 0) {
        return "-";
    }
    if (seconds < 60) {
        return `${Math.round(seconds)}s`;
    }
    const minutes = Math.floor(seconds / 60);
    const remainder = Math.round(seconds % 60);
    return `${minutes}m ${remainder}s`;
}
