const ESC = "\x1b[";
const RESET = `${ESC}0m`;
const BOLD = `${ESC}1m`;
const DIM = `${ESC}2m`;
const FG_GREEN = `${ESC}32m`;
const FG_YELLOW = `${ESC}33m`;
const FG_CYAN = `${ESC}36m`;
const FG_RED = `${ESC}31m`;
const FG_WHITE = `${ESC}37m`;
const FG_GRAY = `${ESC}90m`;
const BG_BLUE = `${ESC}44m`;
const BG_DARK = `${ESC}48;5;236m`;
function formatTime(iso) {
    try {
        const d = new Date(iso);
        return d.toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
    }
    catch {
        return "??:??:??";
    }
}
function formatUptime(seconds) {
    const h = Math.floor(seconds / 3600);
    const m = Math.floor((seconds % 3600) / 60);
    if (h > 0)
        return `${h}h${m}m`;
    return `${m}m`;
}
export function splitGraphemes(s) {
    const Segmenter = Intl.Segmenter;
    if (typeof Segmenter === "function") {
        return Array.from(new Segmenter(undefined, { granularity: "grapheme" }).segment(s), (part) => part.segment);
    }
    return Array.from(s);
}
function charWidth(ch) {
    if (!ch)
        return 0;
    if (/^\p{Mark}$/u.test(ch))
        return 0;
    const code = ch.codePointAt(0) ?? 0;
    if (code === 0)
        return 0;
    if (code < 32 || (code >= 0x7f && code < 0xa0))
        return 0;
    if ((code >= 0x1100 && code <= 0x115f) ||
        (code >= 0x2329 && code <= 0x232a) ||
        (code >= 0x2e80 && code <= 0xa4cf) ||
        (code >= 0xac00 && code <= 0xd7a3) ||
        (code >= 0xf900 && code <= 0xfaff) ||
        (code >= 0xfe10 && code <= 0xfe19) ||
        (code >= 0xfe30 && code <= 0xfe6f) ||
        (code >= 0xff00 && code <= 0xff60) ||
        (code >= 0xffe0 && code <= 0xffe6) ||
        (code >= 0x1f300 && code <= 0x1faff))
        return 2;
    return 1;
}
export function displayWidth(s) {
    return splitGraphemes(s).reduce((sum, part) => sum + Math.max(...Array.from(part, charWidth), 0), 0);
}
function truncate(s, max) {
    let width = 0;
    let out = "";
    for (const part of splitGraphemes(s)) {
        const partWidth = displayWidth(part);
        if (width + partWidth > max)
            return out + "…";
        out += part;
        width += partWidth;
    }
    return out;
}
function stripAnsi(s) {
    // eslint-disable-next-line no-control-regex
    return displayWidth(s.replace(/\x1b\[[0-9;]*m/g, ""));
}
export class TuiRenderer {
    rows = 24;
    cols = 80;
    stream = [];
    maxStreamEntries = 500;
    inputLine = "";
    cursorPos = 0;
    status = null;
    scrollOffset = 0;
    connected = true;
    updateSize() {
        this.rows = process.stdout.rows || 24;
        this.cols = process.stdout.columns || 80;
    }
    setStatus(status) {
        this.status = status;
    }
    setConnected(connected) {
        this.connected = connected;
    }
    addStreamEntry(entry) {
        this.stream.push(entry);
        if (this.stream.length > this.maxStreamEntries) {
            this.stream = this.stream.slice(-this.maxStreamEntries);
        }
        if (this.scrollOffset === 0) {
            // auto-scroll when at bottom
        }
    }
    setInput(line, cursor) {
        this.inputLine = line;
        this.cursorPos = cursor;
    }
    render() {
        this.updateSize();
        const { rows, cols } = this;
        // Hide cursor during render
        process.stdout.write(`${ESC}?25l`);
        // Move to top-left
        process.stdout.write(`${ESC}1;1H`);
        // ── Status bar (2 lines) ────────────────────────────────────────────
        const statusBar = this.renderStatusBar(cols);
        process.stdout.write(statusBar);
        // ── Separator ─────────────────────────────────────────────────────
        process.stdout.write(`${FG_GRAY}${"─".repeat(cols)}${RESET}\n`);
        // ── Event stream (middle area) ──────────────────────────────────────
        const streamHeight = rows - 5; // 2 status + 1 separator + 1 input separator + 1 input
        const streamLines = this.renderStream(streamHeight, cols);
        for (const line of streamLines) {
            process.stdout.write(line + "\n");
        }
        // ── Input separator ─────────────────────────────────────────────────
        process.stdout.write(`${FG_GRAY}${"─".repeat(cols)}${RESET}\n`);
        // ── Input line ──────────────────────────────────────────────────────
        const prompt = `${FG_CYAN}>${RESET} `;
        const maxInputWidth = cols - 3;
        const displayInput = truncate(this.inputLine, maxInputWidth);
        const inputLine = `${prompt}${displayInput}${ESC}K`;
        process.stdout.write(inputLine);
        // Position cursor. cursorPos is a grapheme index; convert to terminal cell width.
        const cursorPrefix = splitGraphemes(this.inputLine).slice(0, this.cursorPos).join("");
        const cursorCol = Math.min(displayWidth(cursorPrefix), maxInputWidth) + 3;
        process.stdout.write(`${ESC}${rows};${cursorCol}H`);
        // Show cursor
        process.stdout.write(`${ESC}?25h`);
    }
    renderStatusBar(cols) {
        const s = this.status;
        if (!s) {
            const line = this.connected
                ? ` ${BG_DARK}${FG_WHITE} Connecting...${" ".repeat(Math.max(0, cols - 15))}${RESET}`
                : ` ${BG_DARK}${FG_RED} Disconnected${" ".repeat(Math.max(0, cols - 14))}${RESET}`;
            return `${line}\n${" ".repeat(cols)}\n`;
        }
        const statusIcon = s.busy ? `${FG_YELLOW}●${RESET}` : `${FG_GREEN}●${RESET}`;
        const statusText = s.busy ? "busy" : "idle";
        const sessionIcon = s.activeSession === "coding" ? `${FG_CYAN}⌨${RESET}` : `${FG_GREEN}💬${RESET}`;
        const line1Parts = [
            `${BG_BLUE}${FG_WHITE}${BOLD} ${s.agentName} ${RESET}`,
            ` ${DIM}v${s.version}${RESET}`,
            ` ${statusIcon} ${statusText}`,
            ` ${sessionIcon} ${s.activeSession}`,
            s.queueSize > 0 ? ` ${FG_YELLOW}📨 ${s.queueSize} queued${RESET}` : "",
            s.compacting ? ` ${FG_YELLOW}compacting${RESET}` : "",
            s.stopRequested ? ` ${FG_RED}stop requested${RESET}` : "",
        ];
        const line1 = line1Parts.join("") + `${ESC}K`;
        const line2Parts = [
            `${DIM} ↑ ${formatUptime(s.uptime)}${RESET}`,
            `${DIM} │ PID ${s.pid}${RESET}`,
            `${DIM} │ ${s.profile}${RESET}`,
            s.codingSession.status !== "idle"
                ? `${DIM} │ coding: ${truncate(s.codingSession.taskSummary || s.codingSession.status, 40)}${RESET}`
                : "",
        ];
        const line2 = line2Parts.join("") + `${ESC}K`;
        return `${line1}\n${line2}\n`;
    }
    renderStream(height, cols) {
        const lines = [];
        if (this.stream.length === 0) {
            const pad = Math.floor(height / 2);
            for (let i = 0; i < pad; i++)
                lines.push(`${ESC}K`);
            lines.push(`${FG_GRAY}  No events yet. Type a message or /help for commands.${RESET}${ESC}K`);
            for (let i = pad + 1; i < height; i++)
                lines.push(`${ESC}K`);
            return lines;
        }
        // Wrap and collect visible entries
        const wrappedLines = [];
        for (const entry of this.stream) {
            const entryLines = this.formatEntry(entry, cols);
            wrappedLines.push(...entryLines);
        }
        // Show last N lines that fit
        const visible = wrappedLines.slice(-height);
        for (let i = 0; i < height; i++) {
            if (i < height - visible.length) {
                lines.push(`${ESC}K`);
            }
            else {
                const idx = i - (height - visible.length);
                lines.push((visible[idx] ?? "") + `${ESC}K`);
            }
        }
        return lines;
    }
    formatEntry(entry, cols) {
        const time = `${FG_GRAY}${formatTime(entry.timestamp)}${RESET}`;
        let prefix;
        switch (entry.type) {
            case "reply":
                prefix = `${FG_GREEN}◀${RESET}`;
                break;
            case "input":
                prefix = `${FG_CYAN}▶${RESET}`;
                break;
            case "command":
                prefix = `${FG_YELLOW}⚡${RESET}`;
                break;
            case "error":
                prefix = `${FG_RED}✗${RESET}`;
                break;
            case "ack":
                prefix = `${FG_GRAY}⋯${RESET}`;
                break;
            default:
                prefix = `${FG_GRAY}·${RESET}`;
                break;
        }
        const stateTag = entry.state ? ` ${DIM}[${entry.state}]${RESET}` : "";
        const header = `${time} ${prefix}${stateTag} `;
        const headerVisualLen = stripAnsi(header);
        const contentWidth = cols - headerVisualLen;
        if (contentWidth < 10)
            return [`${header}${truncate(entry.text, 10)}`];
        const textLines = entry.text.split("\n");
        const result = [];
        for (let li = 0; li < textLines.length; li++) {
            const line = textLines[li];
            if (li === 0) {
                if (line.length <= contentWidth) {
                    result.push(`${header}${line}`);
                }
                else {
                    // Wrap long lines
                    for (let i = 0; i < line.length; i += contentWidth) {
                        const chunk = line.slice(i, i + contentWidth);
                        if (i === 0) {
                            result.push(`${header}${chunk}`);
                        }
                        else {
                            result.push(`${" ".repeat(headerVisualLen)}${chunk}`);
                        }
                    }
                }
            }
            else {
                if (line.length <= contentWidth) {
                    result.push(`${" ".repeat(headerVisualLen)}${line}`);
                }
                else {
                    for (let i = 0; i < line.length; i += contentWidth) {
                        result.push(`${" ".repeat(headerVisualLen)}${line.slice(i, i + contentWidth)}`);
                    }
                }
            }
        }
        return result;
    }
}
//# sourceMappingURL=renderer.js.map