/**
 * File-backed chat session repository.
 *
 * Owns file paths, serialization, deserialization, and orphan-file cleanup for
 * chat sessions. Session services should depend on this class rather than
 * embedding file I/O logic themselves.
 *
 * Current compromise:
 * some older host or test paths still instantiate this repository directly.
 * The intended direction is host -> service -> repository -> files.
 */
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
import { basename, dirname, join } from 'node:path';
import { ChatSessionRecords } from '../records/index.js';
import { ChatSessionCodec } from './chat-session-codec.js';
export class FileChatSessionRepository {
    sessionStoragePath;
    storagePaths;
    constructor(args) {
        this.sessionStoragePath = args.sessionStoragePath;
        this.storagePaths = FileChatSessionRepository.deriveStoragePaths(args.sessionStoragePath);
    }
    list() {
        const resolved = this.loadFromCurrentStorage();
        if (resolved.length > 0) {
            return resolved;
        }
        return [
            ChatSessionRecords.create({
                id: 'session-1',
                name: 'Session 1',
            }),
        ];
    }
    readCatalog() {
        const catalog = FileChatSessionRepository.readCatalogFile(this.deriveStoragePaths().catalogPath);
        return catalog?.sessions.sort(FileChatSessionRepository.compareSessionOrder) ?? [];
    }
    read(sessionId) {
        const paths = this.deriveStoragePaths();
        const catalog = FileChatSessionRepository.readCatalogFile(paths.catalogPath);
        const entry = catalog?.sessions.find((candidate) => candidate.id === sessionId);
        if (!entry) {
            return undefined;
        }
        return FileChatSessionRepository.readSessionFile(paths.sessionsDir, entry)[0];
    }
    save(sessions) {
        mkdirSync(dirname(this.sessionStoragePath), { recursive: true });
        const paths = this.deriveStoragePaths();
        mkdirSync(paths.sessionsDir, { recursive: true });
        const sorted = FileChatSessionRepository.dedupeSessionsById(sessions)
            .sort(FileChatSessionRepository.compareSessionOrder);
        const previousCatalog = FileChatSessionRepository.readCatalogFile(paths.catalogPath);
        const previousSessionBodies = new Map();
        for (const entry of previousCatalog?.sessions ?? []) {
            const body = FileChatSessionRepository.readSessionFileContents(paths.sessionsDir, entry.id);
            if (body !== undefined) {
                previousSessionBodies.set(entry.id, body);
            }
        }
        for (const session of sorted) {
            FileChatSessionRepository.writeSessionFileIfChanged(paths.sessionsDir, session, previousSessionBodies.get(session.id));
        }
        const catalog = {
            version: 1,
            sessions: sorted.map((session) => ChatSessionCodec.projectCatalogEntry(session)),
        };
        FileChatSessionRepository.writeCatalogIfChanged(paths.catalogPath, catalog, previousCatalog);
        FileChatSessionRepository.removeOrphanedSessionFiles(paths.sessionsDir, catalog.sessions.map((session) => session.id));
    }
    deriveStoragePaths() {
        return this.storagePaths;
    }
    static deriveStoragePaths(storagePath) {
        const stateDir = dirname(storagePath);
        const fileName = basename(storagePath);
        const catalogSuffix = '.catalog.json';
        if (fileName.endsWith(catalogSuffix)) {
            const catalogStem = fileName.slice(0, -catalogSuffix.length);
            return {
                // Current layout: preserve the configured catalog path exactly.
                catalogPath: storagePath,
                sessionsDir: join(stateDir, catalogStem),
            };
        }
        return {
            // Non-standard inputs are explicit catalog paths. Preserve them without
            // scanning sibling files or trying to upgrade older layouts.
            catalogPath: storagePath,
            sessionsDir: join(stateDir, fileName),
        };
    }
    loadFromCurrentStorage() {
        return this.loadSessionsFromCatalog(this.deriveStoragePaths());
    }
    loadSessionsFromCatalog(paths) {
        if (!existsSync(paths.catalogPath)) {
            return [];
        }
        try {
            const raw = JSON.parse(readFileSync(paths.catalogPath, 'utf8'));
            const catalog = ChatSessionCodec.parseCatalog(raw);
            if (!catalog) {
                return [];
            }
            const sessions = catalog.sessions.flatMap((entry) => FileChatSessionRepository.readSessionFile(paths.sessionsDir, entry));
            return sessions.sort(FileChatSessionRepository.compareSessionOrder);
        }
        catch (error) {
            process.stderr.write(`Failed to load chat session catalog from ${paths.catalogPath}: ${error instanceof Error ? error.message : String(error)}\n`);
            return [];
        }
    }
    static readSessionFile(sessionsDir, entry) {
        const path = FileChatSessionRepository.sessionFilePath(sessionsDir, entry.id);
        if (!existsSync(path)) {
            return [];
        }
        try {
            const parsed = JSON.parse(readFileSync(path, 'utf8'));
            return ChatSessionCodec.parseSessionBody(parsed, { entry });
        }
        catch (error) {
            process.stderr.write(`Failed to load chat session file ${path}: ${error instanceof Error ? error.message : String(error)}\n`);
            return [];
        }
    }
    static writeSessionFileIfChanged(sessionsDir, session, previousContent) {
        const path = FileChatSessionRepository.sessionFilePath(sessionsDir, session.id);
        const nextContent = ChatSessionCodec.serializeSessionBody(session);
        if (previousContent === nextContent) {
            return;
        }
        writeFileSync(path, nextContent);
    }
    static writeCatalogIfChanged(catalogPath, catalog, previousCatalog) {
        const nextContent = ChatSessionCodec.serializeCatalog(catalog);
        const previousContent = previousCatalog ? ChatSessionCodec.serializeCatalog(previousCatalog) : undefined;
        if (previousContent === nextContent) {
            return;
        }
        writeFileSync(catalogPath, nextContent);
    }
    static readSessionFileContents(sessionsDir, sessionId) {
        const path = FileChatSessionRepository.sessionFilePath(sessionsDir, sessionId);
        if (!existsSync(path)) {
            return undefined;
        }
        try {
            return readFileSync(path, 'utf8');
        }
        catch {
            return undefined;
        }
    }
    static readCatalogFile(catalogPath) {
        if (!existsSync(catalogPath)) {
            return undefined;
        }
        try {
            return ChatSessionCodec.parseCatalog(JSON.parse(readFileSync(catalogPath, 'utf8')));
        }
        catch {
            return undefined;
        }
    }
    static dedupeSessionsById(sessions) {
        const seen = new Set();
        const deduped = [];
        for (const session of sessions) {
            if (seen.has(session.id)) {
                continue;
            }
            seen.add(session.id);
            deduped.push(session);
        }
        return deduped;
    }
    static compareSessionOrder(left, right) {
        if (left.pinned !== right.pinned) {
            return left.pinned ? -1 : 1;
        }
        return right.updatedAt.localeCompare(left.updatedAt);
    }
    static sessionFilePath(sessionsDir, sessionId) {
        return join(sessionsDir, `${sessionId}.json`);
    }
    static removeOrphanedSessionFiles(sessionsDir, activeSessionIds) {
        if (!existsSync(sessionsDir)) {
            return;
        }
        const allowed = new Set(activeSessionIds.map((id) => `${id}.json`));
        for (const name of FileChatSessionRepository.safeReadDirFiles(sessionsDir)) {
            if (allowed.has(name) || !name.endsWith('.json')) {
                continue;
            }
            unlinkSync(join(sessionsDir, name));
        }
    }
    static safeReadDirFiles(path) {
        try {
            return readdirSync(path);
        }
        catch {
            return [];
        }
    }
}
//# sourceMappingURL=file-chat-session-repository.js.map