/**
 * memory-dream-run — Task 327 (+ Task 391 prune-revisions phase).
 *
 * Operator-initiated dream-cycle orchestrator. Runs the configured phases
 * in fixed order with failure isolation: any phase that throws is logged
 * `[dream-cycle:phase-<n>:error]` and added to the run report, but
 * subsequent phases still execute.
 *
 * The `phases` parameter selects a subset (e.g. `[4]` for a citation-only
 * sweep after a bulk ingest). It does NOT reorder — the order is fixed.
 *
 * Phase 4 reads the resume cursor from the most-recent `:Report` (keyword
 * "dream-cycle") via a direct property read (`lastEventCursor`), and writes
 * its own next-cursor onto this run's :Report via a post-write Cypher SET.
 * The cursor lives as a direct property on :Report rather than via a
 * writer-side `extra` field because the canonical `memory-report-write`
 * (Task 332) does not accept arbitrary payload. Subsequent invocations
 * resume after the stored id so a populated graph drains gradually rather
 * than running unbounded LLM calls on every invocation.
 *
 * Phase 5 (Task 391) prunes `:CompiledTruthRevision` rows down to the
 * newest 20 per entity. Phase 6 (Task 596) materialises persistent
 * `:BACKLINKS` edges from `:MENTIONS`/`:REFERENCES`. Phase 7 (Task 596)
 * normalises `tags[]` arrays on `:KnowledgeDocument` nodes and merges
 * `:DefinedTerm {category:'obsidian-tag'}` variants. The `phases` schema
 * accepts 1..7; values outside that range are rejected by zod at the MCP
 * boundary.
 */
import { getSession } from "../lib/neo4j.js";
import { phaseOrphans } from "../lib/dream-cycle/phase-orphans.js";
import { phaseStaleTruth } from "../lib/dream-cycle/phase-stale-truth.js";
import { phaseDeadEdges } from "../lib/dream-cycle/phase-dead-edges.js";
import { phaseCitationAudit } from "../lib/dream-cycle/phase-citation-audit.js";
import { phasePruneRevisions } from "../lib/dream-cycle/phase-prune-revisions.js";
import { phaseBacklinks } from "../lib/dream-cycle/phase-backlinks.js";
import { phaseTagNormalisation } from "../lib/dream-cycle/phase-tag-normalisation.js";
import { memoryReportWrite } from "./memory-report-write.js";
const ALL_PHASES = [1, 2, 3, 4, 5, 6, 7];
const PHASE_DISPATCH = {
    1: { name: "orphans", run: phaseOrphans },
    2: { name: "stale-truth", run: phaseStaleTruth },
    3: { name: "dead-edges", run: phaseDeadEdges },
    4: { name: "citation-audit", run: phaseCitationAudit },
    5: { name: "prune-revisions", run: phasePruneRevisions },
    6: { name: "backlinks", run: phaseBacklinks },
    7: { name: "tag-normalisation", run: phaseTagNormalisation },
};
export async function memoryDreamRun(params) {
    const start = Date.now();
    const { accountId, operatorId } = params;
    const phasesRun = (params.phases ?? [...ALL_PHASES]).filter((p) => ALL_PHASES.includes(p));
    process.stderr.write(`[dream-cycle] started operatorId=${operatorId ?? "<unknown>"} phases=[${phasesRun.join(",")}]\n`);
    // Resume-cursor pickup: the most-recent prior `:Report` for this
    // account with keyword "dream-cycle" carries `lastEventCursor` if its
    // phase 4 was partial. Read it directly from the node — `memory-report-*`
    // tools don't surface arbitrary properties.
    let resumeCursor = null;
    const session = getSession();
    if (phasesRun.includes(4)) {
        const prior = await session.run(`MATCH (r:Report)
       WHERE r.accountId = $accountId AND 'dream-cycle' IN r.keywords
         AND r.lastEventCursor IS NOT NULL
       RETURN r.lastEventCursor AS cursor
       ORDER BY r.occurredAt DESC LIMIT 1`, { accountId });
        const cursorVal = prior.records[0]?.get("cursor");
        if (typeof cursorVal === "string" && cursorVal.length > 0) {
            resumeCursor = cursorVal;
        }
    }
    const phaseResults = [];
    const phaseErrors = [];
    try {
        for (const phase of phasesRun) {
            const dispatch = PHASE_DISPATCH[phase];
            try {
                const result = await dispatch.run({
                    accountId,
                    session,
                    config: {
                        maxEventsPerRun: params.maxEventsPerRun,
                        resumeCursor: phase === 4 ? resumeCursor : null,
                    },
                });
                phaseResults.push(result);
            }
            catch (err) {
                const reason = err instanceof Error ? err.message : String(err);
                phaseErrors.push({ phase, name: dispatch.name, reason });
                process.stderr.write(`[dream-cycle:phase-${phase}:error] reason=${reason}\n`);
            }
        }
    }
    finally {
        await session.close();
    }
    const durationSec = Math.round((Date.now() - start) / 1000);
    process.stderr.write(`[dream-cycle] completed phases=${phaseResults.length} durationSec=${durationSec}\n`);
    // Carry forward phase 4's resume cursor onto the new report so the
    // next invocation can pick up where this one left off.
    const phase4 = phaseResults.find((r) => r.phase === 4);
    const partial = Boolean(phase4?.extra?.partial);
    const newCursor = phase4?.extra?.lastEventCursor ?? null;
    const reportTitle = `Dream cycle ${new Date().toISOString().slice(0, 16).replace("T", " ")}`;
    const reportBody = buildReportBody({
        operatorId,
        phasesRun,
        phaseResults,
        phaseErrors,
        durationSec,
    });
    const { reportId } = await memoryReportWrite({
        accountId,
        title: reportTitle,
        body: reportBody,
        keywords: ["dream-cycle"],
        sourceWorkflow: "dream-cycle",
    });
    // Post-write: attach cursor + run metadata to the :Report. Done as a
    // separate SET because memory-report-write does not accept arbitrary
    // payload; storing the cursor here keeps the resume contract in one
    // place (read above, write here).
    const cursorSession = getSession();
    try {
        await cursorSession.run(`MATCH (r:Report {reportId: $reportId, accountId: $accountId})
       SET r.lastEventCursor = $cursor,
           r.partial = $partial,
           r.operatorId = $operatorId,
           r.phasesRun = $phasesRun,
           r.phase6Count = $phase6Count,
           r.phase7Count = $phase7Count`, {
            reportId,
            accountId,
            cursor: newCursor,
            partial,
            operatorId: operatorId ?? null,
            phasesRun,
            phase6Count: phaseResults.find((r) => r.phase === 6)?.applied ?? null,
            phase7Count: phaseResults.find((r) => r.phase === 7)?.applied ?? null,
        });
    }
    finally {
        await cursorSession.close();
    }
    return { reportId, phasesRun, phaseResults, phaseErrors, durationSec, partial };
}
function buildReportBody(input) {
    const lines = [];
    lines.push(`# Dream cycle run`);
    lines.push(``);
    lines.push(`- Operator: ${input.operatorId ?? "<unknown>"}`);
    lines.push(`- Phases requested: ${input.phasesRun.join(", ")}`);
    lines.push(`- Duration: ${input.durationSec}s`);
    lines.push(``);
    lines.push(`## Phase results`);
    for (const r of input.phaseResults) {
        lines.push(`- **phase ${r.phase} ${r.name}** — duration ${r.durationSec}s, ` +
            `proposed ${r.proposed}, applied ${r.applied}, deferred ${r.deferred}`);
    }
    if (input.phaseErrors.length > 0) {
        lines.push(``);
        lines.push(`## Phase errors`);
        for (const e of input.phaseErrors) {
            lines.push(`- **phase ${e.phase} ${e.name}** — ${e.reason}`);
        }
    }
    // Phase 4 detail block when present.
    const phase4 = input.phaseResults.find((r) => r.phase === 4);
    if (phase4?.extra) {
        const x = phase4.extra;
        lines.push(``);
        lines.push(`## Citation audit (phase 4)`);
        lines.push(`- scanned=${x.scanned} highConfApplied=${x.highConfApplied} ` +
            `lowConfDeferred=${x.lowConfDeferred} malformedFixed=${x.malformedFixed} ` +
            `partial=${x.partial}`);
        if (x.partial) {
            lines.push(`- Resume cursor stored on this report; next run continues after this point.`);
        }
    }
    if (input.phasesRun.includes(6)) {
        const phase6 = input.phaseResults.find((r) => r.phase === 6);
        lines.push(``);
        lines.push(`## Backlinks (phase 6)`);
        lines.push(phase6
            ? `- proposed=${phase6.proposed} applied=${phase6.applied}`
            : `- not run`);
    }
    if (input.phasesRun.includes(7)) {
        const phase7 = input.phaseResults.find((r) => r.phase === 7);
        lines.push(``);
        lines.push(`## Tag normalisation (phase 7)`);
        lines.push(phase7
            ? `- merged=${phase7.applied}`
            : `- not run`);
    }
    return lines.join("\n");
}
//# sourceMappingURL=memory-dream-run.js.map