import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { loadContinuityState, renderContinuityContext, saveContinuityState, } from "./continuity-files.js";
import { detectContinuityIntent } from "./continuity-intent-router.js";
export class ContinuityManager {
    options;
    writeQueue = Promise.resolve();
    cachedState;
    constructor(options) {
        this.options = options;
    }
    async loadState() {
        if (!this.cachedState) {
            this.cachedState = await loadContinuityState(this.options.cwd);
        }
        return this.cachedState;
    }
    async renderContext() {
        const state = await this.loadState();
        const stateHasOperationalContent = state.status !== "inactive" ||
            !!state.goal ||
            !!state.currentTask ||
            state.plan.length > 0 ||
            state.queue.length > 0 ||
            state.decisions.length > 0;
        const projectContinuity = await renderProjectContinuityFiles(this.options.cwd);
        if (!stateHasOperationalContent) {
            return projectContinuity;
        }
        const nativeContext = renderContinuityContext(state);
        return projectContinuity ? `${nativeContext}\n\n${projectContinuity}` : nativeContext;
    }
    async handleUserMessage(text) {
        const intent = detectContinuityIntent(text);
        if (intent.kind === "none") {
            return { intent, handled: false };
        }
        const state = await this.enqueueStateUpdate((currentState) => applyIntent(currentState, intent));
        return {
            intent,
            handled: true,
            context: renderContinuityContext(state),
        };
    }
    createLifecycleHooks() {
        return {
            afterTurnEnd: (context) => this.checkpointAfterTurn(context),
            beforeLoopEnd: (context) => this.checkpointBeforeLoopEnd(context),
        };
    }
    checkpointAfterTurn(context) {
        return this.enqueueCheckpoint({
            timestamp: Date.now(),
            reason: "turn",
            messageStopReason: context.message.stopReason,
            toolResultCount: countToolResults(context.toolResults),
            newMessageCount: context.newMessages.length,
            toolSummaries: summarizeToolResults(context.toolResults),
            modifiedFiles: extractModifiedFiles(context.toolResults),
            validation: summarizeValidation(context.toolResults),
        });
    }
    checkpointBeforeLoopEnd(context) {
        const toolResults = context.toolResults ?? [];
        return this.enqueueCheckpoint({
            timestamp: Date.now(),
            reason: "loop_end",
            messageStopReason: context.message?.stopReason ?? context.reason,
            toolResultCount: context.toolResults ? countToolResults(context.toolResults) : undefined,
            newMessageCount: context.newMessages.length,
            toolSummaries: summarizeToolResults(toolResults),
            modifiedFiles: extractModifiedFiles(toolResults),
            validation: summarizeValidation(toolResults),
        });
    }
    enqueueCheckpoint(checkpoint) {
        return this.enqueueStateUpdate((state) => ({
            ...state,
            updatedAt: checkpoint.timestamp,
            checkpoints: [...state.checkpoints, checkpoint],
            modifiedFiles: mergeUnique(state.modifiedFiles, checkpoint.modifiedFiles ?? []),
            lastValidatedAt: checkpoint.validation ? checkpoint.timestamp : state.lastValidatedAt,
            handoffSummary: createHandoffSummary(state.currentTask, checkpoint),
            nextAction: deriveNextAction(checkpoint),
        })).then(async () => {
            await this.options.onCheckpoint?.(checkpoint);
        });
    }
    enqueueStateUpdate(update) {
        const nextWrite = this.writeQueue.then(async () => {
            const state = await this.loadState();
            const nextState = update(state);
            this.cachedState = nextState;
            await saveContinuityState(nextState);
            return nextState;
        });
        this.writeQueue = nextWrite.then(() => undefined);
        return nextWrite;
    }
}
function applyIntent(state, intent) {
    const now = Date.now();
    const baseState = {
        ...state,
        updatedAt: now,
        lastUserIntent: {
            intent: intent.kind,
            confidence: intent.confidence,
            reason: intent.reason,
            text: intent.text,
            timestamp: now,
        },
        checkpoints: [
            ...state.checkpoints,
            {
                timestamp: now,
                reason: "user_intent",
                newMessageCount: 0,
            },
        ],
    };
    switch (intent.kind) {
        case "init":
            return {
                ...baseState,
                status: "active",
                goal: state.goal ?? extractGoal(intent.text),
                nextAction: "Plan the active continuity goal or continue from the user's latest request.",
                decisions: [...state.decisions, createDecision(now, intent.kind, "Initialized native continuity.")],
            };
        case "plan":
        case "update_plan": {
            const planEntry = createPlanEntry(state.plan.length + 1, now, intent.kind, intent.text);
            const queueItem = createQueueItem(state.queue.length + 1, now, intent.kind, intent.text);
            return {
                ...baseState,
                status: "active",
                plan: [...state.plan, planEntry],
                queue: [...state.queue, queueItem],
                nextAction: "Review the updated continuity plan and execute the first pending queue item when appropriate.",
            };
        }
        case "start": {
            const queue = markFirstPendingInProgress(state.queue, now);
            const currentTask = queue.find((item) => item.status === "in_progress")?.title;
            return {
                ...baseState,
                status: "active",
                queue,
                currentTask,
                nextAction: currentTask
                    ? `Execute current native continuity task: ${currentTask}`
                    : "Execute the first pending native continuity queue item.",
                decisions: [...state.decisions, createDecision(now, intent.kind, "User requested continuity execution.")],
            };
        }
        case "checkpoint":
            return {
                ...baseState,
                nextAction: "Continue from this explicit checkpoint.",
                decisions: [...state.decisions, createDecision(now, intent.kind, intent.text)],
            };
        case "status":
            return {
                ...baseState,
                nextAction: "Report current native continuity status to the user.",
            };
        case "end":
            return {
                ...baseState,
                status: "archived",
                nextAction: "Native continuity is archived. Reinitialize before continuing continuity work.",
                decisions: [...state.decisions, createDecision(now, intent.kind, "Archived native continuity.")],
            };
        case "none":
            return baseState;
    }
}
function countToolResults(toolResults) {
    return toolResults.length;
}
function deriveNextAction(checkpoint) {
    if (checkpoint.reason === "loop_end") {
        return "Review the latest user request and continue from the persisted continuity context.";
    }
    return "Continue the current turn from the latest completed checkpoint.";
}
function createPlanEntry(index, timestamp, intent, text) {
    return {
        id: `P${String(index).padStart(3, "0")}`,
        timestamp,
        intent,
        text,
    };
}
function createQueueItem(index, timestamp, intent, text) {
    return {
        id: `Q${String(index).padStart(3, "0")}`,
        status: "pending",
        title: summarizeAsTitle(text),
        createdAt: timestamp,
        updatedAt: timestamp,
        sourceIntent: intent,
        notes: [text],
    };
}
function createDecision(timestamp, intent, text) {
    return {
        timestamp,
        intent,
        text,
    };
}
function markFirstPendingInProgress(queue, timestamp) {
    let claimed = false;
    return queue.map((item) => {
        if (claimed || item.status !== "pending") {
            return item;
        }
        claimed = true;
        return {
            ...item,
            status: "in_progress",
            updatedAt: timestamp,
        };
    });
}
async function renderProjectContinuityFiles(cwd) {
    const [stateText, queueText, executionText, retrievalRoutesText, agentPolicyText] = await Promise.all([
        readOptionalProjectFile(cwd, "STATE.md"),
        readOptionalProjectFile(cwd, "ACTIVE_QUEUE.md"),
        readOptionalProjectFile(cwd, "AUTONOMOUS_EXECUTION.md"),
        readOptionalProjectFile(cwd, ".threadwell/retrieval-routes.json"),
        readOptionalProjectFile(cwd, ".threadwell/agent-policy.md"),
    ]);
    if (!stateText && !queueText && !executionText && !retrievalRoutesText && !agentPolicyText) {
        return undefined;
    }
    const lines = [
        "# Project continuity files",
        "",
        "Root continuity files were found. Treat STATE.md, ACTIVE_QUEUE.md, and AUTONOMOUS_EXECUTION.md as the primary human-readable source for task/queue/status questions before .threadwell runtime files or broad repository search.",
        "If .threadwell/retrieval-routes.json is present, use it to pick the first files to inspect after the root continuity files and before running broad ls/rg/find exploration.",
        "",
    ];
    if (stateText) {
        lines.push("## STATE.md", "", ...extractContinuityStateSummary(stateText), "");
    }
    if (queueText) {
        lines.push("## ACTIVE_QUEUE.md", "", ...extractContinuityQueueSummary(queueText), "");
    }
    if (executionText) {
        lines.push("## AUTONOMOUS_EXECUTION.md", "", ...extractContinuityExecutionSummary(executionText), "");
    }
    if (retrievalRoutesText) {
        lines.push("## .threadwell/retrieval-routes.json", "", ...extractRetrievalRouteSummary(retrievalRoutesText), "");
    }
    if (agentPolicyText) {
        lines.push("## .threadwell/agent-policy.md", "", ...agentPolicyText.split(/\r?\n/u).slice(0, 80), "");
    }
    return lines.join("\n").trim();
}
async function readOptionalProjectFile(cwd, fileName) {
    try {
        return await readFile(join(cwd, fileName), "utf-8");
    }
    catch {
        return undefined;
    }
}
function extractRetrievalRouteSummary(text) {
    try {
        const routes = JSON.parse(text);
        if (!Array.isArray(routes) || routes.length === 0)
            return ["No retrieval routes detected."];
        return routes.slice(0, 12).flatMap((route) => {
            const intent = typeof route.intent === "string" ? route.intent : "unknown";
            const confidence = typeof route.confidence === "string" ? route.confidence : "medium";
            const files = Array.isArray(route.firstFiles)
                ? route.firstFiles.filter((item) => typeof item === "string").slice(0, 5)
                : [];
            const hints = Array.isArray(route.queryHints)
                ? route.queryHints.filter((item) => typeof item === "string").slice(0, 4)
                : [];
            return [
                `### ${intent} (${confidence})`,
                files.length > 0 ? `First files: ${files.join(", ")}` : "First files: none synthesized.",
                hints.length > 0 ? `Query hints: ${hints.join(", ")}` : "Query hints: none synthesized.",
                "",
            ];
        });
    }
    catch {
        return ["Retrieval routes exist but could not be parsed."];
    }
}
function extractContinuityStateSummary(text) {
    const lines = [];
    const currentStatus = extractMarkdownSection(text, "Current status", 20);
    const activeGoal = extractMarkdownSection(text, "Active goal", 12);
    const nextStep = extractMarkdownSection(text, "Next recommended step", 12);
    if (currentStatus.length > 0) {
        lines.push("### Current status", "", ...currentStatus, "");
    }
    if (activeGoal.length > 0) {
        lines.push("### Active goal", "", ...activeGoal, "");
    }
    if (nextStep.length > 0) {
        lines.push("### Next recommended step", "", ...nextStep, "");
    }
    return lines.length > 0 ? lines : ["STATE.md exists but no compact summary section was detected."];
}
function extractContinuityQueueSummary(text) {
    const lines = [];
    const currentGoal = extractMarkdownSection(text, "Current goal", 16);
    if (currentGoal.length > 0) {
        lines.push("### Current goal", "", ...currentGoal, "");
    }
    const tasks = extractQueueTasks(text);
    if (tasks.length > 0) {
        lines.push("### Open tasks", "");
        for (const task of tasks) {
            lines.push(`- ${task.id} (${task.status}) — ${task.title}`);
        }
        lines.push("");
    }
    else {
        lines.push("No open queue tasks detected.", "");
    }
    return lines;
}
function extractContinuityExecutionSummary(text) {
    const goal = extractMarkdownSection(text, "Goal", 12);
    const constraints = extractMarkdownSection(text, "Constraints", 12);
    const lines = [];
    if (goal.length > 0) {
        lines.push("### Goal", "", ...goal, "");
    }
    if (constraints.length > 0) {
        lines.push("### Constraints", "", ...constraints, "");
    }
    return lines.length > 0 ? lines : ["AUTONOMOUS_EXECUTION.md exists."];
}
function extractMarkdownSection(text, heading, maxLines) {
    const lines = text.split(/\r?\n/u);
    const start = lines.findIndex((line) => line.trim().toLowerCase() === `## ${heading.toLowerCase()}`);
    if (start === -1)
        return [];
    const result = [];
    for (const line of lines.slice(start + 1)) {
        if (line.startsWith("## "))
            break;
        if (line.trim().length === 0 && result.length === 0)
            continue;
        result.push(line);
        if (result.length >= maxLines)
            break;
    }
    return trimTrailingBlankLines(result);
}
function extractQueueTasks(text) {
    const tasks = [];
    const taskPattern = /^###\s+(T\d+)\s+—\s+(.+?)\r?\n\r?\nStatus:\s+(\w+)/gmu;
    for (const match of text.matchAll(taskPattern)) {
        const status = match[3];
        if (status === "done" || status === "cancelled")
            continue;
        tasks.push({ id: match[1], title: match[2], status });
    }
    return tasks.slice(0, 100);
}
function trimTrailingBlankLines(lines) {
    const trimmed = [...lines];
    while (trimmed.at(-1)?.trim() === "") {
        trimmed.pop();
    }
    return trimmed;
}
function summarizeToolResults(toolResults) {
    if (toolResults.length === 0) {
        return undefined;
    }
    return toolResults.map((result) => ({ toolName: result.toolName, isError: result.isError }));
}
function extractModifiedFiles(toolResults) {
    const files = new Set();
    for (const result of toolResults) {
        if (!isLikelyFileMutationTool(result.toolName) || result.isError) {
            continue;
        }
        for (const content of result.content) {
            if (content.type !== "text") {
                continue;
            }
            for (const file of extractPathsFromText(content.text)) {
                files.add(file);
            }
        }
    }
    return files.size > 0 ? Array.from(files) : undefined;
}
function summarizeValidation(toolResults) {
    const bashResults = toolResults.filter((result) => result.toolName === "bash");
    if (bashResults.length === 0) {
        return undefined;
    }
    const failed = bashResults.filter((result) => result.isError).length;
    return failed > 0
        ? `${failed}/${bashResults.length} bash commands failed`
        : `${bashResults.length} bash commands completed`;
}
function isLikelyFileMutationTool(toolName) {
    return toolName === "edit" || toolName === "write" || toolName === "bash";
}
function extractPathsFromText(text) {
    const matches = text.match(/[\w./-]+\.[\w-]+/g) ?? [];
    return matches.filter((match) => !match.startsWith("http://") && !match.startsWith("https://"));
}
function mergeUnique(existing, additions) {
    return Array.from(new Set([...existing, ...additions]));
}
function createHandoffSummary(currentTask, checkpoint) {
    const taskText = currentTask ? `Current task: ${currentTask}. ` : "";
    return `${taskText}Last checkpoint: ${checkpoint.reason} at ${new Date(checkpoint.timestamp).toISOString()}. Next action: ${deriveNextAction(checkpoint)}`;
}
function summarizeAsTitle(text) {
    const normalized = text.replace(/\s+/g, " ").trim();
    if (normalized.length <= 80) {
        return normalized || "Continuity task";
    }
    return `${normalized.slice(0, 77)}...`;
}
function extractGoal(text) {
    const trimmed = text.trim();
    return trimmed.length > 0 ? trimmed : "Native continuity session";
}
export function createContinuityContextMessage(text) {
    return {
        role: "user",
        content: [
            {
                type: "text",
                text: `<continuity_context>\n${text.trim()}\n</continuity_context>`,
            },
        ],
        timestamp: Date.now(),
    };
}
//# sourceMappingURL=continuity-manager.js.map