import { SessionManager } from "@mariozechner/pi-coding-agent";
function errorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
function messageContentIndex(candidate) {
    return typeof candidate.assistantMessageEvent?.contentIndex === "number"
        ? candidate.assistantMessageEvent.contentIndex
        : undefined;
}
function promptSource(source) {
    return source === "user" || source === "ui" ? "interactive" : "rpc";
}
function lastTextPartFromMessage(message) {
    if (!message || typeof message !== "object")
        return undefined;
    const content = message.content;
    if (!Array.isArray(content))
        return undefined;
    for (let index = content.length - 1; index >= 0; index -= 1) {
        const part = content[index];
        if (!part || typeof part !== "object")
            continue;
        const candidate = part;
        if (candidate.type === "text" && typeof candidate.text === "string" && candidate.text.length > 0) {
            return { text: candidate.text, contentIndex: index };
        }
    }
    return undefined;
}
function toolCallFromMessage(message, contentIndex) {
    if (!message || typeof message !== "object" || typeof contentIndex !== "number")
        return undefined;
    const content = message.content;
    if (!Array.isArray(content))
        return undefined;
    const candidate = content[contentIndex];
    if (!candidate || typeof candidate !== "object")
        return undefined;
    const toolCall = candidate;
    if (toolCall.type !== "toolCall" || typeof toolCall.id !== "string" || typeof toolCall.name !== "string") {
        return undefined;
    }
    return { id: toolCall.id, name: toolCall.name, args: toolCall.arguments ?? {} };
}
function toolCallFromAssistantEvent(candidate) {
    const eventToolCall = candidate.assistantMessageEvent?.toolCall;
    if (eventToolCall && typeof eventToolCall.id === "string" && typeof eventToolCall.name === "string") {
        return { id: eventToolCall.id, name: eventToolCall.name, args: eventToolCall.arguments ?? {} };
    }
    return toolCallFromMessage(candidate.message, candidate.assistantMessageEvent?.contentIndex);
}
function normalizeToolCallEvent(piboSessionId, candidate) {
    if (candidate.type === "message_update" &&
        (candidate.assistantMessageEvent?.type === "toolcall_start" ||
            candidate.assistantMessageEvent?.type === "toolcall_end")) {
        const toolCall = toolCallFromAssistantEvent(candidate);
        if (!toolCall)
            return undefined;
        return {
            type: "tool_call",
            piboSessionId,
            toolCallId: toolCall.id,
            toolName: toolCall.name,
            args: toolCall.args,
            argsComplete: candidate.assistantMessageEvent.type === "toolcall_end",
        };
    }
    return undefined;
}
function normalizeToolExecutionEvent(piboSessionId, candidate) {
    if (typeof candidate.toolCallId !== "string" || typeof candidate.toolName !== "string") {
        return undefined;
    }
    if (candidate.type === "tool_execution_start") {
        return {
            type: "tool_execution_started",
            piboSessionId,
            toolCallId: candidate.toolCallId,
            toolName: candidate.toolName,
            args: candidate.args,
        };
    }
    if (candidate.type === "tool_execution_update") {
        return {
            type: "tool_execution_updated",
            piboSessionId,
            toolCallId: candidate.toolCallId,
            toolName: candidate.toolName,
            args: candidate.args,
            partialResult: candidate.partialResult,
        };
    }
    if (candidate.type === "tool_execution_end") {
        return {
            type: "tool_execution_finished",
            piboSessionId,
            toolCallId: candidate.toolCallId,
            toolName: candidate.toolName,
            result: candidate.result,
            isError: candidate.isError === true,
        };
    }
    return undefined;
}
function normalizePiEvent(piboSessionId, event) {
    if (!event || typeof event !== "object")
        return undefined;
    const candidate = event;
    if (candidate.type === "message_update" &&
        candidate.assistantMessageEvent?.type === "text_delta" &&
        typeof candidate.assistantMessageEvent.delta === "string") {
        return {
            type: "assistant_delta",
            piboSessionId,
            contentIndex: messageContentIndex(candidate),
            text: candidate.assistantMessageEvent.delta,
        };
    }
    if (candidate.type === "message_update" &&
        candidate.assistantMessageEvent?.type === "thinking_start") {
        return { type: "thinking_started", piboSessionId, contentIndex: messageContentIndex(candidate) };
    }
    if (candidate.type === "message_update" &&
        candidate.assistantMessageEvent?.type === "thinking_delta" &&
        typeof candidate.assistantMessageEvent.delta === "string") {
        return { type: "thinking_delta", piboSessionId, contentIndex: messageContentIndex(candidate), text: candidate.assistantMessageEvent.delta };
    }
    if (candidate.type === "message_update" && candidate.assistantMessageEvent?.type === "thinking_end") {
        const text = typeof candidate.assistantMessageEvent.content === "string" ? candidate.assistantMessageEvent.content : undefined;
        return text === undefined
            ? { type: "thinking_finished", piboSessionId, contentIndex: messageContentIndex(candidate) }
            : { type: "thinking_finished", piboSessionId, contentIndex: messageContentIndex(candidate), text };
    }
    const toolCallEvent = normalizeToolCallEvent(piboSessionId, candidate);
    if (toolCallEvent)
        return toolCallEvent;
    const toolExecutionEvent = normalizeToolExecutionEvent(piboSessionId, candidate);
    if (toolExecutionEvent)
        return toolExecutionEvent;
    if (candidate.type === "message_end") {
        const message = candidate.message;
        const role = message?.role;
        if (role === "assistant") {
            if (message?.stopReason === "error" || typeof message?.errorMessage === "string") {
                return {
                    type: "session_error",
                    piboSessionId,
                    error: typeof message.errorMessage === "string" && message.errorMessage.length > 0
                        ? message.errorMessage
                        : "Assistant message failed.",
                };
            }
            const textPart = lastTextPartFromMessage(candidate.message);
            if (textPart) {
                return {
                    type: "assistant_message",
                    piboSessionId,
                    contentIndex: textPart.contentIndex,
                    text: textPart.text,
                };
            }
        }
    }
    return undefined;
}
export class RoutedSession {
    piboSessionId;
    runtime;
    emit;
    pluginRegistry;
    forwardPiEvents;
    onSessionOperation;
    queue = [];
    processing = false;
    disposed = false;
    activeMessage;
    activeAssistantIndex;
    nextAssistantIndex = 0;
    activeThinkingIndex;
    nextThinkingIndex = 0;
    unsubscribe;
    constructor(piboSessionId, runtime, emit, pluginRegistry, forwardPiEvents, onSessionOperation) {
        this.piboSessionId = piboSessionId;
        this.runtime = runtime;
        this.emit = emit;
        this.pluginRegistry = pluginRegistry;
        this.forwardPiEvents = forwardPiEvents;
        this.onSessionOperation = onSessionOperation;
        this.bindRuntimeSession();
        this.runtime.setRebindSession(async () => {
            this.bindRuntimeSession();
        });
    }
    bindRuntimeSession() {
        this.unsubscribe?.();
        this.unsubscribe = this.runtime.session.subscribe((event) => {
            const normalized = normalizePiEvent(this.piboSessionId, event);
            if (normalized) {
                this.emit(this.withActiveMessage(normalized));
            }
            if (this.forwardPiEvents) {
                this.emit({ type: "pi_event", piboSessionId: this.piboSessionId, event });
            }
        });
    }
    enqueueMessage(event) {
        this.assertActive();
        this.queue.push(event);
        const output = {
            type: "message_queued",
            piboSessionId: this.piboSessionId,
            eventId: event.id,
            queuedMessages: this.queue.length,
            text: event.text,
            source: event.source,
        };
        this.emit(output);
        void this.drain();
        return output;
    }
    async executeAction(event) {
        this.assertActive();
        const result = await this.runAction(event);
        if (isSessionOperationResult(result))
            await this.onSessionOperation?.(result, event);
        const output = {
            type: "execution_result",
            piboSessionId: this.piboSessionId,
            eventId: event.id,
            action: event.action,
            result,
        };
        this.emit(output);
        return output;
    }
    getStatus() {
        return {
            piboSessionId: this.piboSessionId,
            queuedMessages: this.queue.length,
            processing: this.processing,
            streaming: this.runtime.session.isStreaming,
            activeTools: this.runtime.session.getActiveToolNames(),
            cwd: this.runtime.cwd,
            disposed: this.disposed,
        };
    }
    removeQueuedMessages(predicate) {
        this.assertActive();
        let removed = 0;
        for (let index = this.queue.length - 1; index >= 0; index -= 1) {
            if (!predicate(this.queue[index]))
                continue;
            this.queue.splice(index, 1);
            removed += 1;
        }
        return removed;
    }
    getCurrentSession() {
        return this.createSessionSnapshot();
    }
    async listSessions() {
        const manager = this.runtime.session.sessionManager;
        const sessions = await SessionManager.list(this.runtime.cwd, manager.getSessionDir());
        return sessions.map((session) => ({
            path: session.path,
            id: session.id,
            cwd: session.cwd,
            name: session.name,
            parentSessionPath: session.parentSessionPath,
            created: session.created.toISOString(),
            modified: session.modified.toISOString(),
            messageCount: session.messageCount,
            firstMessage: session.firstMessage,
        }));
    }
    getForkCandidates() {
        return this.runtime.session.getUserMessagesForForking();
    }
    async forkSession(entryId) {
        this.assertActive();
        const previous = this.createSessionSnapshot();
        const result = await this.runtime.fork(entryId);
        return {
            piboSessionId: this.piboSessionId,
            previous,
            current: this.createSessionSnapshot(),
            cancelled: result.cancelled,
            selectedText: result.selectedText,
        };
    }
    async cloneSession() {
        this.assertActive();
        const leafId = this.runtime.session.sessionManager.getLeafId();
        if (!leafId) {
            throw new Error("Cannot clone session: no current entry selected");
        }
        const previous = this.createSessionSnapshot();
        const result = await this.runtime.fork(leafId, { position: "at" });
        return {
            piboSessionId: this.piboSessionId,
            previous,
            current: this.createSessionSnapshot(),
            cancelled: result.cancelled,
        };
    }
    getSessionTree() {
        this.assertActive();
        return {
            current: this.createSessionSnapshot(),
            tree: normalizeSessionTree(this.runtime.session.sessionManager.getTree()),
        };
    }
    async navigateSessionTree(params) {
        this.assertActive();
        const previous = this.createSessionSnapshot();
        const result = await this.runtime.session.navigateTree(params.entryId, {
            summarize: params.summarize,
            customInstructions: params.customInstructions,
            replaceInstructions: params.replaceInstructions,
            label: params.label,
        });
        return {
            piboSessionId: this.piboSessionId,
            previous,
            current: this.createSessionSnapshot(),
            cancelled: result.cancelled,
            editorText: result.editorText,
            summaryEntryId: result.summaryEntry?.id,
        };
    }
    async switchSession(params) {
        this.assertActive();
        const previous = this.createSessionSnapshot();
        const result = await this.runtime.switchSession(params.sessionFile, { cwdOverride: params.cwdOverride });
        return {
            piboSessionId: this.piboSessionId,
            previous,
            current: this.createSessionSnapshot(),
            cancelled: result.cancelled,
        };
    }
    setThinkingLevel(level) {
        this.assertActive();
        this.runtime.session.setThinkingLevel(level);
        return this.getThinkingResult();
    }
    cycleThinkingLevel() {
        this.assertActive();
        this.runtime.session.cycleThinkingLevel();
        return this.getThinkingResult();
    }
    async dispose() {
        if (this.disposed)
            return;
        this.queue.length = 0;
        this.unsubscribe?.();
        this.unsubscribe = undefined;
        this.disposed = true;
        await this.runtime.dispose();
    }
    async cancelMessage(eventId) {
        this.assertActive();
        const queuedIndex = this.queue.findIndex((event) => event.id === eventId);
        if (queuedIndex >= 0) {
            this.queue.splice(queuedIndex, 1);
            return true;
        }
        if (this.activeMessage?.id === eventId) {
            await this.runtime.session.abort();
            return true;
        }
        return false;
    }
    async drain() {
        if (this.processing || this.disposed)
            return;
        this.processing = true;
        try {
            while (this.queue.length > 0 && !this.disposed) {
                const event = this.queue.shift();
                this.emit({
                    type: "message_started",
                    piboSessionId: this.piboSessionId,
                    eventId: event.id,
                    text: event.text,
                    source: event.source,
                });
                try {
                    this.activeMessage = event;
                    this.activeAssistantIndex = undefined;
                    this.nextAssistantIndex = 0;
                    this.activeThinkingIndex = undefined;
                    this.nextThinkingIndex = 0;
                    await this.runtime.session.prompt(event.text, { source: promptSource(event.source) });
                    this.emit({
                        type: "message_finished",
                        piboSessionId: this.piboSessionId,
                        eventId: event.id,
                        source: event.source,
                    });
                }
                catch (error) {
                    this.emit({
                        type: "session_error",
                        piboSessionId: this.piboSessionId,
                        eventId: event.id,
                        error: errorMessage(error),
                    });
                }
                finally {
                    this.activeMessage = undefined;
                    this.activeAssistantIndex = undefined;
                    this.nextAssistantIndex = 0;
                    this.activeThinkingIndex = undefined;
                    this.nextThinkingIndex = 0;
                }
            }
        }
        finally {
            this.processing = false;
        }
    }
    async runAction(event) {
        const action = event.action;
        const gatewayAction = this.pluginRegistry.getGatewayAction(action);
        if (!gatewayAction) {
            throw new Error(`Unknown execution action "${action}"`);
        }
        return await gatewayAction.execute({
            piboSessionId: this.piboSessionId,
            getStatus: () => this.getStatus(),
            clearQueue: () => this.clearQueue(),
            abort: async () => {
                await this.runtime.session.abort();
            },
            dispose: () => this.dispose(),
            getCurrentSession: () => this.getCurrentSession(),
            listSessions: () => this.listSessions(),
            getForkCandidates: () => this.getForkCandidates(),
            forkSession: (entryId) => this.forkSession(entryId),
            cloneSession: () => this.cloneSession(),
            getSessionTree: () => this.getSessionTree(),
            navigateSessionTree: (params) => this.navigateSessionTree(params),
            switchSession: (params) => this.switchSession(params),
            setThinkingLevel: (level) => this.setThinkingLevel(level),
            cycleThinkingLevel: () => this.cycleThinkingLevel(),
        }, event);
    }
    assertActive() {
        if (this.disposed) {
            throw new Error(`Session "${this.piboSessionId}" has been disposed`);
        }
    }
    getThinkingResult() {
        return {
            level: this.runtime.session.thinkingLevel,
            availableLevels: this.runtime.session.getAvailableThinkingLevels(),
            supported: this.runtime.session.supportsThinking(),
        };
    }
    clearQueue() {
        const cleared = this.queue.length;
        this.queue.length = 0;
        return cleared;
    }
    createSessionSnapshot() {
        const session = this.runtime.session;
        const manager = session.sessionManager;
        return {
            piSessionId: session.sessionId,
            sessionFile: session.sessionFile,
            leafId: manager.getLeafId(),
            cwd: this.runtime.cwd,
            sessionName: session.sessionName,
            parentSessionFile: manager.getHeader()?.parentSession,
        };
    }
    withActiveMessage(event) {
        if (this.activeMessage?.id && event.type === "assistant_delta") {
            const assistantIndex = this.activeAssistantIndex ?? this.nextAssistantIndex;
            if (this.activeAssistantIndex === undefined) {
                this.nextAssistantIndex += 1;
                this.activeAssistantIndex = assistantIndex;
            }
            return { ...event, eventId: this.activeMessage.id, assistantIndex };
        }
        if (this.activeMessage?.id && event.type === "assistant_message") {
            const assistantIndex = this.activeAssistantIndex ?? this.nextAssistantIndex;
            if (this.activeAssistantIndex === undefined) {
                this.nextAssistantIndex += 1;
            }
            this.activeAssistantIndex = undefined;
            return { ...event, eventId: this.activeMessage.id, assistantIndex };
        }
        if (this.activeMessage?.id && event.type === "thinking_started") {
            const thinkingIndex = this.nextThinkingIndex;
            this.nextThinkingIndex += 1;
            this.activeThinkingIndex = thinkingIndex;
            return { ...event, eventId: this.activeMessage.id, thinkingIndex };
        }
        if (this.activeMessage?.id && (event.type === "thinking_delta" || event.type === "thinking_finished")) {
            const thinkingIndex = this.activeThinkingIndex ?? this.nextThinkingIndex;
            if (this.activeThinkingIndex === undefined) {
                this.nextThinkingIndex += 1;
                this.activeThinkingIndex = thinkingIndex;
            }
            const output = { ...event, eventId: this.activeMessage.id, thinkingIndex };
            if (event.type === "thinking_finished")
                this.activeThinkingIndex = undefined;
            return output;
        }
        if (this.activeMessage?.id &&
            (event.type === "tool_call" ||
                event.type === "tool_execution_started" ||
                event.type === "tool_execution_updated" ||
                event.type === "tool_execution_finished" ||
                event.type === "session_error")) {
            return { ...event, eventId: this.activeMessage.id };
        }
        return event;
    }
}
function normalizeSessionTree(nodes) {
    return nodes.map((node) => ({
        entry: JSON.parse(JSON.stringify(node.entry)),
        children: normalizeSessionTree(node.children),
        label: node.label,
        labelTimestamp: node.labelTimestamp,
    }));
}
function isSessionOperationResult(value) {
    if (!value || typeof value !== "object")
        return false;
    const candidate = value;
    return (typeof candidate.piboSessionId === "string" &&
        Boolean(candidate.current) &&
        typeof candidate.current?.piSessionId === "string");
}
