/**
 * Node-only file I/O and migration helpers for session JSONL files.
 *
 * Split out from session-schema.ts so the pure schema is browser-safe — the
 * embedded runtime imports only the schema, never these helpers.
 */
import { randomUUID } from 'crypto';
import { closeSync, existsSync, openSync, readdirSync, readFileSync, readSync, statSync } from 'fs';
import { join } from 'path';
import { CURRENT_SESSION_VERSION, parseSessionEntries } from '../../core/session-schema.js';
/** Generate a unique short ID (8 hex chars, collision-checked) */
function generateId(byId) {
    for (let i = 0; i < 100; i++) {
        const id = randomUUID().slice(0, 8);
        if (!byId.has(id))
            return id;
    }
    // Fallback to full UUID if somehow we have collisions
    return randomUUID();
}
/** Migrate v1 → v2: add id/parentId tree structure. Mutates in place. */
function migrateV1ToV2(entries) {
    const ids = new Set();
    let prevId = null;
    for (const entry of entries) {
        if (entry.type === 'session') {
            entry.version = 2;
            continue;
        }
        entry.id = generateId(ids);
        entry.parentId = prevId;
        prevId = entry.id;
        // Convert firstKeptEntryIndex to firstKeptEntryId for compaction
        if (entry.type === 'compaction') {
            const comp = entry;
            if (typeof comp.firstKeptEntryIndex === 'number') {
                const targetEntry = entries[comp.firstKeptEntryIndex];
                if (targetEntry && targetEntry.type !== 'session') {
                    comp.firstKeptEntryId = targetEntry.id;
                }
                delete comp.firstKeptEntryIndex;
            }
        }
    }
}
/** Migrate v2 → v3: rename hookMessage role to custom. Mutates in place. */
function migrateV2ToV3(entries) {
    for (const entry of entries) {
        if (entry.type === 'session') {
            entry.version = 3;
            continue;
        }
        // Update message entries with hookMessage role
        if (entry.type === 'message') {
            const msgEntry = entry;
            if (msgEntry.message && msgEntry.message.role === 'hookMessage') {
                msgEntry.message.role = 'custom';
            }
        }
    }
}
/**
 * Run all necessary migrations to bring entries to current version.
 * Mutates entries in place. Returns true if any migration was applied.
 */
function migrateToCurrentVersion(entries) {
    const header = entries.find((e) => e.type === 'session');
    const version = header?.version ?? 1;
    if (version >= CURRENT_SESSION_VERSION)
        return false;
    if (version < 2)
        migrateV1ToV2(entries);
    if (version < 3)
        migrateV2ToV3(entries);
    return true;
}
/** Exported for testing */
export function migrateSessionEntries(entries) {
    migrateToCurrentVersion(entries);
}
/** Exported for testing */
export function loadEntriesFromFile(filePath) {
    if (!existsSync(filePath))
        return [];
    const content = readFileSync(filePath, 'utf8');
    const entries = parseSessionEntries(content);
    // Validate session header
    if (entries.length === 0)
        return entries;
    const header = entries[0];
    if (header.type !== 'session' || typeof header.id !== 'string') {
        return [];
    }
    return entries;
}
function isValidSessionFile(filePath) {
    try {
        const fd = openSync(filePath, 'r');
        const buffer = Buffer.alloc(512);
        const bytesRead = readSync(fd, buffer, 0, 512, 0);
        closeSync(fd);
        const firstLine = buffer.toString('utf8', 0, bytesRead).split('\n')[0];
        if (!firstLine)
            return false;
        const header = JSON.parse(firstLine);
        return header.type === 'session' && typeof header.id === 'string';
    }
    catch {
        return false;
    }
}
/** Exported for testing */
export function findMostRecentSession(sessionDir) {
    try {
        const files = readdirSync(sessionDir)
            .filter((f) => f.endsWith('.jsonl'))
            .map((f) => join(sessionDir, f))
            .filter(isValidSessionFile)
            .map((path) => ({ path, mtime: statSync(path).mtime }))
            .sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
        return files[0]?.path || null;
    }
    catch {
        return null;
    }
}
//# sourceMappingURL=session-schema-node-io.js.map