import { access, readFile, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { OperationQueue } from "../util/operation-queue.js";
export const MIGRATION_CHECKLIST_FILE = "migration-checklist.json";
export const MIGRATION_CHECKLIST_VERSION = 1;
const SCRATCH_DIR_NAME = "scratch";
/**
 * Per-app serialization of read-modify-write mutations on the shared
 * migration checklist JSON. Parallel sub-agents (see
 * `spawnCodingSubagents`) can otherwise race: two concurrent
 * `build_manageChecklist` calls would both read the same snapshot,
 * compute new state independently, and both write — losing one update.
 *
 * Reads (`readMigrationChecklist`) do not go through the queue.
 */
const mutationQueues = new Map();
function getMutationQueue(appRootDirPath) {
    let queue = mutationQueues.get(appRootDirPath);
    if (!queue) {
        queue = new OperationQueue();
        mutationQueues.set(appRootDirPath, queue);
    }
    return queue;
}
export async function isMigrationChecklistActive(appRootDirPath) {
    return (await readMigrationChecklist(appRootDirPath)) !== null;
}
export async function readMigrationChecklist(appRootDirPath) {
    for (const checklistPath of getChecklistPathCandidates(appRootDirPath)) {
        try {
            const raw = await readFile(checklistPath, "utf-8");
            const parsed = JSON.parse(raw);
            if (parsed &&
                parsed.version === MIGRATION_CHECKLIST_VERSION &&
                Array.isArray(parsed.items)) {
                return {
                    version: MIGRATION_CHECKLIST_VERSION,
                    overallStatus: parsed.overallStatus === "completed" ? "completed" : "active",
                    items: parsed.items,
                    lastUpdated: typeof parsed.lastUpdated === "string"
                        ? parsed.lastUpdated
                        : new Date().toISOString(),
                };
            }
        }
        catch {
            // Try next candidate.
        }
    }
    return null;
}
export async function seedMigrationChecklist(args) {
    const now = new Date().toISOString();
    const state = {
        version: MIGRATION_CHECKLIST_VERSION,
        overallStatus: "active",
        lastUpdated: now,
        items: args.apiChecklist.map((entry) => ({
            id: `api_${entry.name}`,
            content: `Port API: ${entry.name}`,
            status: "pending",
            createdAt: now,
            origin: "seed_api",
            apiName: entry.name,
            stubPath: entry.stubPath,
        })),
    };
    await writeMigrationChecklist(args.appRootDirPath, state);
    return state;
}
export async function addMigrationChecklistItem(args) {
    return getMutationQueue(args.appRootDirPath).enqueue(async () => {
        const checklist = (await readMigrationChecklist(args.appRootDirPath)) ??
            {
                version: MIGRATION_CHECKLIST_VERSION,
                items: [],
                overallStatus: "active",
                lastUpdated: new Date().toISOString(),
            };
        const item = {
            id: `item_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
            content: args.content,
            status: args.status ?? "pending",
            createdAt: new Date().toISOString(),
            origin: args.origin ?? "clark",
        };
        const state = {
            ...checklist,
            items: [...checklist.items, item],
            lastUpdated: new Date().toISOString(),
        };
        await writeMigrationChecklist(args.appRootDirPath, state);
        return { state, item };
    });
}
export async function updateMigrationChecklistItem(args) {
    return getMutationQueue(args.appRootDirPath).enqueue(async () => {
        const checklist = await readMigrationChecklist(args.appRootDirPath);
        if (!checklist) {
            throw new Error("Migration checklist is not active");
        }
        const itemIndex = checklist.items.findIndex((item) => item.id === args.itemId);
        if (itemIndex === -1) {
            throw new Error(`Checklist item with ID "${args.itemId}" not found`);
        }
        const existingItem = checklist.items[itemIndex];
        if (!existingItem) {
            throw new Error(`Checklist item at index ${itemIndex} is undefined`);
        }
        const nextStatus = args.status ?? existingItem.status;
        const nextFailureReason = args.failureReason ?? existingItem.failureReason;
        // No automatic retry: a write that says `failed` is terminal.
        // Recovery happens through the UI's "Fix with Clark" action, which
        // calls `retryFailedSeedApiItems` to flip rows back to `pending`
        // with a bumped `attemptCount` and the prior reason stashed as
        // `lastFailureReason` for the retry prompt.
        const item = {
            ...existingItem,
            content: args.content ?? existingItem.content,
            status: nextStatus,
            // Only keep failureReason when status is "failed"; clear it on any
            // other transition so a retried item doesn't ship a stale reason.
            failureReason: nextStatus === "failed" ? nextFailureReason : undefined,
            updatedAt: new Date().toISOString(),
        };
        const items = [...checklist.items];
        items[itemIndex] = item;
        const state = {
            ...checklist,
            items,
            lastUpdated: new Date().toISOString(),
        };
        await writeMigrationChecklist(args.appRootDirPath, state);
        return { state, item };
    });
}
/**
 * Flip every `seed_api` item currently in `failed` state back to
 * `pending`, bump its `attemptCount`, and preserve the prior
 * `failureReason` as `lastFailureReason` so the retry prompt can carry
 * it. Used by `/sb-migration-retry-unresolved` to let the user trigger
 * another pass once the auto-retry budget has been spent.
 *
 * Unlike `updateMigrationChecklistItem` this writes directly without
 * going through the auto-retry transform — the user has already
 * consented to an additional attempt, so budget enforcement doesn't
 * apply.
 */
export async function retryFailedSeedApiItems(appRootDirPath) {
    return getMutationQueue(appRootDirPath).enqueue(async () => {
        const checklist = await readMigrationChecklist(appRootDirPath);
        if (!checklist) {
            throw new Error("Migration checklist is not active");
        }
        let retriedCount = 0;
        const now = new Date().toISOString();
        const items = checklist.items.map((item) => {
            if (item.origin !== "seed_api" || item.status !== "failed") {
                return item;
            }
            retriedCount += 1;
            return {
                ...item,
                status: "pending",
                failureReason: undefined,
                lastFailureReason: item.failureReason ?? item.lastFailureReason,
                attemptCount: (item.attemptCount ?? 0) + 1,
                updatedAt: now,
            };
        });
        const state = {
            ...checklist,
            items,
            lastUpdated: now,
        };
        await writeMigrationChecklist(appRootDirPath, state);
        return { state, retriedCount };
    });
}
export function getMigrationChecklistPath(appRootDirPath) {
    return join(appRootDirPath, SCRATCH_DIR_NAME, MIGRATION_CHECKLIST_FILE);
}
export async function markMigrationChecklistCompleted(args) {
    const checklist = await readMigrationChecklist(args.appRootDirPath);
    if (!checklist) {
        throw new Error("Migration checklist is not active");
    }
    const state = {
        ...checklist,
        overallStatus: "completed",
        lastUpdated: new Date().toISOString(),
    };
    await writeMigrationChecklist(args.appRootDirPath, state);
    return state;
}
async function writeMigrationChecklist(appRootDirPath, state) {
    const checklistPath = await resolveChecklistPathForWrite(appRootDirPath);
    await writeFile(checklistPath, JSON.stringify(state, null, 2) + "\n", "utf-8");
}
function getChecklistPathCandidates(appRootDirPath) {
    const primary = getMigrationChecklistPath(appRootDirPath);
    const parentCandidate = getMigrationChecklistPath(dirname(appRootDirPath));
    if (primary === parentCandidate) {
        return [primary];
    }
    return [primary, parentCandidate];
}
async function resolveChecklistPathForWrite(appRootDirPath) {
    const candidates = getChecklistPathCandidates(appRootDirPath);
    for (const candidate of candidates) {
        try {
            await access(candidate);
            return candidate;
        }
        catch {
            // Keep checking.
        }
    }
    for (const candidate of candidates) {
        try {
            await access(dirname(candidate));
            return candidate;
        }
        catch {
            // Keep checking.
        }
    }
    return candidates[0] ?? getMigrationChecklistPath(appRootDirPath);
}
//# sourceMappingURL=migration-checklist.js.map