import { runRender } from "./render-runner.js";
export class RenderFrontend {
    client;
    workerId;
    version;
    workspace;
    llm;
    logger;
    inFlight = null;
    heartbeatTimer = null;
    detachers = [];
    currentStatus = "idle";
    constructor(opts) {
        this.client = opts.client;
        this.workerId = opts.workerId;
        this.version = opts.version ?? "unknown";
        this.workspace = opts.workspace;
        this.llm = opts.llm;
        this.logger = opts.logger ?? {
            info: (m) => console.log(m),
            warn: (m) => console.warn(m),
            error: (m) => console.error(m),
        };
    }
    async start() {
        const onMessage = (msg) => {
            this.handleIncoming(msg).catch((err) => this.logger.error(`RenderFrontend handler crashed for ${msg.type}: ${err instanceof Error ? err.message : err}`));
        };
        this.client.on("message", onMessage);
        this.detachers.push(() => this.client.off("message", onMessage));
        const onOpen = () => {
            this.sendHello().catch((err) => this.logger.warn(`RenderFrontend hello failed: ${err instanceof Error ? err.message : err}`));
        };
        this.client.on("open", onOpen);
        this.detachers.push(() => this.client.off("open", onOpen));
        this.heartbeatTimer = setInterval(() => {
            this.client.send({ type: "worker_heartbeat", payload: {} }).catch((err) => this.logger.warn(`[RenderFrontend] heartbeat failed: ${err instanceof Error ? err.message : err}`));
        }, 30_000);
    }
    async stop() {
        if (this.heartbeatTimer) {
            clearInterval(this.heartbeatTimer);
            this.heartbeatTimer = null;
        }
        for (const d of this.detachers)
            d();
        this.detachers = [];
        if (this.inFlight) {
            this.inFlight.controller.abort();
            this.inFlight = null;
        }
    }
    async sendHello() {
        const extras = this.client.getExtras();
        await this.client.send({
            type: "hello",
            payload: {
                worker_id: this.workerId,
                version: this.version,
                config_etag: extras?.configEtag ?? "unknown",
                current_status: this.currentStatus,
            },
        });
    }
    async handleIncoming(msg) {
        switch (msg.type) {
            case "task_dispatch":
                await this.handleDispatch(msg.payload);
                return;
            case "cancel_task": {
                const p = msg.payload;
                if (this.inFlight?.taskRunId === p.task_run_id) {
                    this.inFlight.controller.abort();
                }
                return;
            }
            case "ping_request": {
                const p = msg.payload;
                await this.client.send({
                    type: "pong",
                    payload: { ref_id: p.ref_id, status: this.currentStatus },
                });
                return;
            }
            default:
                // Render workers ignore chat/config messages.
                return;
        }
    }
    async handleDispatch(payload) {
        if (payload.kind !== "render" || !payload.render) {
            this.logger.warn(`[RenderFrontend] ignoring non-render dispatch ${payload.task_run_id}`);
            return;
        }
        if (this.inFlight) {
            this.logger.warn(`[RenderFrontend] busy; rejecting ${payload.task_run_id}`);
            return;
        }
        const controller = new AbortController();
        this.inFlight = { taskRunId: payload.task_run_id, controller };
        this.currentStatus = "busy";
        await this.emitEvent({ task_run_id: payload.task_run_id, event: "dispatched_ack" });
        await this.emitEvent({ task_run_id: payload.task_run_id, event: "started", status: "running" });
        await this.sendStatus("busy", payload.task_run_id);
        try {
            const result = await runRender(payload.render, {
                workspace: this.workspace,
                llm: this.llm,
                signal: controller.signal,
                logger: this.logger,
            });
            await this.emitEvent({
                task_run_id: payload.task_run_id,
                event: "finished",
                status: "success",
                result,
            });
        }
        catch (err) {
            const message = err instanceof Error ? err.message : String(err);
            this.logger.error(`[RenderFrontend] render ${payload.task_run_id} failed: ${message}`);
            await this.emitEvent({
                task_run_id: payload.task_run_id,
                event: "finished",
                status: "failed",
                error: message,
                result: { error: message },
            });
        }
        finally {
            this.inFlight = null;
            this.currentStatus = "idle";
            await this.sendStatus("idle", null);
        }
    }
    async emitEvent(payload) {
        try {
            await this.client.send({ type: "task_event", payload });
        }
        catch (err) {
            this.logger.warn(`[RenderFrontend] task_event send failed: ${err instanceof Error ? err.message : err}`);
        }
    }
    async sendStatus(status, taskId) {
        try {
            await this.client.send({
                type: "status_change",
                payload: { status, ...(taskId ? { current_task_id: taskId } : {}) },
            });
        }
        catch (err) {
            this.logger.warn(`[RenderFrontend] status_change failed: ${err instanceof Error ? err.message : err}`);
        }
    }
}
//# sourceMappingURL=render-frontend.js.map