import { layout } from "./layout.js";
import { render } from "./render/renderer.js";
export class Screen {
    output;
    input;
    width;
    height;
    constructor(opts) {
        this.output = opts.output;
        this.input = opts.input;
        this.width = opts.width;
        this.height = opts.height;
    }
    render(root, label) {
        const positioned = layout(root, this.width, this.height);
        const frame = render(positioned);
        this.output.write(frame, label);
        return frame;
    }
    nextKey() {
        return this.input.nextKey();
    }
    nextLine(prompt) {
        return this.input.nextLine(prompt);
    }
    size() {
        return { width: this.width, height: this.height };
    }
    async runLoop(opts) {
        // The render / handleKey / isDone callbacks may be async — the
        // declarative bridge in `lib/stdlib/ui.ts` adapts Agency callback
        // values into async wrappers (Agency functions invoke through the
        // runtime call path which returns a Promise). Pure-TS callers can
        // still pass sync functions; the awaits become trivial.
        let state = opts.initialState;
        this.render(await opts.render(state), opts.label);
        // Single in-flight `nextKey()` shared across tick iterations.
        // Calling `this.nextKey()` once per tick would enqueue a fresh
        // waiter on the underlying input source's FIFO queue every time
        // a tick wins the race; the next keypress would then resolve the
        // oldest waiter (an abandoned tick-race promise) instead of the
        // one the current `Promise.race` is awaiting, requiring one
        // keypress per accumulated tick before the loop starts reacting.
        let pendingKeyPromise = null;
        while (!(await opts.isDone(state))) {
            if (opts.tickMs !== undefined) {
                if (pendingKeyPromise === null) {
                    pendingKeyPromise = this.nextKey().then((ev) => ({ kind: "key", ev }));
                }
                const tickPromise = new Promise((resolve) => setTimeout(() => resolve({ kind: "tick" }), opts.tickMs));
                const result = await Promise.race([pendingKeyPromise, tickPromise]);
                if (result.kind === "key") {
                    pendingKeyPromise = null;
                    state = await opts.handleKey(state, result.ev);
                }
            }
            else {
                const event = await this.nextKey();
                state = await opts.handleKey(state, event);
            }
            this.render(await opts.render(state), opts.label);
        }
        return state;
    }
    destroy() {
        this.input.destroy();
        if (this.output.destroy)
            this.output.destroy();
    }
}
