/**
 * Serve mode: Run the agent as a persistent WebSocket server.
 *
 * Each WebSocket connection gets its own AgentSession with full RPC protocol.
 * The server listens on localhost (default port 3141).
 *
 * Protocol:
 * - Client sends JSON messages (same format as RPC commands)
 * - Server sends JSON messages (same format as RPC responses/events)
 * - One agent session per WebSocket connection
 */
import * as fs from "node:fs";
import * as http from "node:http";
import * as path from "node:path";
import { WebSocket, WebSocketServer } from "ws";
import { handleGatewayRequest } from "../../core/local/model-gateway.js";
import { createAgentSession } from "../../core/sdk.js";
import { runRpcOverTransport } from "../rpc/rpc-transport.js";
const PID_FILE = path.join(process.env.HOME ?? "/tmp", ".avis", "daemon.pid");
function writePidFile(port) {
    const dir = path.dirname(PID_FILE);
    fs.mkdirSync(dir, { recursive: true });
    fs.writeFileSync(PID_FILE, JSON.stringify({ pid: process.pid, port }));
}
function removePidFile() {
    try {
        fs.unlinkSync(PID_FILE);
    }
    catch {
        // ignore
    }
}
export function readPidFile() {
    try {
        const data = JSON.parse(fs.readFileSync(PID_FILE, "utf-8"));
        return data;
    }
    catch {
        return null;
    }
}
/**
 * Check if the daemon is running.
 */
export function isDaemonRunning() {
    const info = readPidFile();
    if (!info)
        return null;
    try {
        // Signal 0 checks if process exists without killing it
        process.kill(info.pid, 0);
        return info;
    }
    catch {
        // Process not running, clean up stale pid file
        removePidFile();
        return null;
    }
}
/**
 * Start the WebSocket server.
 */
export async function runServeMode(options) {
    const { port, host, sessionOptionsFactory } = options;
    const server = http.createServer(async (req, res) => {
        // Model gateway handles /props, /v1/models, /v1/chat/completions
        const handled = await handleGatewayRequest(req, res);
        if (handled)
            return;
        // Health check fallback
        res.writeHead(200, { "Content-Type": "application/json" });
        res.end(JSON.stringify({ status: "ok", pid: process.pid }));
    });
    const wss = new WebSocketServer({ server });
    let connectionCount = 0;
    wss.on("connection", async (ws) => {
        const connId = ++connectionCount;
        console.error(`[serve] connection #${connId} opened`);
        try {
            const sessionOptions = await sessionOptionsFactory();
            const { session } = await createAgentSession(sessionOptions);
            // Create a transport that bridges WebSocket <-> RPC protocol
            const transport = {
                send(data) {
                    if (ws.readyState === WebSocket.OPEN) {
                        ws.send(data);
                    }
                },
                onMessage(handler) {
                    ws.on("message", (raw) => {
                        handler(raw.toString());
                    });
                },
                onClose(handler) {
                    ws.on("close", handler);
                },
            };
            await runRpcOverTransport(session, transport);
        }
        catch (err) {
            console.error(`[serve] connection #${connId} error:`, err);
            if (ws.readyState === WebSocket.OPEN) {
                ws.close(1011, "Internal error");
            }
        }
        console.error(`[serve] connection #${connId} closed`);
    });
    // Graceful shutdown
    const shutdown = () => {
        console.error("[serve] shutting down...");
        removePidFile();
        // Close all connections
        for (const client of wss.clients) {
            client.close(1001, "Server shutting down");
        }
        wss.close();
        server.close(() => {
            process.exit(0);
        });
        // Force exit after 5s
        setTimeout(() => process.exit(1), 5000).unref();
    };
    process.on("SIGTERM", shutdown);
    process.on("SIGINT", shutdown);
    process.title = "Avis Daemon";
    return new Promise((_resolve, reject) => {
        server.on("error", (err) => {
            if (err.code === "EADDRINUSE") {
                const running = isDaemonRunning();
                if (running) {
                    console.error(`[serve] port ${port} in use (daemon already running, pid ${running.pid})`);
                }
                else {
                    console.error(`[serve] port ${port} already in use`);
                }
                process.exit(1);
            }
            reject(err);
        });
        server.listen(port, host, () => {
            writePidFile(port);
            console.error(`[serve] avis daemon listening on ws://${host}:${port} (pid ${process.pid})`);
        });
    });
}
//# sourceMappingURL=serve-mode.js.map