import { Type } from "../../sdk/ai/index.js";
import { defineTool } from "../../sdk/coding-agent/index.js";
import { recallMemorySources, } from "../branch.js";
import { renderRecallSourceEntries, renderRecallSourceEntry } from "../serialize.js";
import { estimateEntryTokens } from "../tokens.js";
export const RECALL_OBSERVATION_TOOL_NAME = "recall";
const MEMORY_ID_PATTERN = /^[a-f0-9]{12}$/;
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 formatDisplayTimestamp(...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 textContentBlocks(content) {
    return Array.isArray(content) ? content.filter((block) => !!block && typeof block === "object") : [];
}
function uniqueStrings(items) {
    return Array.from(new Set(items));
}
function sourceOriginAndQualifiers(entry) {
    if (entry.type === "message" && entry.message && typeof entry.message === "object") {
        const msg = entry.message;
        const timestamp = formatDisplayTimestamp(msg.timestamp, entry.timestamp);
        if (msg.role === "user")
            return { origin: "User", timestamp, qualifiers: [] };
        if (msg.role === "assistant") {
            const toolCalls = uniqueStrings(textContentBlocks(msg.content)
                .filter((block) => block.type === "toolCall" && typeof block.name === "string")
                .map((block) => block.name));
            return {
                origin: "Assistant",
                timestamp,
                qualifiers: toolCalls.length > 0 ? [`tool calls: ${toolCalls.join(", ")}`] : [],
            };
        }
        const toolName = msg.toolName;
        return { origin: `Tool result: ${typeof toolName === "string" && toolName ? toolName : "unknown"}`, timestamp, qualifiers: [] };
    }
    if (entry.type === "custom_message") {
        return {
            origin: "Custom message",
            timestamp: formatDisplayTimestamp(entry.timestamp),
            qualifiers: typeof entry.customType === "string" && entry.customType ? [`custom: ${entry.customType}`] : [],
        };
    }
    if (entry.type === "branch_summary") {
        return { origin: "Branch summary", timestamp: formatDisplayTimestamp(entry.timestamp), qualifiers: [] };
    }
    return { origin: entry.type || "Entry", timestamp: formatDisplayTimestamp(entry.timestamp), qualifiers: [] };
}
function renderSourceEntryContentOnly(entry) {
    const rendered = renderRecallSourceEntry(entry);
    return rendered?.replace(/^\[[^\]]+\]:\s?/, "") || undefined;
}
function sourceEntryDetails(entry, includeContent) {
    const { origin, timestamp, qualifiers } = sourceOriginAndQualifiers(entry);
    const content = renderSourceEntryContentOnly(entry);
    return {
        id: entry.id,
        origin,
        timestamp,
        tokens: estimateEntryTokens(entry),
        qualifiers,
        ...(includeContent && content ? { content } : {}),
    };
}
function observationDetails(observation) {
    return {
        id: observation.id,
        content: observation.content,
        timestamp: observation.timestamp,
        relevance: observation.relevance,
    };
}
function reflectionDetails(reflection, reflectionIndex) {
    return {
        id: reflection.id,
        content: reflection.content,
        supportingObservationIds: reflection.supportingObservationIds,
        ...(reflection.legacy === true ? { legacy: true } : {}),
        reflectionIndex,
    };
}
function observationMatchDetails(match, includeSourceContent = true) {
    if (match.status === "ok") {
        return {
            status: "ok",
            observationEntryId: match.observationEntryId,
            observationRecordIndex: match.observationRecordIndex,
            observation: observationDetails(match.observation),
            sourceEntryIds: match.sourceEntryIds,
            sourceEntries: match.sourceEntries.map((entry) => sourceEntryDetails(entry, includeSourceContent)),
            sourceCharacterCount: renderRecallSourceEntries(match.sourceEntries).length,
        };
    }
    if (match.status === "source_unavailable") {
        return {
            status: "source_unavailable",
            observationEntryId: match.observationEntryId,
            observationRecordIndex: match.observationRecordIndex,
            observation: observationDetails(match.observation),
            sourceEntryIds: match.sourceEntryIds,
            ...(includeSourceContent
                ? {
                    sourceEntries: match.sourceEntries.map((entry) => sourceEntryDetails(entry, true)),
                    sourceCharacterCount: renderRecallSourceEntries(match.sourceEntries).length,
                }
                : {}),
            missingSourceEntryIds: match.missingSourceEntryIds,
            nonSourceEntryIds: match.nonSourceEntryIds,
        };
    }
    return {
        status: "no_source",
        observationEntryId: match.observationEntryId,
        observationRecordIndex: match.observationRecordIndex,
        observation: observationDetails(match.observation),
    };
}
function textResult(text, details) {
    return {
        content: [{ type: "text", text }],
        details,
    };
}
function emptyDetails(status, memoryId, message) {
    return {
        status,
        memoryId,
        observationId: memoryId,
        collision: false,
        partial: false,
        reflections: [],
        directObservationMatches: [],
        observations: [],
        matches: [],
        sourceEntries: [],
        unavailableSupportingObservations: [],
        unavailableReflectionProvenance: [],
        missingSourceEntryIds: [],
        nonSourceEntryIds: [],
        message,
    };
}
function aggregateStatus(details) {
    const observationOnly = details.reflections.length === 0 && details.unavailableSupportingObservations.length === 0 && details.unavailableReflectionProvenance.length === 0;
    if (observationOnly && details.observations.some((match) => match.status === "ok"))
        return "ok";
    if (observationOnly && details.observations.some((match) => match.status === "source_unavailable"))
        return "source_unavailable";
    if (observationOnly && details.observations.length > 0)
        return "no_source";
    if (details.unavailableReflectionProvenance.length > 0 && details.observations.length === 0 && details.sourceEntries.length === 0)
        return "no_provenance";
    if (details.partial)
        return "partial";
    if (details.sourceEntries.length > 0)
        return "ok";
    if (details.reflections.length > 0)
        return "ok";
    if (details.observations.length > 0)
        return "no_source";
    return "not_found";
}
function friendlyNoSourceMessage(memoryId) {
    return `Observation ${memoryId} has no source entries associated with it. This can happen for legacy observations created before source recall was available.`;
}
function friendlySourceUnavailableMessage(match) {
    const missing = match.missingSourceEntryIds && match.missingSourceEntryIds.length > 0 ? ` missing: ${match.missingSourceEntryIds.join(", ")}` : "";
    const nonSource = match.nonSourceEntryIds && match.nonSourceEntryIds.length > 0 ? ` non-source: ${match.nonSourceEntryIds.join(", ")}` : "";
    return `Observation ${match.observation.id} has source entries associated, but some are unavailable on the current branch or are not source-renderable.${missing}${nonSource}`;
}
function reflectionLineText(reflection) {
    return `[\`${reflection.id}\`] ${reflection.content}`;
}
function observationLineText(observation) {
    return `[\`${observation.id}\`] ${observation.timestamp} [${observation.relevance}] ${observation.content}`;
}
function renderObservationOnlyTextFromResult(result) {
    const sections = [];
    if (result.collision) {
        sections.push(`Multiple observations share id ${result.memoryId}; returning all matching source results from the current branch.`);
    }
    for (const match of result.directObservationMatches) {
        if (match.status === "ok") {
            const sourceText = renderRecallSourceEntries(match.sourceEntries);
            if (sourceText.trim())
                sections.push(sourceText);
            else
                sections.push(`Observation ${match.observation.id} has source entries associated, but they rendered no text content.`);
            continue;
        }
        if (match.status === "source_unavailable") {
            sections.push(friendlySourceUnavailableMessage(observationMatchDetails(match, false)));
            continue;
        }
        sections.push(friendlyNoSourceMessage(match.observation.id));
    }
    return sections.join("\n\n");
}
function unavailableSupportingLineText(item) {
    return `Supporting observation ${item.observationId} for reflection ${item.reflectionId} is unavailable on the current branch.`;
}
function unavailableReflectionProvenanceLineText(item) {
    return `Reflection ${item.reflectionId} was migrated from legacy memory created before reflection provenance was recorded, so no supporting observations or raw sources are available.`;
}
function unavailableObservationSourceLineText(match) {
    return `Observation ${match.observation.id} has no source entries associated. This can happen for legacy observations created before source recall was available.`;
}
function renderMemoryText(result) {
    const sections = [];
    if (result.collision) {
        sections.push(`Memory id ${result.memoryId} matched multiple observations/reflections; returning all available evidence from the current branch.`);
    }
    if (result.reflectionMatches.length > 0) {
        sections.push(`Reflections:\n${result.reflectionMatches.map((match) => reflectionLineText(reflectionDetails(match.reflection, match.reflectionIndex))).join("\n")}`);
    }
    if (result.observations.length > 0) {
        sections.push(`Observations:\n${result.observations.map((match) => observationLineText(match.observation)).join("\n")}`);
    }
    if (result.unavailableSupportingObservations.length > 0) {
        sections.push(`Unavailable supporting observations:\n${result.unavailableSupportingObservations
            .map((item) => unavailableSupportingLineText({
            reflectionId: item.reflection.id,
            reflectionIndex: item.reflectionIndex,
            observationId: item.observationId,
        }))
            .join("\n")}`);
    }
    if (result.unavailableReflectionProvenance.length > 0) {
        sections.push(`Unavailable reflection provenance:\n${result.unavailableReflectionProvenance
            .map((item) => unavailableReflectionProvenanceLineText({
            reflectionId: item.reflection.id,
            reflectionIndex: item.reflectionIndex,
            reason: item.reason,
        }))
            .join("\n")}`);
    }
    const noSourceObservations = result.observations.filter((match) => match.status === "no_source");
    if (noSourceObservations.length > 0) {
        sections.push(`Unavailable observation sources:\n${noSourceObservations.map(unavailableObservationSourceLineText).join("\n")}`);
    }
    if (result.missingSourceEntryIds.length > 0 || result.nonSourceEntryIds.length > 0) {
        const parts = [];
        if (result.missingSourceEntryIds.length > 0)
            parts.push(`missing: ${result.missingSourceEntryIds.join(", ")}`);
        if (result.nonSourceEntryIds.length > 0)
            parts.push(`non-source: ${result.nonSourceEntryIds.join(", ")}`);
        sections.push(`Unavailable source entries: ${parts.join("; ")}`);
    }
    const sourceText = renderRecallSourceEntries(result.sourceEntries);
    if (sourceText.trim())
        sections.push(`Sources:\n${sourceText}`);
    if (sections.length === 0)
        sections.push(`Memory ${result.memoryId} was found, but no source evidence rendered.`);
    return sections.join("\n\n");
}
function resultDetails(result, includeSourceContent = true) {
    const reflections = result.reflectionMatches.map((match) => reflectionDetails(match.reflection, match.reflectionIndex));
    const memoryLayerRecall = result.reflectionMatches.length > 0 || result.unavailableSupportingObservations.length > 0;
    const includeObservationSources = (_match) => includeSourceContent;
    const observations = result.observations.map((match) => observationMatchDetails(match, includeObservationSources(match)));
    const directObservationMatches = result.directObservationMatches.map((match) => observationMatchDetails(match, includeObservationSources(match)));
    const sourceEntries = memoryLayerRecall ? result.sourceEntries.map((entry) => sourceEntryDetails(entry, includeSourceContent)) : [];
    const unavailableSupportingObservations = result.unavailableSupportingObservations.map((item) => ({
        reflectionId: item.reflection.id,
        reflectionIndex: item.reflectionIndex,
        observationId: item.observationId,
    }));
    const unavailableReflectionProvenance = result.unavailableReflectionProvenance.map((item) => ({
        reflectionId: item.reflection.id,
        reflectionIndex: item.reflectionIndex,
        reason: item.reason,
    }));
    const partial = result.partial;
    const detailWithoutStatus = {
        memoryId: result.memoryId,
        observationId: result.memoryId,
        collision: result.collision,
        partial,
        reflections,
        directObservationMatches,
        observations,
        matches: directObservationMatches,
        sourceEntries,
        unavailableSupportingObservations,
        unavailableReflectionProvenance,
        missingSourceEntryIds: result.missingSourceEntryIds,
        nonSourceEntryIds: result.nonSourceEntryIds,
        sourceCharacterCount: renderRecallSourceEntries(result.sourceEntries).length,
    };
    return {
        status: aggregateStatus(detailWithoutStatus),
        ...detailWithoutStatus,
    };
}
function isObservationOnly(details) {
    return details.reflections.length === 0 && details.unavailableSupportingObservations.length === 0 && details.unavailableReflectionProvenance.length === 0;
}
function renderFoundResult(result) {
    const details = resultDetails(result);
    const text = isObservationOnly(details) ? renderObservationOnlyTextFromResult(result) : renderMemoryText(result);
    return textResult(text, details);
}
export const recallObservationTool = defineTool({
    name: RECALL_OBSERVATION_TOOL_NAME,
    label: "Recall memory evidence",
    description: "Recover exact evidence and source context behind a compacted observational-memory observation or reflection id on the current branch. " +
        "Use when compressed memory is important and original source context is needed before acting.",
    promptSnippet: "Use recall(<id>) to recover exact source context behind compacted memory observations/reflections when precision matters.",
    promptGuidelines: [
        "Use recall before making an important decision that depends on a compacted observation or reflection whose details are unclear.",
        "Use recall when you need exact wording, rationale, file paths, commands, errors, commits, user constraints, or provenance behind a remembered claim.",
        "Use recall when a broad reflection is relevant but you need its supporting observations or raw sources to continue safely.",
        "Use recall when the user asks why you believe something, what supports a memory, or what was decided earlier.",
        "Do not use recall as semantic search or transcript browsing; you must already have a specific 12-character memory id.",
        "Do not recall every id preemptively. Recall only when exact source context will materially improve the next action.",
    ],
    parameters: Type.Object({
        id: Type.String({
            pattern: "^[a-f0-9]{12}$",
            description: "12-character lowercase hex observation or reflection id shown in compacted memory, /om-view, or a previous recall result. " +
                "Must be a specific id; this tool does not search by topic.",
        }),
    }),
    async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
        const memoryId = params.id;
        if (!MEMORY_ID_PATTERN.test(memoryId)) {
            const message = `Memory id must be 12 lowercase hex characters. Received: ${memoryId}`;
            return textResult(message, emptyDetails("invalid_id", memoryId, message));
        }
        const branchEntries = ctx.sessionManager.getBranch();
        const result = recallMemorySources(branchEntries, memoryId);
        if (result.status === "not_found") {
            const message = `No observation or reflection with id ${memoryId} was found on the current branch.`;
            return textResult(message, emptyDetails("not_found", memoryId, message));
        }
        return renderFoundResult(result);
    },
});
export function registerRecallTool(ext) {
    ext.registerTool(recallObservationTool);
}
