import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { resolveFridayNextConfig } from "../config.js";
import { getHostOpenClawConfigSnapshot } from "../host-config.js";
import { getFridayNextRuntime } from "../runtime.js";
function currentHistoryDir() {
    const cfg = resolveFridayNextConfig(getHostOpenClawConfigSnapshot(getFridayNextRuntime().config));
    return cfg.historyDir;
}
function ensureDir(dir) {
    fs.mkdirSync(dir, { recursive: true });
}
function sha256(value) {
    return crypto.createHash("sha256").update(value).digest("hex");
}
function safeSegment(value) {
    const trimmed = value.trim().toLowerCase();
    const slug = trimmed
        .replace(/[^a-z0-9._-]+/gi, "-")
        .replace(/-+/g, "-")
        .replace(/^[-._]+|[-._]+$/g, "");
    if (slug && slug.length <= 96)
        return slug;
    return `s-${sha256(value).slice(0, 32)}`;
}
export function sessionIdFromSessionKey(sessionKey, accountId) {
    const parts = sessionKey.trim().split(":").filter(Boolean);
    if (parts.length >= 4 && parts[0] === "friday" && parts[1] === "account") {
        if (!accountId || parts[2] === accountId) {
            return safeSegment(parts.slice(3).join("-"));
        }
    }
    if (sessionKey.startsWith("agent:main:friday:account:")) {
        const rest = sessionKey.slice("agent:main:friday:account:".length);
        const parts2 = rest.split(":").filter(Boolean);
        if (parts2.length >= 2 && (!accountId || parts2[0] === accountId)) {
            return safeSegment(parts2.slice(1).join("-"));
        }
    }
    return `legacy-${sha256(sessionKey).slice(0, 32)}`;
}
function accountDir(accountId, historyDir = currentHistoryDir()) {
    return path.join(historyDir, "accounts", safeSegment(accountId));
}
function sessionsDir(accountId, historyDir = currentHistoryDir()) {
    return path.join(accountDir(accountId, historyDir), "sessions");
}
function sessionFile(accountId, sessionKey, historyDir = currentHistoryDir()) {
    return path.join(sessionsDir(accountId, historyDir), `${sessionIdFromSessionKey(sessionKey, accountId)}.jsonl`);
}
function sessionsIndexPath(accountId, historyDir = currentHistoryDir()) {
    return path.join(accountDir(accountId, historyDir), "sessions.json");
}
function readJsonFile(file, fallback) {
    try {
        return JSON.parse(fs.readFileSync(file, "utf8"));
    }
    catch {
        return fallback;
    }
}
function writeJsonFile(file, value) {
    ensureDir(path.dirname(file));
    fs.writeFileSync(file, JSON.stringify(value, null, 2), "utf8");
}
function readEventsFromFile(file) {
    try {
        const raw = fs.readFileSync(file, "utf8");
        const events = [];
        for (const line of raw.split(/\n/)) {
            const trimmed = line.trim();
            if (!trimmed)
                continue;
            try {
                const parsed = JSON.parse(trimmed);
                if (parsed?.v === 1 && typeof parsed.seq === "number")
                    events.push(parsed);
            }
            catch {
                // Ignore corrupt tail lines; append-only files may be truncated by crashes.
            }
        }
        events.sort((a, b) => a.seq - b.seq);
        return events;
    }
    catch {
        return [];
    }
}
function deriveTitle(event) {
    if (event.kind !== "user_message")
        return undefined;
    const text = typeof event.payload.text === "string" ? event.payload.text.trim() : "";
    if (!text)
        return undefined;
    return text.replace(/\s+/g, " ").slice(0, 80);
}
function upsertSessionSummary(accountId, sessionKey, events, historyDir) {
    if (events.length === 0)
        return;
    const indexFile = sessionsIndexPath(accountId, historyDir);
    const index = readJsonFile(indexFile, { sessions: [] });
    const sessionId = sessionIdFromSessionKey(sessionKey, accountId);
    const existingIdx = index.sessions.findIndex((s) => s.sessionId === sessionId || s.sessionKey === sessionKey);
    const first = events[0];
    const last = events[events.length - 1];
    const messageCount = events.filter((e) => e.kind === "user_message" || e.kind === "assistant_message").length;
    const title = events.map(deriveTitle).find((v) => typeof v === "string" && v.length > 0);
    const next = {
        sessionKey,
        sessionId,
        title: title ?? (existingIdx >= 0 ? index.sessions[existingIdx]?.title : undefined),
        createdAt: existingIdx >= 0 ? index.sessions[existingIdx]?.createdAt ?? first.ts : first.ts,
        lastActiveAt: last.ts,
        lastSeq: last.seq,
        messageCount,
    };
    if (existingIdx >= 0) {
        index.sessions[existingIdx] = next;
    }
    else {
        index.sessions.push(next);
    }
    index.sessions.sort((a, b) => b.lastActiveAt - a.lastActiveAt);
    writeJsonFile(indexFile, index);
}
function eventDedupeKey(event) {
    if (typeof event.eventId === "string" && event.eventId.trim())
        return `event:${event.eventId.trim()}`;
    const payloadId = event.payload && typeof event.payload.id === "string" && event.payload.id.trim()
        ? event.payload.id.trim()
        : "";
    if (payloadId)
        return `payload:${event.kind}:${payloadId}`;
    return "";
}
export class TranscriptStore {
    historyDir;
    constructor(historyDir = currentHistoryDir()) {
        this.historyDir = historyDir;
    }
    appendEvent(accountId, sessionKey, input) {
        return this.appendEvents(accountId, sessionKey, [input])[0];
    }
    appendEvents(accountId, sessionKey, inputs) {
        const cleanAccountId = accountId.trim();
        const cleanSessionKey = sessionKey.trim();
        if (!cleanAccountId)
            throw new Error("Missing accountId");
        if (!cleanSessionKey)
            throw new Error("Missing sessionKey");
        if (inputs.length === 0)
            return [];
        const file = sessionFile(cleanAccountId, cleanSessionKey, this.historyDir);
        ensureDir(path.dirname(file));
        const existing = readEventsFromFile(file);
        const dedupe = new Set(existing.map(eventDedupeKey).filter(Boolean));
        let nextSeq = existing.length > 0 ? Math.max(...existing.map((e) => e.seq)) + 1 : 1;
        const appended = [];
        for (const input of inputs) {
            const key = eventDedupeKey(input);
            if (key && dedupe.has(key))
                continue;
            const event = {
                v: 1,
                seq: nextSeq,
                ts: typeof input.ts === "number" && Number.isFinite(input.ts) ? Math.floor(input.ts) : Date.now(),
                sessionKey: cleanSessionKey,
                clientId: input.clientId.trim() || "unknown",
                kind: input.kind,
                payload: input.payload,
                eventId: input.eventId,
                lamportTs: input.lamportTs,
            };
            fs.appendFileSync(file, `${JSON.stringify(event)}\n`, "utf8");
            appended.push(event);
            if (key)
                dedupe.add(key);
            nextSeq += 1;
        }
        if (appended.length > 0) {
            upsertSessionSummary(cleanAccountId, cleanSessionKey, [...existing, ...appended], this.historyDir);
        }
        return appended;
    }
    readSince(accountId, sessionKey, sinceSeq = 0, limit = 200) {
        const file = sessionFile(accountId, sessionKey, this.historyDir);
        const max = Math.max(1, Math.min(1000, Math.floor(limit)));
        return readEventsFromFile(file).filter((e) => e.seq > sinceSeq).slice(0, max);
    }
    listSessions(accountId) {
        const index = readJsonFile(sessionsIndexPath(accountId, this.historyDir), { sessions: [] });
        return [...index.sessions].sort((a, b) => b.lastActiveAt - a.lastActiveAt);
    }
    compact(accountId, sessionKey, keepLast) {
        const file = sessionFile(accountId, sessionKey, this.historyDir);
        const events = readEventsFromFile(file);
        const keep = Math.max(1, Math.floor(keepLast));
        if (events.length <= keep)
            return;
        const kept = events.slice(-keep);
        const summary = {
            v: 1,
            seq: kept[0].seq - 1,
            ts: Date.now(),
            sessionKey,
            clientId: "server",
            kind: "summary",
            payload: {
                compactedBeforeSeq: kept[0].seq,
                droppedEvents: events.length - kept.length,
            },
        };
        fs.writeFileSync(file, `${JSON.stringify(summary)}\n${kept.map((e) => JSON.stringify(e)).join("\n")}\n`, "utf8");
        upsertSessionSummary(accountId, sessionKey, [summary, ...kept], this.historyDir);
    }
}
export function getTranscriptStore() {
    return new TranscriptStore();
}
