import { hashId } from "./ids.js";
function pad(n) {
    return n.toString().padStart(2, "0");
}
function fmtLocal(d) {
    return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
function formatTimestamp(v) {
    if (v === undefined)
        return "????-??-?? ??:??";
    const d = new Date(v);
    return Number.isNaN(d.getTime()) ? "????-??-?? ??:??" : fmtLocal(d);
}
function formatRecallTimestamp(...values) {
    for (const v of values) {
        if (v === undefined)
            continue;
        const d = new Date(v);
        if (!Number.isNaN(d.getTime()))
            return fmtLocal(d);
    }
    return "Unknown time";
}
function textAndPlaceholders(content, options = {}) {
    if (typeof content === "string")
        return content;
    if (!Array.isArray(content))
        return "[non-text content omitted]";
    const parts = [];
    for (const block of content) {
        if (!block || typeof block !== "object") {
            parts.push("[non-text content omitted]");
            continue;
        }
        if (block.type === "text" && typeof block.text === "string") {
            parts.push(block.text);
            continue;
        }
        if (block.type === "thinking") {
            if (options.omitRedactedThinking && block.redacted === true)
                continue;
            if (options.includeThinking && typeof block.thinking === "string") {
                parts.push(`[thinking: ${block.thinking}]`);
                continue;
            }
            parts.push("[non-text content omitted]");
            continue;
        }
        if (block.type === "toolCall" && typeof block.name === "string") {
            parts.push(`[${block.name}(${JSON.stringify(block.arguments ?? {})})]`);
            continue;
        }
        parts.push("[non-text content omitted]");
    }
    return parts.join("\n");
}
function textOnly(content) {
    if (content == null)
        return "";
    if (typeof content === "string")
        return content;
    if (!Array.isArray(content))
        return "";
    return content
        .filter((b) => b?.type === "text" && typeof b.text === "string")
        .map((b) => b.text)
        .join("\n");
}
export function serializeConversation(messages) {
    return messages
        .map((msg) => {
        const time = formatTimestamp(msg.timestamp);
        if (msg.role === "user") {
            const text = textOnly(msg.content);
            return `[User @ ${time}]: ${text}`;
        }
        if (msg.role === "assistant") {
            const body = textAndPlaceholders(msg.content, {
                includeThinking: true,
                omitRedactedThinking: true,
            })
                .split("\n")
                .filter(Boolean)
                .join("\n");
            if (!body)
                return null;
            return `[Assistant @ ${time}]: ${body}`;
        }
        const text = textOnly(msg.content);
        return `[Tool result for ${msg.toolName} @ ${time}]: ${text}`;
    })
        .filter((line) => line !== null)
        .join("\n\n");
}
export function nowTimestamp() {
    return fmtLocal(new Date());
}
export const MAX_RECORD_CONTENT_CHARS = 10_000;
export function truncateRecordContent(content) {
    if (content.length <= MAX_RECORD_CONTENT_CHARS)
        return content;
    const head = content.slice(0, MAX_RECORD_CONTENT_CHARS);
    const dropped = content.length - MAX_RECORD_CONTENT_CHARS;
    return `${head} … [truncated ${dropped} chars]`;
}
function renderCustomMessage(entry, options) {
    const time = options.recallFormat ? formatRecallTimestamp(entry.timestamp) : formatTimestamp(entry.timestamp);
    const text = options.recallFormat
        ? textAndPlaceholders(entry.content)
        : typeof entry.content === "string"
            ? entry.content
            : Array.isArray(entry.content)
                ? entry.content
                    .filter((b) => b?.type === "text" && typeof b.text === "string")
                    .map((b) => b.text)
                    .join("\n")
                : "";
    if (options.recallFormat) {
        const origin = entry.customType ? `Custom message (${entry.customType})` : "Custom message";
        return `[${origin} @ ${time}]: ${text}`;
    }
    const tag = entry.customType ? `Custom (${entry.customType})` : "Custom";
    return `[${tag} @ ${time}]: ${text}`;
}
export function serializeBranchEntries(entries) {
    const blocks = [];
    for (const entry of entries) {
        if (entry.type === "message" && entry.message) {
            const part = serializeConversation([entry.message]);
            if (part)
                blocks.push(part);
            continue;
        }
        if (entry.type === "custom_message") {
            blocks.push(renderCustomMessage(entry, { recallFormat: false }));
            continue;
        }
        if (entry.type === "branch_summary" && typeof entry.summary === "string") {
            const time = formatTimestamp(entry.timestamp);
            blocks.push(`[Branch summary @ ${time}]: ${entry.summary}`);
        }
    }
    return blocks.join("\n\n");
}
export const DURABLE_SOURCE_ENTRY_ID_PREFIX = "srcv1:";
function isSourceRenderableEntry(entry) {
    return entry.type === "message" || entry.type === "custom_message" || entry.type === "branch_summary";
}
function sourceTimestampSignature(entry) {
    if (entry.type === "message" && entry.message && typeof entry.message === "object") {
        const msg = entry.message;
        if (msg.timestamp !== undefined)
            return String(msg.timestamp);
    }
    if (entry.timestamp !== undefined)
        return String(entry.timestamp);
    return "";
}
export function durableSourceEntryId(entry) {
    if (!isSourceRenderableEntry(entry))
        return undefined;
    const rendered = serializeBranchEntries([entry]);
    if (!rendered.trim())
        return undefined;
    const fingerprint = [
        "source-v1",
        `type:${entry.type}`,
        `customType:${entry.customType ?? ""}`,
        `timestamp:${sourceTimestampSignature(entry)}`,
        rendered,
    ].join("\n");
    return `${DURABLE_SOURCE_ENTRY_ID_PREFIX}${hashId(fingerprint)}`;
}
export function sourceEntryReferenceIds(entry) {
    const refs = [];
    if (entry.id)
        refs.push(entry.id);
    const durableId = durableSourceEntryId(entry);
    if (durableId && durableId !== entry.id)
        refs.push(durableId);
    return refs;
}
export function serializeSourceAddressedBranchEntries(entries) {
    const blocks = [];
    const sourceEntryIds = [];
    for (const entry of entries) {
        if (!entry.id || !isSourceRenderableEntry(entry))
            continue;
        const rendered = serializeBranchEntries([entry]);
        if (!rendered.trim())
            continue;
        const sourceEntryId = durableSourceEntryId(entry) ?? entry.id;
        sourceEntryIds.push(sourceEntryId);
        blocks.push(`[Source entry id: ${sourceEntryId}]\n${rendered}`);
    }
    return { text: blocks.join("\n\n"), sourceEntryIds };
}
function renderRecallMessage(entry) {
    if (!entry.message || typeof entry.message !== "object")
        return null;
    const msg = entry.message;
    const time = formatRecallTimestamp(msg.timestamp, entry.timestamp);
    if (msg.role === "user") {
        return `[User @ ${time}]: ${textAndPlaceholders(msg.content)}`;
    }
    if (msg.role === "assistant") {
        const body = textAndPlaceholders(msg.content, {
            includeThinking: true,
            omitRedactedThinking: true,
        })
            .split("\n")
            .filter(Boolean)
            .join("\n");
        if (!body)
            return null;
        return `[Assistant @ ${time}]: ${body}`;
    }
    return `[Tool result: ${msg.toolName} @ ${time}]: ${textAndPlaceholders(msg.content)}`;
}
export function renderRecallSourceEntry(entry) {
    if (entry.type === "message")
        return renderRecallMessage(entry);
    if (entry.type === "custom_message")
        return renderCustomMessage(entry, { recallFormat: true });
    if (entry.type === "branch_summary" && typeof entry.summary === "string") {
        const time = formatRecallTimestamp(entry.timestamp);
        return `[Branch summary @ ${time}]: ${entry.summary}`;
    }
    return null;
}
export function renderRecallSourceEntries(entries) {
    return entries
        .map(renderRecallSourceEntry)
        .filter((block) => block !== null && block.trim().length > 0)
        .join("\n\n");
}
