/**
 * Startup engine-switch boundary.
 *
 * VisionClaw's session ID is opaque to the underlying engine: Claude
 * Code stores its transcript under `~/.claude/projects/<slug>/<id>.jsonl`
 * while the Pi Coding Agent writes to
 * `<profile>/pi-agent/<mode>/sessions/<id>.jsonl`. The two stores share
 * nothing — switching engines mid-session would split the conversation
 * across them with no logical continuity.
 *
 * The engine itself is **not** persisted anywhere — it is always derived
 * from `config.json` via `getAgentEngine()`. The *prior* engine is
 * inferred at startup from where the existing transcript file lives on
 * disk; no extra `session.json` field is required.
 *
 * ## Collapse semantics
 *
 * Dual-session adds another wrinkle: the prior run may have had a
 * general session AND a paused/in-progress coding session, possibly
 * with cross-session memos in the mailbox. On engine switch we don't
 * try to migrate any of that state — the new engine wouldn't know how
 * to resume a transcript it didn't write, and a half-resumed coding
 * task with a stale mailbox is more confusing than useful.
 *
 * Instead, when **either** the general or coding transcript was
 * written by a different engine than `config.json` currently selects:
 *
 *   1. Summarize every existing transcript (general + coding if any).
 *   2. Snapshot the mailbox (active mode, memos, coding-session info)
 *      into a text section so the agent doesn't lose track of what was
 *      in flight or what the coding task was supposed to be.
 *   3. Combine summaries + mailbox snapshot into a single document.
 *   4. Rotate every persisted general/coding session id into
 *      `previousSessionIds` (the old transcripts remain on disk for
 *      `memory_transcript_search`).
 *   5. Clear the mailbox (mark active=general, drop memos, reset
 *      codingSession to idle).
 *   6. Inject the combined summary into the freshly created general
 *      session as priority context for its first turn.
 *
 * If a persisted session id has no transcript at any known engine path,
 * but also cannot be safely resumed by the current engine, we still retire
 * it without transcript summarization. This protects startup from looping
 * on stale cross-engine ids when the transcript filename cannot be resolved.
 *
 * `SessionManager` constructs the coding session conditionally on
 * mailbox state, so clearing the mailbox before constructing the
 * manager naturally yields a single general-only session.
 */
import fs from "node:fs";
import path from "node:path";
import { getConfigDir, loadSessionId, rotateSessionId } from "../config/index.js";
import { clearMailbox, loadMailbox, } from "./mailbox.js";
import { getClaudeTranscriptDirForProfileDir } from "../utils/claude-transcripts.js";
import { summarizeWithFallback } from "./transcript-summarizer.js";
import { logger } from "../logger.js";
/** Build the conventional Claude transcript path for a session id. */
function claudeTranscriptPath(sessionId) {
    return path.join(getClaudeTranscriptDirForProfileDir(getConfigDir()), `${sessionId}.jsonl`);
}
/** Build the Pi Coding transcript directory for a mode. */
function piCodingTranscriptDir(mode) {
    return path.join(getConfigDir(), "pi-agent", mode, "sessions");
}
/** Locate a Pi Coding transcript for a persisted session id. */
function findPiCodingTranscriptPath(mode, sessionId) {
    const transcriptPath = path.join(piCodingTranscriptDir(mode), `${sessionId}.jsonl`);
    return fs.existsSync(transcriptPath) && fs.lstatSync(transcriptPath).isFile()
        ? transcriptPath
        : null;
}
/**
 * Infer which engine wrote the transcript for `sessionId` by checking
 * the on-disk locations both engines use. Returns `null` when neither
 * transcript exists (legacy / never-run session).
 */
export function inferPriorEngine(mode, sessionId) {
    const claudePath = claudeTranscriptPath(sessionId);
    if (fs.existsSync(claudePath)) {
        return { engine: "claude", transcriptPath: claudePath };
    }
    const piPath = findPiCodingTranscriptPath(mode, sessionId);
    if (piPath) {
        return { engine: "pi-coding", transcriptPath: piPath };
    }
    return null;
}
function collectPersistedSessions() {
    const sessions = [];
    for (const mode of ["general", "coding"]) {
        const id = loadSessionId(mode);
        if (!id)
            continue;
        sessions.push({ mode, sessionId: id });
    }
    return sessions;
}
function collectPriorSessions(persisted) {
    const sessions = [];
    for (const { mode, sessionId } of persisted) {
        const inferred = inferPriorEngine(mode, sessionId);
        if (!inferred)
            continue;
        sessions.push({ mode, sessionId, engine: inferred.engine, transcriptPath: inferred.transcriptPath });
    }
    return sessions;
}
function renderMailboxSnapshot(mailbox) {
    const lines = ["### Mailbox snapshot (cleared on switch)"];
    lines.push(`- active session: ${mailbox.activeSession}`);
    const cs = mailbox.codingSession;
    if (cs.status !== "idle" || cs.taskSummary || cs.id || cs.cwd) {
        lines.push(`- coding session: status=${cs.status}` +
            (cs.taskSummary ? `, task="${cs.taskSummary}"` : "") +
            (cs.cwd ? `, cwd=${cs.cwd}` : "") +
            (cs.id ? `, id=${cs.id}` : ""));
    }
    else {
        lines.push("- coding session: (idle)");
    }
    if (mailbox.memos.length > 0) {
        lines.push("- pending memos:");
        for (const memo of mailbox.memos) {
            lines.push(`  - [${memo.timestamp}] ${memo.from} → ${memo.to}: ${memo.content}`);
        }
    }
    else {
        lines.push("- pending memos: (none)");
    }
    return lines.join("\n");
}
function renderHeader(toEngine, persisted, prior) {
    const transcriptByKey = new Map(prior.map((p) => [`${p.mode}:${p.sessionId}`, p.engine]));
    const list = persisted
        .map((p) => {
        const engine = transcriptByKey.get(`${p.mode}:${p.sessionId}`);
        return engine
            ? `${p.mode} (engine="${engine}", id=${p.sessionId})`
            : `${p.mode} (id=${p.sessionId}, transcript not found)`;
    })
        .join(", ");
    return (`Engine switched to "${toEngine}" between runs. At least one prior transcript ` +
        `was written by a different engine and cannot be resumed in place. Prior ` +
        `session state: [${list}]. ` +
        `A single fresh general session has been created; the prior session ids have ` +
        `been moved to previousSessionIds and remain searchable via ` +
        `\`memory_transcript_search\`. Any prior coding task or cross-session memos ` +
        `have been folded into the summary below.`);
}
function renderSection(prior, summary) {
    return [
        `## Prior ${prior.mode} session (engine="${prior.engine}", id=${prior.sessionId})`,
        "",
        summary,
    ].join("\n");
}
/**
 * Detect, summarize, and collapse a cross-engine session boundary if
 * one applies for this restart. Returns `null` when no engine switch is
 * needed (all existing transcripts were written by `currentEngine`, or
 * there are no prior transcripts at all).
 *
 * Side effects when a boundary is detected and a summary is produced:
 *  - every persisted general/coding session id is rotated into
 *    `previousSessionIds`,
 *  - the mailbox is cleared.
 *
 * Callers must invoke this **before** constructing `SessionManager` so
 * the manager sees the post-collapse mailbox (single general session,
 * no coding session). After construction, hand `combinedSummary` to
 * `sessionManager.injectSummary("general", ...)`.
 */
export async function prepareEngineSwitchBoundary(config, currentEngine) {
    const persisted = collectPersistedSessions();
    if (persisted.length === 0)
        return null;
    const prior = collectPriorSessions(persisted);
    // A session id alone does not tell us which engine wrote it. If no
    // transcript exists in either known engine location, rotate the stale
    // ids only when the active engine has no matching transcript to resume.
    // This prevents Claude from trying to resume Pi ids (and vice versa)
    // after a provider/model switch where the prior transcript file cannot
    // be found under its conventional name.
    if (prior.length === 0) {
        const allIdsHaveCurrentEngineTranscripts = persisted.every(({ mode, sessionId }) => {
            if (currentEngine === "claude")
                return fs.existsSync(claudeTranscriptPath(sessionId));
            return findPiCodingTranscriptPath(mode, sessionId) !== null;
        });
        if (allIdsHaveCurrentEngineTranscripts)
            return null;
        const mailboxSection = renderMailboxSnapshot(loadMailbox());
        for (const p of persisted) {
            rotateSessionId(p.mode);
            logger.system(`[engine-switch] rotated stale ${p.mode} session ${p.sessionId} into previousSessionIds (transcript not found)`);
        }
        clearMailbox();
        logger.system("[engine-switch] no resumable current-engine transcripts found; collapsed to fresh general session");
        return {
            toEngine: currentEngine,
            priorSessions: [],
            combinedSummary: [
                `A session boundary was created for "${currentEngine}" startup. Existing session ids were retired because no matching transcript files were found for the current engine to resume safely.`,
                "",
                mailboxSection,
                "",
                "Start a fresh session and continue normally. If needed, use memory_transcript_search to inspect older transcripts.",
            ].join("\n"),
        };
    }
    const mismatch = prior.some((p) => p.engine !== currentEngine);
    if (!mismatch)
        return null;
    // Summarize every prior session we have a transcript for, even ones
    // that already match `currentEngine` — they were active alongside
    // the mismatched session and the user may have moved work between
    // them. We're collapsing everything into one fresh session anyway.
    const sections = [];
    for (const p of prior) {
        logger.system(`[engine-switch] summarizing prior ${p.mode} session ${p.sessionId} (engine=${p.engine})`);
        const summary = await summarizeWithFallback(p.transcriptPath, config);
        sections.push(renderSection(p, summary));
    }
    const mailbox = loadMailbox();
    const mailboxSection = renderMailboxSnapshot(mailbox);
    const combinedSummary = [
        renderHeader(currentEngine, persisted, prior),
        "",
        ...sections,
        "",
        mailboxSection,
        "",
        "Review the prior context above, check your memory directory and todo list, and continue where the prior engine left off.",
    ].join("\n");
    // Rotate every persisted session id (whether or not its transcript
    // was found — they're all being retired together) and clear the
    // mailbox. SessionManager, constructed after this call, will see a
    // clean slate.
    for (const p of persisted) {
        rotateSessionId(p.mode);
        logger.system(`[engine-switch] rotated ${p.mode} session ${p.sessionId} into previousSessionIds`);
    }
    clearMailbox();
    logger.system(`[engine-switch] mailbox cleared; collapsed to fresh general session`);
    return {
        toEngine: currentEngine,
        priorSessions: prior,
        combinedSummary,
    };
}
//# sourceMappingURL=engine-switch.js.map