import { appendFileSync, mkdirSync } from "node:fs";
import { homedir } from "node:os";
import { dirname, join } from "node:path";
export class TranscriptViewport {
    content;
    getRows;
    onStateChange;
    getDiagnostics;
    scrollOffsetFromBottom = 0;
    unreadLineCount = 0;
    lastContentHeight = 0;
    lastEmittedState;
    lastDiagnosticAt = 0;
    cachedWidth;
    cachedContentLines;
    constructor(content, getRows, onStateChange, getDiagnostics) {
        this.content = content;
        this.getRows = getRows;
        this.onStateChange = onStateChange;
        this.getDiagnostics = getDiagnostics;
    }
    invalidate() {
        this.invalidateCache();
        this.content.invalidate();
    }
    invalidateCache() {
        this.cachedWidth = undefined;
        this.cachedContentLines = undefined;
    }
    render(width) {
        const rows = Math.max(0, Math.floor(this.getRows()));
        if (rows === 0) {
            this.lastContentHeight = this.content.render(width).length;
            return [];
        }
        const startedAt = performance.now();
        const cached = this.cachedWidth === width && this.cachedContentLines !== undefined;
        const contentLines = cached ? this.cachedContentLines : this.content.render(width);
        if (!cached) {
            this.cachedWidth = width;
            this.cachedContentLines = contentLines;
        }
        const renderMs = performance.now() - startedAt;
        const contentHeight = contentLines.length;
        this.writeDiagnostics({ width, rows, contentHeight, renderMs, cached });
        const maxOffset = Math.max(0, contentHeight - rows);
        const grewBy = Math.max(0, contentHeight - this.lastContentHeight);
        if (this.scrollOffsetFromBottom > 0 && grewBy > 0) {
            this.scrollOffsetFromBottom += grewBy;
            this.unreadLineCount += grewBy;
        }
        this.scrollOffsetFromBottom = Math.min(Math.max(0, this.scrollOffsetFromBottom), maxOffset);
        if (this.scrollOffsetFromBottom === 0) {
            this.unreadLineCount = 0;
        }
        this.lastContentHeight = contentHeight;
        this.emitStateIfChanged();
        if (contentHeight <= rows) {
            return [...contentLines, ...Array.from({ length: rows - contentHeight }, () => "")];
        }
        const end = contentHeight - this.scrollOffsetFromBottom;
        const start = Math.max(0, end - rows);
        return contentLines.slice(start, end);
    }
    scrollUp(lines = 1) {
        this.scrollOffsetFromBottom += Math.max(0, Math.floor(lines));
    }
    scrollDown(lines = 1) {
        this.scrollOffsetFromBottom = Math.max(0, this.scrollOffsetFromBottom - Math.max(0, Math.floor(lines)));
        if (this.scrollOffsetFromBottom === 0) {
            this.unreadLineCount = 0;
        }
    }
    pageUp() {
        this.scrollUp(Math.max(1, this.getRows() - 1));
    }
    pageDown() {
        this.scrollDown(Math.max(1, this.getRows() - 1));
    }
    followLatest() {
        this.scrollOffsetFromBottom = 0;
        this.unreadLineCount = 0;
    }
    isPinned() {
        return this.scrollOffsetFromBottom > 0;
    }
    getUnreadLineCount() {
        return this.unreadLineCount;
    }
    getScrollOffsetFromBottom() {
        return this.scrollOffsetFromBottom;
    }
    writeDiagnostics(data) {
        if (process.env.THREADWELL_DEBUG_TRANSCRIPT !== "1")
            return;
        const now = Date.now();
        if (now - this.lastDiagnosticAt < 250 && data.renderMs < 8)
            return;
        this.lastDiagnosticAt = now;
        const logPath = join(homedir(), ".threadwell", "agent", "threadwell-debug.log");
        mkdirSync(dirname(logPath), { recursive: true });
        const diagnostics = this.getDiagnostics?.();
        appendFileSync(logPath, `[${new Date(now).toISOString()}] transcriptRender width=${data.width} rows=${data.rows} lines=${data.contentHeight} renderMs=${data.renderMs.toFixed(2)} cached=${data.cached} scrollOffset=${this.scrollOffsetFromBottom} unread=${this.unreadLineCount} childCount=${diagnostics?.childCount ?? "unknown"}\n`);
    }
    emitStateIfChanged() {
        if (!this.onStateChange)
            return;
        const state = {
            pinned: this.isPinned(),
            unreadLineCount: this.unreadLineCount,
            scrollOffsetFromBottom: this.scrollOffsetFromBottom,
        };
        if (this.lastEmittedState?.pinned === state.pinned &&
            this.lastEmittedState.unreadLineCount === state.unreadLineCount &&
            this.lastEmittedState.scrollOffsetFromBottom === state.scrollOffsetFromBottom) {
            return;
        }
        this.lastEmittedState = state;
        this.onStateChange(state);
    }
}
//# sourceMappingURL=transcript-viewport.js.map