import { mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { THREADWELL_CONFIG_DIR_NAME } from "../../config.js";
const MAX_CHECKPOINTS = 50;
const MAX_PLAN_ENTRIES = 100;
const MAX_DECISIONS = 100;
const MAX_MODIFIED_FILES = 100;
export function getContinuityPaths(cwd) {
    return createContinuityPaths(join(cwd, THREADWELL_CONFIG_DIR_NAME, "continuity"));
}
function createContinuityPaths(dir) {
    return {
        dir,
        state: join(dir, "state.json"),
        context: join(dir, "context.md"),
        plan: join(dir, "plan.md"),
        queue: join(dir, "queue.md"),
        decisionLog: join(dir, "decision-log.md"),
        nextAction: join(dir, "next-action.md"),
    };
}
export function createEmptyContinuityState(cwd) {
    return {
        version: 1,
        updatedAt: Date.now(),
        cwd,
        status: "inactive",
        blockedOn: [],
        openQuestions: [],
        modifiedFiles: [],
        plan: [],
        queue: [],
        decisions: [],
        checkpoints: [],
    };
}
export async function loadContinuityState(cwd) {
    const paths = getContinuityPaths(cwd);
    try {
        const raw = await readFile(paths.state, "utf-8");
        return normalizeContinuityState(parseContinuityStateJson(raw), cwd);
    }
    catch (error) {
        if (isFileNotFoundError(error)) {
            return createEmptyContinuityState(cwd);
        }
        return createEmptyContinuityState(cwd);
    }
}
function parseContinuityStateJson(raw) {
    try {
        return JSON.parse(raw);
    }
    catch { }
    const markerIndex = raw.indexOf("<!-- THREADSUITE:START -->");
    if (markerIndex > 0) {
        return JSON.parse(raw.slice(0, markerIndex).trim());
    }
    const objectText = extractFirstJsonObject(raw);
    return objectText ? JSON.parse(objectText) : undefined;
}
function extractFirstJsonObject(raw) {
    let start = -1;
    let depth = 0;
    let inString = false;
    let escaped = false;
    for (let index = 0; index < raw.length; index++) {
        const char = raw[index];
        if (char === undefined)
            continue;
        if (inString) {
            if (escaped)
                escaped = false;
            else if (char === "\\")
                escaped = true;
            else if (char === '"')
                inString = false;
            continue;
        }
        if (char === '"') {
            inString = true;
            continue;
        }
        if (char === "{") {
            if (depth === 0)
                start = index;
            depth++;
            continue;
        }
        if (char !== "}")
            continue;
        depth--;
        if (depth === 0 && start >= 0)
            return raw.slice(start, index + 1);
    }
    return undefined;
}
export async function saveContinuityState(state) {
    const paths = getContinuityPaths(state.cwd);
    const trimmedState = trimContinuityState(state);
    await mkdir(dirname(paths.state), { recursive: true });
    await writeFile(paths.state, `${JSON.stringify(trimmedState, null, 2)}\n`, "utf-8");
    await writeFile(paths.context, renderContinuityContext(trimmedState), "utf-8");
    await writeFile(paths.plan, renderContinuityPlan(trimmedState), "utf-8");
    await writeFile(paths.queue, renderContinuityQueue(trimmedState), "utf-8");
    await writeFile(paths.decisionLog, renderContinuityDecisionLog(trimmedState), "utf-8");
    await writeFile(paths.nextAction, `${trimmedState.nextAction ?? ""}\n`, "utf-8");
}
export function renderContinuityContext(state) {
    const latestCheckpoint = state.checkpoints.at(-1);
    const lines = [
        "# Native continuity context",
        "",
        `Updated: ${new Date(state.updatedAt).toISOString()}`,
        `CWD: ${state.cwd}`,
        `Status: ${state.status}`,
        "",
        "## Goal",
        "",
        state.goal ?? "No active goal recorded.",
        "",
        "## Current next action",
        "",
        state.nextAction ?? "Continue from the user's latest request.",
        "",
        "## Current task",
        "",
        state.currentTask ?? "No current task recorded.",
        "",
        "## Blockers",
        "",
        ...renderListOrEmpty(state.blockedOn, "No blockers recorded."),
        "",
        "## Open questions",
        "",
        ...renderListOrEmpty(state.openQuestions, "No open questions recorded."),
        "",
        "## Modified files",
        "",
        ...renderListOrEmpty(state.modifiedFiles.slice(-10), "No modified files recorded."),
        "",
        "## Handoff summary",
        "",
        state.handoffSummary ?? "No handoff summary recorded.",
        "",
        "## Memory anchors",
        "",
        ...renderMemoryAnchorLines(state),
        "",
        "## Plan summary",
        "",
        ...renderRecentPlanLines(state),
        "",
        "## Queue summary",
        "",
        ...renderQueueSummaryLines(state),
        "",
        "## Last user intent",
        "",
        state.lastUserIntent
            ? `- ${state.lastUserIntent.intent} (${state.lastUserIntent.confidence}): ${state.lastUserIntent.reason}`
            : "No user intent recorded yet.",
        "",
        "## Last checkpoint",
        "",
    ];
    if (latestCheckpoint) {
        lines.push(`- Time: ${new Date(latestCheckpoint.timestamp).toISOString()}`, `- Reason: ${latestCheckpoint.reason}`, `- New messages: ${latestCheckpoint.newMessageCount}`);
        if (latestCheckpoint.messageStopReason) {
            lines.push(`- Message stop reason: ${latestCheckpoint.messageStopReason}`);
        }
        if (latestCheckpoint.toolResultCount !== undefined) {
            lines.push(`- Tool results: ${latestCheckpoint.toolResultCount}`);
        }
        if (latestCheckpoint.toolSummaries?.length) {
            lines.push("- Tools: " +
                latestCheckpoint.toolSummaries
                    .map((tool) => `${tool.toolName}${tool.isError ? " (error)" : ""}`)
                    .join(", "));
        }
        if (latestCheckpoint.modifiedFiles?.length) {
            lines.push(`- Modified files: ${latestCheckpoint.modifiedFiles.join(", ")}`);
        }
        if (latestCheckpoint.validation) {
            lines.push(`- Validation: ${latestCheckpoint.validation}`);
        }
    }
    else {
        lines.push("No checkpoints recorded yet.");
    }
    lines.push("", "## Recent checkpoints", "");
    for (const checkpoint of state.checkpoints.slice(-5).reverse()) {
        lines.push(`- ${new Date(checkpoint.timestamp).toISOString()} ${checkpoint.reason} (${checkpoint.newMessageCount} new messages)`);
    }
    return `${lines.join("\n")}\n`;
}
export function renderContinuityPlan(state) {
    const lines = ["# Continuity plan", "", `Updated: ${new Date(state.updatedAt).toISOString()}`, ""];
    if (state.plan.length === 0) {
        lines.push("No plan entries yet.");
        return `${lines.join("\n")}\n`;
    }
    for (const entry of state.plan) {
        lines.push(`## ${entry.id} — ${new Date(entry.timestamp).toISOString()}`, "", `Intent: ${entry.intent}`, "", entry.text, "");
    }
    return `${lines.join("\n")}\n`;
}
export function renderContinuityQueue(state) {
    const lines = ["# Continuity queue", "", `Updated: ${new Date(state.updatedAt).toISOString()}`, ""];
    if (state.queue.length === 0) {
        lines.push("No queue items yet.");
        return `${lines.join("\n")}\n`;
    }
    for (const item of state.queue) {
        lines.push(`## ${item.id} — ${item.title}`, "", `Status: ${item.status}`, `Created: ${new Date(item.createdAt).toISOString()}`, `Updated: ${new Date(item.updatedAt).toISOString()}`, `Source intent: ${item.sourceIntent ?? "none"}`, "", "Notes:", ...item.notes.map((note) => `- ${note}`), "");
    }
    return `${lines.join("\n")}\n`;
}
export function renderContinuityDecisionLog(state) {
    const lines = ["# Continuity decision log", "", `Updated: ${new Date(state.updatedAt).toISOString()}`, ""];
    if (state.decisions.length === 0) {
        lines.push("No decisions recorded yet.");
        return `${lines.join("\n")}\n`;
    }
    for (const decision of state.decisions) {
        lines.push(`- ${new Date(decision.timestamp).toISOString()} [${decision.intent}] ${decision.text}`);
    }
    return `${lines.join("\n")}\n`;
}
function renderListOrEmpty(values, emptyText) {
    return values.length > 0 ? values.map((value) => `- ${value}`) : [emptyText];
}
function renderMemoryAnchorLines(state) {
    const lines = [];
    const latestCheckpoint = state.checkpoints.at(-1);
    if (state.modifiedFiles.length > 0) {
        lines.push(...state.modifiedFiles.slice(-10).map((file) => `- file: ${file}`));
    }
    if (latestCheckpoint?.validation) {
        lines.push(`- validation: ${latestCheckpoint.validation}`);
    }
    if (latestCheckpoint) {
        lines.push(`- checkpoint: ${new Date(latestCheckpoint.timestamp).toISOString()} ${latestCheckpoint.reason}`);
    }
    if (state.decisions.length > 0) {
        lines.push(...state.decisions
            .slice(-5)
            .map((decision) => `- decision: ${decision.intent} ${new Date(decision.timestamp).toISOString()}`));
    }
    return lines.length > 0 ? lines : ["No memory anchors recorded yet."];
}
function renderRecentPlanLines(state) {
    if (state.plan.length === 0) {
        return ["No plan entries yet."];
    }
    return state.plan.slice(-5).map((entry) => `- ${entry.id}: ${entry.intent} — ${firstLine(entry.text)}`);
}
function renderQueueSummaryLines(state) {
    if (state.queue.length === 0) {
        return ["No queue items yet."];
    }
    return state.queue.slice(0, 10).map((item) => `- ${item.id} [${item.status}] ${item.title}`);
}
function trimContinuityState(state) {
    return {
        ...state,
        modifiedFiles: state.modifiedFiles.slice(-MAX_MODIFIED_FILES),
        plan: state.plan.slice(-MAX_PLAN_ENTRIES),
        decisions: state.decisions.slice(-MAX_DECISIONS),
        checkpoints: state.checkpoints.slice(-MAX_CHECKPOINTS),
    };
}
function normalizeContinuityState(value, cwd) {
    if (!isRecord(value)) {
        return createEmptyContinuityState(cwd);
    }
    const checkpoints = Array.isArray(value.checkpoints) ? value.checkpoints.filter(isContinuityCheckpoint) : [];
    const plan = Array.isArray(value.plan) ? value.plan.filter(isContinuityPlanEntry) : [];
    const queue = Array.isArray(value.queue) ? value.queue.filter(isContinuityQueueItem) : [];
    const decisions = Array.isArray(value.decisions) ? value.decisions.filter(isContinuityDecisionEntry) : [];
    return {
        version: 1,
        updatedAt: typeof value.updatedAt === "number" ? value.updatedAt : Date.now(),
        cwd,
        status: isContinuityModeStatus(value.status) ? value.status : "inactive",
        goal: typeof value.goal === "string" ? value.goal : undefined,
        currentTask: typeof value.currentTask === "string" ? value.currentTask : undefined,
        blockedOn: readStringArray(value.blockedOn),
        openQuestions: readStringArray(value.openQuestions),
        modifiedFiles: readStringArray(value.modifiedFiles),
        lastValidatedAt: typeof value.lastValidatedAt === "number" ? value.lastValidatedAt : undefined,
        handoffSummary: typeof value.handoffSummary === "string" ? value.handoffSummary : undefined,
        plan,
        queue,
        decisions,
        checkpoints,
        lastUserIntent: isContinuityLastUserIntent(value.lastUserIntent) ? value.lastUserIntent : undefined,
        nextAction: typeof value.nextAction === "string" ? value.nextAction : undefined,
    };
}
function readStringArray(value) {
    return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
}
function isContinuityCheckpoint(value) {
    if (!isRecord(value)) {
        return false;
    }
    return (typeof value.timestamp === "number" &&
        (value.reason === "turn" || value.reason === "loop_end" || value.reason === "user_intent") &&
        typeof value.newMessageCount === "number" &&
        (value.messageStopReason === undefined || typeof value.messageStopReason === "string") &&
        (value.toolResultCount === undefined || typeof value.toolResultCount === "number") &&
        (value.toolSummaries === undefined ||
            (Array.isArray(value.toolSummaries) && value.toolSummaries.every(isContinuityToolSummary))) &&
        (value.modifiedFiles === undefined ||
            (Array.isArray(value.modifiedFiles) && value.modifiedFiles.every((file) => typeof file === "string"))) &&
        (value.validation === undefined || typeof value.validation === "string"));
}
function isContinuityToolSummary(value) {
    return isRecord(value) && typeof value.toolName === "string" && typeof value.isError === "boolean";
}
function isContinuityPlanEntry(value) {
    return (isRecord(value) &&
        typeof value.id === "string" &&
        typeof value.timestamp === "number" &&
        typeof value.intent === "string" &&
        typeof value.text === "string");
}
function isContinuityQueueItem(value) {
    return (isRecord(value) &&
        typeof value.id === "string" &&
        isContinuityQueueStatus(value.status) &&
        typeof value.title === "string" &&
        typeof value.createdAt === "number" &&
        typeof value.updatedAt === "number" &&
        (value.sourceIntent === undefined || typeof value.sourceIntent === "string") &&
        Array.isArray(value.notes) &&
        value.notes.every((note) => typeof note === "string"));
}
function isContinuityDecisionEntry(value) {
    return (isRecord(value) &&
        typeof value.timestamp === "number" &&
        typeof value.intent === "string" &&
        typeof value.text === "string");
}
function isContinuityLastUserIntent(value) {
    return (isRecord(value) &&
        typeof value.intent === "string" &&
        typeof value.confidence === "number" &&
        typeof value.reason === "string" &&
        typeof value.text === "string" &&
        typeof value.timestamp === "number");
}
function isContinuityModeStatus(value) {
    return value === "inactive" || value === "active" || value === "paused" || value === "archived";
}
function isContinuityQueueStatus(value) {
    return (value === "pending" ||
        value === "in_progress" ||
        value === "done" ||
        value === "blocked" ||
        value === "partial" ||
        value === "cancelled");
}
function firstLine(text) {
    return text.split(/\r?\n/, 1)[0]?.trim() || text.trim();
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isFileNotFoundError(error) {
    return isRecord(error) && error.code === "ENOENT";
}
//# sourceMappingURL=continuity-files.js.map