import { hostname } from "node:os";
import { getAgentDir } from "@sheason/pi-coding-agent";
import { HUB_PROTOCOL_VERSION, } from "../../hub/index.js";
import { getErrorMessage } from "../../hub/utils.js";
import { SocketPeerClient } from "../client/socket-client.js";
import { collectPeerConfigSnapshot } from "../config/peer-config-snapshot.js";
import { HUB_DEFAULT_AGENT_ID } from "../constants.js";
import { PeerMcpRuntime } from "../mcp/peer-mcp-runtime.js";
import { PeerSourceRuntime } from "../sources/peer-source-runtime.js";
import { PeerAppState } from "../state/peer-app-state.js";
import { PeerUiState } from "../state/peer-ui-state.js";
import { executePeerToolRequest } from "../tools/index.js";
function isSourceResourceNotFoundError(error) {
    const message = getErrorMessage(error);
    return /Source resourceId .* not found/i.test(message);
}
export class PeerRuntime {
    appState = new PeerAppState();
    uiState = new PeerUiState();
    client;
    hello;
    peerMcpRuntime;
    peerSourceRuntime;
    cwd;
    agentDir;
    configSnapshot;
    baseTools;
    constructor(options) {
        const cwd = options.cwd ?? process.cwd();
        const agentDir = options.agentDir ?? getAgentDir();
        this.cwd = cwd;
        this.agentDir = agentDir;
        const configSnapshot = collectPeerConfigSnapshot({ cwd, agentDir });
        this.configSnapshot = configSnapshot;
        const executorEnabled = options.clientKind === "guest" ? false : options.executorEnabled !== false;
        this.baseTools = executorEnabled
            ? (options.tools ?? ["read", "write", "edit", "bash", "grep", "find", "ls"])
            : [];
        this.hello = {
            peerId: options.peerId ?? `${hostname()}-${process.pid}`,
            ...(options.agentId !== undefined && options.agentId !== "" ? { agentId: options.agentId } : {}),
            token: options.token ?? "",
            protocolVersion: HUB_PROTOCOL_VERSION,
            ...(options.clientKind && { clientKind: options.clientKind }),
            displayName: options.displayName,
            version: options.version,
            platform: process.platform,
            hostname: hostname(),
            cwd,
            ...(options.executorEnabled === false ? { executorEnabled: false } : {}),
        };
        this.peerMcpRuntime = new PeerMcpRuntime({
            cwd: this.hello.cwd ?? process.cwd(),
            snapshot: configSnapshot,
        });
        this.client = new SocketPeerClient({
            hubUrl: options.hubUrl,
            hello: this.hello,
            appState: this.appState,
            uiState: this.uiState,
            onHandshakeLog: options.onHandshakeLog,
            onGuestAgentMessage: options.onGuestAgentMessage,
            onToolCallRequest: options.onToolCallRequest ??
                (async (payload, socket) => {
                    if (await this.peerMcpRuntime.executeToolRequest(this.hello.peerId, payload, socket)) {
                        return;
                    }
                    await executePeerToolRequest(this.hello.cwd ?? process.cwd(), payload, socket);
                }),
        });
        this.peerSourceRuntime = new PeerSourceRuntime({
            cwd,
            agentDir,
            peerId: this.hello.peerId,
            isHubRunning: () => this.appState.getSnapshot().selectedAgent?.status.isRunning === true,
            targetAgentId: () => this.getBoundAgentId(),
            emitSourceMessage: async (sourceName, text, agentId) => {
                await this.client.sendSourceMessage({ sourceName, text, ...(agentId && { agentId }) });
            },
        });
    }
    async start() {
        let peerMcpStarted = false;
        try {
            if (this.hello.executorEnabled !== false) {
                await this.peerMcpRuntime.start();
                peerMcpStarted = true;
            }
            const config = this.createPeerConfigPayload();
            await this.client.connect();
            if (this.hello.clientKind !== "guest") {
                await this.client.uploadConfig(config);
            }
            await this.client.waitForInitialSync();
            if (this.hello.clientKind !== "guest") {
                await this.peerSourceRuntime.start();
            }
        }
        catch (error) {
            if (peerMcpStarted) {
                await this.peerMcpRuntime.stop().catch(() => { });
            }
            await this.client.disconnect().catch(() => { });
            throw error;
        }
    }
    async stop() {
        await this.peerSourceRuntime.stop();
        await this.client.disconnect();
        await this.peerMcpRuntime.stop();
    }
    async switchAgent(agentId) {
        const nextAgentId = agentId.trim();
        if (!nextAgentId) {
            throw new Error("Agent id is required.");
        }
        if (nextAgentId === this.getBoundAgentId()) {
            this.hello.agentId = nextAgentId;
            return;
        }
        const previousAgentId = this.hello.agentId;
        await this.peerSourceRuntime.stop();
        try {
            await this.client.disconnect();
            this.hello.agentId = nextAgentId;
            await this.client.connect();
            await this.client.uploadConfig(this.createPeerConfigPayload());
            await this.client.waitForInitialSync();
            await this.peerSourceRuntime.start();
        }
        catch (error) {
            await this.client.disconnect().catch(() => { });
            this.hello.agentId = previousAgentId;
            await this.restorePreviousAgentAfterFailedSwitch().catch(() => { });
            throw error;
        }
    }
    /**
     * Effective hub agent for this peer: authoritative `hub:welcome.agentId` when connected,
     * otherwise the `agentId` from `peer:hello` if set, or `root`.
     */
    getBoundAgentId() {
        const fromWelcome = this.appState.getSnapshot().welcome?.agentId;
        if (fromWelcome !== undefined && fromWelcome !== "") {
            return fromWelcome;
        }
        if (this.hello.agentId !== undefined && this.hello.agentId.trim() !== "") {
            return this.hello.agentId.trim();
        }
        return HUB_DEFAULT_AGENT_ID;
    }
    async queueWrite(text) {
        await this.client.queueWrite(text);
    }
    async queueFlush() {
        await this.client.queueFlush();
    }
    async abort() {
        return this.client.abort();
    }
    async setModel(modelResourceId) {
        await this.client.setModel(modelResourceId);
    }
    async setThinkingLevel(level) {
        await this.client.setThinkingLevel(level);
    }
    async invokeCommand(commandName, args) {
        if (commandName === "reload") {
            await this.client.uploadConfig(this.createPeerConfigPayload());
        }
        await this.client.invokeCommand(commandName, args);
    }
    async getSessionSources() {
        return [...(await this.client.getSessionSources()), ...this.peerSourceRuntime.getStatuses()];
    }
    async pauseSource(resourceId) {
        let found = false;
        let notFoundError;
        try {
            await this.client.pauseSource(resourceId);
            found = true;
        }
        catch (error) {
            if (!isSourceResourceNotFoundError(error)) {
                throw error;
            }
            notFoundError = error;
        }
        for (const localResourceId of this.peerSourceRuntime.getMatchingLocalSourceResourceIds(resourceId)) {
            await this.peerSourceRuntime.pauseSource(localResourceId);
            found = true;
        }
        if (!found) {
            throw notFoundError instanceof Error ? notFoundError : new Error(`Source resourceId ${resourceId} not found`);
        }
        return this.getSessionSources();
    }
    async restartSource(resourceId) {
        let notFoundError;
        try {
            await this.client.restartSource(resourceId);
            return this.getSessionSources();
        }
        catch (error) {
            if (!isSourceResourceNotFoundError(error)) {
                throw error;
            }
            notFoundError = error;
        }
        const localResourceIds = this.peerSourceRuntime.getMatchingLocalSourceResourceIds(resourceId);
        if (localResourceIds.length === 0) {
            throw notFoundError instanceof Error ? notFoundError : new Error(`Source resourceId ${resourceId} not found`);
        }
        for (const localResourceId of localResourceIds) {
            await this.peerSourceRuntime.restartSource(localResourceId);
        }
        return this.getSessionSources();
    }
    async removeSource(resourceId) {
        if (this.peerSourceRuntime.hasLocalSourceResourceId(resourceId)) {
            await this.peerSourceRuntime.removeSource(resourceId);
            return this.getSessionSources();
        }
        return this.client.removeSource(resourceId);
    }
    async getMcpServers() {
        return this.client.getMcpServers();
    }
    async getSkills() {
        return this.client.getSkills();
    }
    async pauseMcpServer(name) {
        return this.client.pauseMcpServer(name);
    }
    async restartMcpServer(name) {
        return this.client.restartMcpServer(name);
    }
    async removeMcpServer(name) {
        return this.client.removeMcpServer(name);
    }
    retryConnection() {
        this.client.retryConnectionNow();
    }
    createPeerConfigPayload() {
        this.configSnapshot = collectPeerConfigSnapshot({ cwd: this.cwd, agentDir: this.agentDir });
        const config = {
            configSnapshot: this.configSnapshot,
            tools: [],
        };
        if (this.hello.executorEnabled !== false) {
            config.mcpSnapshot = this.peerMcpRuntime.getSnapshot();
            config.tools = [...this.baseTools, ...this.peerMcpRuntime.getRemoteToolNames(this.hello.peerId)];
        }
        return config;
    }
    async restorePreviousAgentAfterFailedSwitch() {
        await this.client.connect();
        await this.client.uploadConfig(this.createPeerConfigPayload());
        await this.client.waitForInitialSync();
        await this.peerSourceRuntime.start();
    }
    getStatus() {
        return {
            hello: this.hello,
            app: this.appState.getSnapshot(),
            ui: this.uiState.getSnapshot(),
        };
    }
}
//# sourceMappingURL=peer-runtime.js.map