import { closeSync, fstatSync, openSync, readSync } from 'node:fs';
const DEFAULT_READ_BYTES = 2 * 1024 * 1024;
const DEFAULT_MIN_READ_BYTES = 256 * 1024;
const LINE_BOUNDED_MIN_READ_BYTES = 128 * 1024;
const READ_BYTES_PER_RECORD = 8 * 1024;
const READ_BYTES_PER_RENDERED_LINE = 1536;
const DEFAULT_RECORD_LIMIT = 400;
const MAX_RECORD_LIMIT = 1000;
const MAX_RENDERED_LINES = 5000;
const MAX_RENDERED_CHARS = 1024 * 1024;
const MAX_RECORD_CHARS = 256 * 1024;
export function readTranscriptHistoryPage(terminalId, source, options = {}) {
    let fd = null;
    try {
        fd = openSync(source.filePath, 'r');
        const stat = fstatSync(fd);
        const cursor = options.cursor ? decodeCursor(options.cursor, source) : undefined;
        if (cursor === null) {
            return null;
        }
        const beforeOffset = Math.max(0, Math.min(cursor?.beforeOffset ?? stat.size, stat.size));
        const limit = normalizeRecordLimit(options.limit);
        const maxRenderedLines = normalizeRenderedLineLimit(options.maxLines);
        const includeTools = options.includeTools ?? true;
        const window = readLineWindow(fd, beforeOffset, readBytesForLimit(limit, maxRenderedLines));
        if (window.lines.length === 0) {
            const nextOffset = window.startOffset;
            return {
                terminalId,
                source: { provider: source.provider, sessionId: source.sessionId },
                cursor: nextOffset > 0 ? encodeCursor(source, nextOffset) : null,
                hasMore: nextOffset > 0,
                output: []
            };
        }
        const blocks = [];
        let firstIncludedStartChar = null;
        let renderedChars = 0;
        let renderedLines = 0;
        let renderedRecords = 0;
        for (let index = window.lines.length - 1; index >= 0; index -= 1) {
            const span = window.lines[index];
            const record = parseJsonLine(span.line);
            if (!record) {
                continue;
            }
            const block = renderRecord(source.provider, record, { includeTools });
            if (!block) {
                continue;
            }
            const cappedBlock = truncate(block, MAX_RECORD_CHARS);
            const nextChars = renderedChars + cappedBlock.length;
            const nextLines = renderedLines + countLines(cappedBlock);
            if (blocks.length > 0 && (nextChars > MAX_RENDERED_CHARS || nextLines > maxRenderedLines)) {
                break;
            }
            blocks.push(cappedBlock);
            firstIncludedStartChar = span.startChar;
            renderedChars = nextChars;
            renderedLines = nextLines;
            renderedRecords += 1;
            if (renderedRecords >= limit) {
                break;
            }
        }
        if (blocks.length === 0) {
            const nextOffset = window.startOffset;
            return {
                terminalId,
                source: { provider: source.provider, sessionId: source.sessionId },
                cursor: nextOffset > 0 ? encodeCursor(source, nextOffset) : null,
                hasMore: nextOffset > 0,
                output: []
            };
        }
        blocks.reverse();
        if (!options.cursor) {
            blocks.push(`[nterminal] ${providerLabel(source.provider)} transcript archive ends here. Live terminal screen continues below.`);
        }
        const nextOffset = firstIncludedStartChar === null
            ? window.startOffset
            : window.startOffset + Buffer.byteLength(window.text.slice(0, firstIncludedStartChar), 'utf8');
        const output = `${blocks.join('\n\n')}\n`.replace(/\n/g, '\r\n');
        return {
            terminalId,
            source: { provider: source.provider, sessionId: source.sessionId },
            cursor: nextOffset > 0 ? encodeCursor(source, nextOffset) : null,
            hasMore: nextOffset > 0,
            output: [output]
        };
    }
    catch {
        return null;
    }
    finally {
        if (fd !== null) {
            closeSync(fd);
        }
    }
}
function readLineWindow(fd, beforeOffset, maxReadBytes = DEFAULT_READ_BYTES) {
    if (beforeOffset <= 0) {
        return { text: '', startOffset: 0, lines: [] };
    }
    const startOffset = Math.max(0, beforeOffset - maxReadBytes);
    const readBytes = beforeOffset - startOffset;
    const buffer = Buffer.alloc(readBytes);
    const actualBytes = readSync(fd, buffer, 0, readBytes, startOffset);
    const text = buffer.subarray(0, actualBytes).toString('utf8');
    const lines = collectLineSpans(text, startOffset > 0);
    return { text, startOffset, lines };
}
function readBytesForLimit(limit, maxRenderedLines) {
    if (maxRenderedLines < MAX_RENDERED_LINES) {
        return Math.max(LINE_BOUNDED_MIN_READ_BYTES, Math.min(DEFAULT_READ_BYTES, maxRenderedLines * READ_BYTES_PER_RENDERED_LINE));
    }
    return Math.max(DEFAULT_MIN_READ_BYTES, Math.min(DEFAULT_READ_BYTES, limit * READ_BYTES_PER_RECORD));
}
function collectLineSpans(text, skipLeadingPartial) {
    const spans = [];
    let lineStart = 0;
    for (let index = 0; index < text.length; index += 1) {
        if (text.charCodeAt(index) !== 10) {
            continue;
        }
        addSpan(spans, text, lineStart, index, skipLeadingPartial && lineStart === 0);
        lineStart = index + 1;
    }
    if (lineStart < text.length) {
        addSpan(spans, text, lineStart, text.length, skipLeadingPartial && lineStart === 0);
    }
    return spans;
}
function addSpan(spans, text, start, end, skip) {
    if (skip) {
        return;
    }
    const lineEnd = end > start && text.charCodeAt(end - 1) === 13 ? end - 1 : end;
    const line = text.slice(start, lineEnd);
    if (line.trim()) {
        spans.push({ line, startChar: start });
    }
}
function renderRecord(provider, record, options) {
    return provider === 'codex' ? renderCodexRecord(record, options) : renderClaudeRecord(record, options);
}
function renderCodexRecord(record, options) {
    const type = readString(record.type);
    const payload = readRecord(record.payload);
    if (type === 'event_msg' && payload && readString(payload.type) === 'user_message') {
        return formatBlock('User', readString(record.timestamp), readString(payload.message));
    }
    if (type === 'compacted') {
        return formatBlock('System', readString(record.timestamp), '[context compacted]');
    }
    if (type !== 'response_item' || !payload) {
        return null;
    }
    const payloadType = readString(payload.type);
    if (payloadType === 'message' && readString(payload.role) === 'assistant') {
        return formatBlock('Codex', readString(record.timestamp), extractContentText(payload.content));
    }
    if (payloadType === 'function_call' || payloadType === 'custom_tool_call') {
        if (!options.includeTools) {
            return null;
        }
        const name = readString(payload.name) || 'tool';
        return formatBlock(`Tool call: ${name}`, readString(record.timestamp), stringifyForTranscript(payload.arguments ?? payload.input));
    }
    if (payloadType === 'function_call_output' || payloadType === 'custom_tool_call_output') {
        if (!options.includeTools) {
            return null;
        }
        return formatBlock('Tool output', readString(record.timestamp), stringifyForTranscript(payload.output));
    }
    return null;
}
function renderClaudeRecord(record, options) {
    const type = readString(record.type);
    if (!type || type === 'last-prompt' || type === 'permission-mode' || type === 'file-history-snapshot' || type === 'summary') {
        return null;
    }
    const message = readRecord(record.message);
    if (type === 'user' && record.isMeta !== true) {
        return renderClaudeContentBlocks('User', readString(record.timestamp), message?.content ?? record.content, options);
    }
    if (type === 'assistant') {
        return renderClaudeContentBlocks('Claude', readString(record.timestamp), message?.content ?? record.content, options);
    }
    return null;
}
function renderClaudeContentBlocks(label, timestamp, value, options) {
    if (!Array.isArray(value)) {
        return renderClaudeContentItem(label, timestamp, value, options);
    }
    const blocks = [];
    const textParts = [];
    const flushText = () => {
        const block = formatBlock(label, timestamp, textParts.join('\n'));
        textParts.length = 0;
        if (block) {
            blocks.push(block);
        }
    };
    for (const item of value) {
        const record = readRecord(item);
        const type = readString(record?.type);
        if (isClaudeHiddenContentType(type)) {
            continue;
        }
        if (type === 'tool_use') {
            flushText();
            if (!options.includeTools) {
                continue;
            }
            const name = readString(record?.name) || 'tool';
            const block = formatBlock(`Tool call: ${name}`, timestamp, stringifyForTranscript(record?.input));
            if (block) {
                blocks.push(block);
            }
            continue;
        }
        if (type === 'tool_result') {
            flushText();
            if (!options.includeTools) {
                continue;
            }
            const block = formatBlock('Tool output', timestamp, extractContentText(record?.content));
            if (block) {
                blocks.push(block);
            }
            continue;
        }
        const text = extractContentText(item);
        if (text) {
            textParts.push(text);
        }
    }
    flushText();
    return blocks.length > 0 ? blocks.join('\n\n') : null;
}
function renderClaudeContentItem(label, timestamp, value, options) {
    const record = readRecord(value);
    const type = readString(record?.type);
    if (isClaudeHiddenContentType(type)) {
        return null;
    }
    if (type === 'tool_use') {
        if (!options.includeTools) {
            return null;
        }
        const name = readString(record?.name) || 'tool';
        return formatBlock(`Tool call: ${name}`, timestamp, stringifyForTranscript(record?.input));
    }
    if (type === 'tool_result') {
        if (!options.includeTools) {
            return null;
        }
        return formatBlock('Tool output', timestamp, extractContentText(record?.content));
    }
    return formatBlock(label, timestamp, extractContentText(value));
}
function formatBlock(label, timestamp, body) {
    const cleanBody = sanitizeTranscriptText(body).trimEnd();
    if (!cleanBody) {
        return null;
    }
    const header = timestamp ? `[${timestamp}] ${label}` : label;
    return `${header}\n${cleanBody}`;
}
function extractContentText(value) {
    if (typeof value === 'string') {
        return value;
    }
    if (Array.isArray(value)) {
        const parts = [];
        for (const item of value) {
            const text = extractContentText(item);
            if (text) {
                parts.push(text);
            }
        }
        return parts.join('\n');
    }
    const record = readRecord(value);
    if (!record) {
        return stringifyForTranscript(value);
    }
    const type = readString(record.type);
    if (isClaudeHiddenContentType(type)) {
        return '';
    }
    const directText = readString(record.text);
    if (directText) {
        return directText;
    }
    if (type === 'tool_use') {
        const name = readString(record.name) || 'tool';
        return `[tool call: ${name}]\n${stringifyForTranscript(record.input)}`;
    }
    if (type === 'tool_result') {
        return `[tool output]\n${extractContentText(record.content)}`;
    }
    if (record.content !== undefined) {
        return extractContentText(record.content);
    }
    return stringifyForTranscript(value);
}
function isClaudeHiddenContentType(type) {
    return type === 'thinking' || type === 'redacted_thinking';
}
function stringifyForTranscript(value) {
    if (typeof value === 'string') {
        const trimmed = value.trim();
        if ((trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
            try {
                return JSON.stringify(JSON.parse(trimmed), null, 2);
            }
            catch {
                return value;
            }
        }
        return value;
    }
    if (value === undefined || value === null) {
        return '';
    }
    try {
        return JSON.stringify(value, null, 2);
    }
    catch {
        return String(value);
    }
}
function sanitizeTranscriptText(value) {
    return value
        .replace(/\r\n/g, '\n')
        .replace(/\r/g, '\n')
        .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
        .replace(/\x1b[()][A-Za-z0-9]/g, '')
        .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, '');
}
function normalizeRecordLimit(value) {
    if (value === undefined || !Number.isInteger(value) || value <= 0) {
        return DEFAULT_RECORD_LIMIT;
    }
    return Math.min(value, MAX_RECORD_LIMIT);
}
function normalizeRenderedLineLimit(value) {
    if (value === undefined || !Number.isInteger(value) || value <= 0) {
        return MAX_RENDERED_LINES;
    }
    return Math.min(value, MAX_RENDERED_LINES);
}
function countLines(value) {
    return Math.max(1, (value.match(/\n/g)?.length ?? 0) + 1);
}
function truncate(value, maxChars) {
    if (value.length <= maxChars) {
        return value;
    }
    return `${value.slice(0, maxChars)}\n[truncated]`;
}
function parseJsonLine(line) {
    try {
        return readRecord(JSON.parse(line));
    }
    catch {
        return null;
    }
}
function readRecord(value) {
    return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
}
function readString(value) {
    return typeof value === 'string' ? value : '';
}
function providerLabel(provider) {
    return provider === 'codex' ? 'Codex' : 'Claude';
}
function encodeCursor(source, beforeOffset) {
    const cursor = {
        v: 1,
        provider: source.provider,
        sessionId: source.sessionId,
        filePath: source.filePath,
        beforeOffset
    };
    return Buffer.from(JSON.stringify(cursor), 'utf8').toString('base64url');
}
function decodeCursor(value, source) {
    try {
        const parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
        const beforeOffset = typeof parsed.beforeOffset === 'number' ? parsed.beforeOffset : null;
        if (parsed.v !== 1 ||
            parsed.provider !== source.provider ||
            parsed.sessionId !== source.sessionId ||
            parsed.filePath !== source.filePath ||
            beforeOffset === null ||
            !Number.isInteger(beforeOffset) ||
            beforeOffset < 0) {
            return null;
        }
        return {
            v: 1,
            provider: parsed.provider,
            sessionId: parsed.sessionId,
            filePath: parsed.filePath,
            beforeOffset
        };
    }
    catch {
        return null;
    }
}
//# sourceMappingURL=transcriptHistory.js.map