import { TUI } from "@earendil-works/pi-tui";
import { runCallback } from "../../core/callback-activity.js";
import { CustomMessageComponent } from "../interactive/components/custom-message.js";
import { ToolExecutionComponent } from "../interactive/components/tool-execution.js";
import { INTERACTIVE_ENGINE_MAX_FRAME_BYTES, parseInteractiveEngineCommand, serializeInteractiveEngineMessage, } from "./protocol.js";
class RenderTerminal {
    constructor(requestRender) {
        this.columns = 80;
        this.rows = 24;
        this.kittyProtocolActive = false;
        this.requestRender = requestRender;
    }
    start() { }
    stop() { }
    async drainInput() { }
    write() { this.requestRender(); }
    moveBy() { }
    hideCursor() { }
    showCursor() { }
    clearLine() { }
    clearFromCursor() { }
    clearScreen() { }
    setTitle() { }
    setProgress() { }
}
function boundedLines(lines) {
    let remaining = INTERACTIVE_ENGINE_MAX_FRAME_BYTES - 512;
    const result = [];
    for (const line of lines) {
        if (remaining <= 0)
            break;
        const bytes = Buffer.from(line);
        const value = bytes.length <= remaining ? line : bytes.subarray(0, remaining).toString("utf8");
        result.push(value);
        remaining -= Buffer.byteLength(value, "utf8");
    }
    return result;
}
export class EngineRenderService {
    constructor(write) {
        this.records = new Map();
        this.write = write;
    }
    bindSession(session) {
        this.dispose();
        this.session = session;
    }
    handleLine(line) {
        const command = parseInteractiveEngineCommand(line);
        if (!command || !command.type.startsWith("engine_") || command.type.startsWith("engine_custom_"))
            return false;
        if (command.type === "engine_render_dispose") {
            this.disposeRecord(command.componentId);
            return true;
        }
        if (command.type !== "engine_tool_render" && command.type !== "engine_message_render")
            return false;
        const name = command.type === "engine_tool_render" ? `tool:${command.toolName}` : `message:${command.message.customType ?? "custom"}`;
        void runCallback({ kind: "renderer", name }, async () => {
            return command.type === "engine_tool_render" ? this.renderTool(command) : this.renderMessage(command);
        }).then((lines) => this.send({
            type: "engine_custom_frame",
            componentId: command.componentId,
            requestId: command.requestId,
            lines,
        })).catch((error) => this.send({
            type: "engine_custom_frame",
            componentId: command.componentId,
            requestId: command.requestId,
            lines: [`Remote renderer failed: ${error.message}`],
        }));
        return true;
    }
    dispose() {
        for (const id of [...this.records.keys()])
            this.disposeRecord(id);
    }
    boundSession() {
        const session = this.session;
        if (!session)
            throw new Error("Renderer session is not bound");
        return session;
    }
    createTerminal(componentId) {
        return new RenderTerminal(() => this.send({ type: "engine_custom_invalidate", componentId }));
    }
    renderTool(command) {
        let record = this.records.get(command.componentId);
        if (record?.kind !== "tool") {
            const session = this.boundSession();
            this.disposeRecord(command.componentId);
            const terminal = this.createTerminal(command.componentId);
            terminal.columns = Math.max(1, command.width);
            const tui = new TUI(terminal);
            const component = new ToolExecutionComponent(command.toolName, command.toolCallId, command.args, { showImages: command.showImages, imageWidthCells: command.imageWidthCells }, session.getToolDefinition(command.toolName), tui, session.sessionManager.getCwd());
            tui.addChild(component);
            record = { kind: "tool", component, terminal, tui };
            this.records.set(command.componentId, record);
        }
        else {
            record.terminal.columns = Math.max(1, command.width);
            record.component.updateArgs(command.args);
            record.component.setShowImages(command.showImages);
            record.component.setImageWidthCells(command.imageWidthCells);
        }
        const tool = record.component;
        if (command.executionStarted)
            tool.markExecutionStarted();
        if (command.argsComplete)
            tool.setArgsComplete();
        tool.setExpanded(command.expanded);
        if (command.result) {
            tool.updateResult(command.result, command.isPartial);
        }
        return boundedLines(tool.render(command.width));
    }
    renderMessage(command) {
        let record = this.records.get(command.componentId);
        if (record?.kind !== "message") {
            const session = this.boundSession();
            this.disposeRecord(command.componentId);
            const terminal = this.createTerminal(command.componentId);
            terminal.columns = Math.max(1, command.width);
            const tui = new TUI(terminal);
            const message = command.message;
            const component = new CustomMessageComponent(message, session.extensionRunner.getMessageRenderer(message.customType));
            tui.addChild(component);
            record = { kind: "message", component, terminal, tui };
            this.records.set(command.componentId, record);
        }
        else {
            record.terminal.columns = Math.max(1, command.width);
        }
        record.component.setExpanded(command.expanded);
        return boundedLines(record.component.render(command.width));
    }
    disposeRecord(id) {
        const record = this.records.get(id);
        if (!record)
            return;
        this.records.delete(id);
        if (record.kind === "tool")
            record.component.dispose();
        record.tui.stop();
    }
    send(message) {
        this.write(serializeInteractiveEngineMessage(message));
    }
}
//# sourceMappingURL=engine-render-service.js.map