// Memory maintenance orchestrator (Epic #204) — the impure caller that drives the pure
// consolidation engine (#208) + maintenance planner (governance #209) against the vault (#206).
//
// POST /api/memory/maintenance runs a bounded, synchronous pass:
//   1. Load every memory (all scopes) + the access stats.
//   2. Run consolidation on the accepted subset; persist proposed edges; AUTO-APPLY pairwise
//      supersede review items (a correction superseding one older conflicting memory) by demoting
//      the loser to `superseded`. Multi-way merges are NOT auto-resolved — they stay as review
//      items for the Memory Center.
//   3. Compute the maintenance plan and apply it: promote (-> accepted), reinforce / decay
//      (confidence patch), archive (-> archived), forget (vault delete + tombstone + reason).
//   4. Emit one audit event per applied effect and return the counts.
//
// CSRF: the server dispatch layer enforces x-keiko-csrf for POST, so this route is guarded without
// any per-handler check. External faults (a vault write that throws) are wrapped into a typed 500
// rather than crashing the loopback server.
import { randomUUID } from "node:crypto";
import { runConsolidation, } from "@oscharko-dev/keiko-memory-consolidation";
import { planMemoryMaintenance, } from "@oscharko-dev/keiko-memory-governance";
import { errorBody } from "./routes.js";
import { recordMemoryAudit } from "./memory-audit-handler.js";
function emptyCounts() {
    return {
        promoted: 0,
        reinforced: 0,
        decayed: 0,
        archived: 0,
        forgotten: 0,
        superseded: 0,
        edgesCreated: 0,
        clustersInspected: 0,
    };
}
function isRouteResult(value) {
    return (typeof value === "object" && value !== null && typeof value.status === "number");
}
function resolveVault(deps) {
    if (deps.memoryVault === undefined) {
        return {
            status: 503,
            body: errorBody("MEMORY_UNAVAILABLE", "Memory vault is not configured."),
        };
    }
    return deps.memoryVault;
}
function emitAudit(evidenceStore, kind, surface, summary, extra) {
    if (evidenceStore === undefined)
        return;
    const event = {
        schemaVersion: "1",
        kind,
        eventId: randomUUID(),
        occurredAt: Date.now(),
        initiatorSurface: surface,
        summary,
        ...extra,
    };
    recordMemoryAudit({ evidenceStore }, event);
}
// Patch a record's confidence by rebuilding the provenance envelope (confidence lives there).
function patchConfidence(vault, record, confidence) {
    vault.updateMemory(record.id, { provenance: { ...record.provenance, confidence } }, Date.now());
}
function recordsById(records) {
    const map = new Map();
    for (const record of records)
        map.set(record.id, record);
    return map;
}
// ─── Consolidation effects ───────────────────────────────────────────────────
function applyEdges(vault, edges) {
    let created = 0;
    for (const edge of edges) {
        vault.insertEdge(edge);
        created += 1;
    }
    return created;
}
// A pairwise supersede picks the OLDER memory as the loser: the newer correction wins, the older
// conflicting fact is demoted to `superseded` so retrieval suppresses it. Multi-way merges carry a
// `merge` action and are intentionally left untouched for human review.
function pairwiseSupersede(action) {
    return action?.kind === "supersede" ? action : null;
}
function applySupersessions(vault, evidenceStore, reviewItems, byId) {
    let superseded = 0;
    for (const item of reviewItems) {
        const action = pairwiseSupersede(item.proposedAction);
        if (action === null)
            continue;
        const loser = byId.get(action.older);
        if (loser?.status !== "accepted")
            continue;
        vault.updateMemory(action.older, { status: "superseded" }, Date.now());
        superseded += 1;
        emitSupersedeAudit(evidenceStore, action, loser.scope);
    }
    return superseded;
}
function emitSupersedeAudit(evidenceStore, action, scope) {
    emitAudit(evidenceStore, "memory:superseded", "consolidation", "Auto-superseded an older conflicting memory.", {
        oldMemoryId: action.older,
        newMemoryId: action.newer,
        scope,
    });
}
function runConsolidationPass(vault, evidenceStore, records, counts) {
    const result = runConsolidation(records, {
        nowMs: Date.now(),
        newEdgeId: () => randomUUID(),
        newReviewItemId: () => randomUUID(),
    });
    counts.edgesCreated += applyEdges(vault, result.edgesProposed);
    counts.clustersInspected += result.clustersInspected;
    counts.superseded += applySupersessions(vault, evidenceStore, result.reviewItems, recordsById(records));
}
// ─── Plan application ──────────────────────────────────────────────────────────
// Applies the reinforce / decay / archive / forget effects on the post-consolidation snapshot.
// Promotions are applied SEPARATELY and BEFORE consolidation (see runMemoryMaintenance) so that
// freshly-accepted memories are visible to conflict detection within the same maintenance pass.
function applyDecayEffects(vault, evidenceStore, plan, byId, counts) {
    applyConfidencePatches(vault, plan.reinforce, byId, (n) => (counts.reinforced += n));
    applyConfidencePatches(vault, plan.decay, byId, (n) => (counts.decayed += n));
    applyArchives(vault, evidenceStore, plan.archive, byId, counts);
    applyForgets(vault, evidenceStore, plan.forget, byId, counts);
}
function applyPromotions(vault, evidenceStore, ids, byId, counts) {
    for (const id of ids) {
        const record = byId.get(id);
        if (record === undefined)
            continue;
        vault.updateMemory(id, { status: "accepted" }, Date.now());
        counts.promoted += 1;
        emitAudit(evidenceStore, "memory:accepted", "memory-center", "Promoted a strong proposed memory.", { memoryId: id, scope: record.scope });
    }
}
function applyConfidencePatches(vault, patches, byId, bump) {
    for (const patch of patches) {
        const record = byId.get(patch.id);
        if (record === undefined)
            continue;
        patchConfidence(vault, record, patch.confidence);
        bump(1);
    }
}
function applyArchives(vault, evidenceStore, ids, byId, counts) {
    for (const id of ids) {
        const record = byId.get(id);
        if (record === undefined)
            continue;
        vault.updateMemory(id, { status: "archived" }, Date.now());
        counts.archived += 1;
        emitAudit(evidenceStore, "memory:archived", "retention", "Archived a faded memory.", {
            memoryId: id,
            scope: record.scope,
        });
    }
}
function applyForgets(vault, evidenceStore, forgets, byId, counts) {
    for (const forget of forgets) {
        const record = byId.get(forget.id);
        if (record === undefined)
            continue;
        vault.deleteMemory(forget.id, {
            tombstone: true,
            forgetterSurface: "memory-maintenance",
            reason: forget.reason,
            nowMs: Date.now(),
        });
        counts.forgotten += 1;
        emitAudit(evidenceStore, "memory:forgotten", "retention", `Forgot a memory (${forget.reason}).`, {
            memoryId: forget.id,
            scope: record.scope,
            tombstoned: true,
        });
    }
}
// Reusable maintenance core. Drives consolidation + the governance plan against a vault, emitting
// audit events when an evidence store is supplied. Exported so both the BFF route handler and the
// `keiko memory maintain` CLI run the SAME pass — no duplicated orchestration.
export function runMemoryMaintenance(vault, evidenceStore) {
    const counts = emptyCounts();
    // Phase 1 — promote strong `proposed` memories FIRST. Consolidation and conflict detection only
    // inspect `accepted` records, so without this a vault full of freshly-captured `proposed`
    // memories would need a SECOND maintenance run before any near-duplicate or polarity conflict is
    // resolved. Promoting up front makes a single "Run maintenance" fully effective.
    const beforePromote = vault.listMemories({ includeExpired: true });
    const promoteStats = vault.getAccessStats();
    const promotePlan = planMemoryMaintenance(beforePromote, promoteStats, { nowMs: Date.now() });
    applyPromotions(vault, evidenceStore, promotePlan.promote, recordsById(beforePromote), counts);
    // Phase 2 — consolidate the now-accepted set: link near-duplicates and auto-supersede pairwise
    // correction conflicts (the older conflicting fact -> `superseded`).
    const accepted = vault
        .listMemories({ includeExpired: true })
        .filter((record) => record.status === "accepted");
    runConsolidationPass(vault, evidenceStore, accepted, counts);
    // Phase 3 — reinforce / decay / archive / forget on the post-consolidation snapshot. The access
    // stats feed the strength model.
    const all = vault.listMemories({ includeExpired: true });
    const accessStats = vault.getAccessStats();
    const plan = planMemoryMaintenance(all, accessStats, { nowMs: Date.now() });
    applyDecayEffects(vault, evidenceStore, plan, recordsById(all), counts);
    return counts;
}
export function handleRunMaintenance(ctx, deps) {
    void ctx;
    const vault = resolveVault(deps);
    if (isRouteResult(vault))
        return vault;
    try {
        const counts = runMemoryMaintenance(vault, deps.evidenceStore);
        return { status: 200, body: counts };
    }
    catch (error) {
        const message = error instanceof Error ? error.message : "Memory maintenance failed.";
        return { status: 500, body: errorBody("MEMORY_MAINTENANCE_FAILED", message) };
    }
}
