import { attachPolicyMeta } from "./attach-planning-response-meta.js";
import { enforcePlanningGenerationPolicy, getPlanningGenerationPolicy, mergePlanningGenerationPolicyWarnings } from "./planning-config.js";
import { digestPayload, findIdempotentMutation, mutationEvidence, readIdempotencyValue, readOptionalExpectedPlanningGeneration } from "./mutation-utils.js";
import { commitCanonicalPlanningEvents } from "./persistence/planning-canonical-mutation-hook.js";
import { draftPlanningPhaseCatalogRemovedEvent, draftPlanningPhaseCatalogUpsertedEvent } from "./persistence/planning-event-draft.js";
import { isPlanningGitSyncPublishActive } from "./persistence/planning-canonical-sync-domains.js";
import { buildOrderedPhaseCatalogList, collectPhaseCatalogHintsFromTasks, deletePhaseCatalogRow, enrichFuturePhaseCatalogWithTaskSummaries, normalizeCatalogShortDescription, phaseCatalogTableAvailable, upsertPhaseCatalogRow, validatePhaseCatalogKey } from "./persistence/phase-catalog-store.js";
import { kitWorkspaceStatusPublicToSnapshot, openSqliteDualForWorkspaceStatus, readKitWorkspaceStatusRow, readWorkspaceStatusSnapshotFromDual } from "./persistence/workspace-status-store.js";
import { parseLeadingPhaseOrdinal, phaseLadderBlocksBeforeCurrent, resolveCanonicalPhase } from "./phase-resolution.js";
import { TaskEngineError } from "./transitions.js";
function nowIso() {
    return new Date().toISOString();
}
const phaseCatalogMutationCache = new Map();
function phaseCatalogMutationCacheKey(workspacePath, phaseKey, clientMutationId) {
    return `${workspacePath}::${phaseKey}::${clientMutationId}`;
}
export async function runListPhaseCatalog(ctx) {
    try {
        const dual = openSqliteDualForWorkspaceStatus(ctx);
        const db = dual.getDatabase();
        const ws = readKitWorkspaceStatusRow(db);
        const taskHints = collectPhaseCatalogHintsFromTasks(dual.taskDocument.tasks);
        const phasesRaw = buildOrderedPhaseCatalogList(db, ws, taskHints);
        const phases = enrichFuturePhaseCatalogWithTaskSummaries(phasesRaw, dual.taskDocument.tasks, ws?.currentKitPhase ?? null);
        const data = {
            schemaVersion: 1,
            phases,
            supported: phaseCatalogTableAvailable(db)
        };
        attachPolicyMeta(data, ctx, dual.getPlanningGeneration());
        return {
            ok: true,
            code: "phase-catalog-listed",
            message: phases.length === 0
                ? "Phase catalog is empty"
                : phases.length === 1
                    ? "Listed 1 phase catalog entry"
                    : `Listed ${phases.length} phase catalog entries`,
            data
        };
    }
    catch (e) {
        if (e instanceof TaskEngineError) {
            return { ok: false, code: e.code, message: e.message };
        }
        return {
            ok: false,
            code: "storage-read-error",
            message: `Failed to list phase catalog: ${e.message}`
        };
    }
}
export async function runUpsertPhaseCatalogEntry(ctx, planning, store, rawArgs) {
    const pkRaw = typeof rawArgs.phaseKey === "string" ? rawArgs.phaseKey : "";
    const phaseKey = validatePhaseCatalogKey(pkRaw);
    if (!phaseKey) {
        return {
            ok: false,
            code: "invalid-task-schema",
            message: "upsert-phase-catalog-entry requires phaseKey (non-empty; letters, digits, dot, underscore, hyphen; max 64 chars)"
        };
    }
    const remove = rawArgs.remove === true;
    const actorRaw = typeof rawArgs.actor === "string" ? rawArgs.actor.trim() : "";
    const actor = actorRaw.length > 0 ? actorRaw : undefined;
    const clientMutationId = readIdempotencyValue(rawArgs);
    const descNorm = normalizeCatalogShortDescription(rawArgs.shortDescription);
    if (!descNorm.ok) {
        return { ok: false, code: "invalid-task-schema", message: descNorm.message };
    }
    if (remove && !descNorm.omit && rawArgs.shortDescription !== undefined) {
        return {
            ok: false,
            code: "invalid-task-schema",
            message: "upsert-phase-catalog-entry cannot combine remove:true with shortDescription"
        };
    }
    const workspaceStatus = readWorkspaceStatusSnapshotFromDual(planning.sqliteDual);
    const phaseRes = resolveCanonicalPhase({
        effectiveConfig: ctx.effectiveConfig,
        workspaceStatus
    });
    const assignedOrd = parseLeadingPhaseOrdinal(phaseKey);
    const wsOrd = phaseRes.canonicalPhaseKey !== undefined && phaseRes.canonicalPhaseKey !== null
        ? parseLeadingPhaseOrdinal(phaseRes.canonicalPhaseKey)
        : null;
    if (phaseLadderBlocksBeforeCurrent(ctx.effectiveConfig) &&
        assignedOrd !== null &&
        wsOrd !== null &&
        assignedOrd < wsOrd) {
        return {
            ok: false,
            code: "phase-target-before-current-workspace-phase",
            message: `upsert-phase-catalog-entry rejects '${phaseKey}' because its leading phase number (${assignedOrd}) is before workspace current kit phase (${phaseRes.canonicalPhaseKey})`
        };
    }
    const gate = enforcePlanningGenerationPolicy(getPlanningGenerationPolicy({
        effectiveConfig: ctx.effectiveConfig
    }), rawArgs);
    if (!gate.ok) {
        return { ok: false, code: gate.code, message: gate.message };
    }
    const dual = planning.sqliteDual;
    const db = dual.getDatabase();
    if (!phaseCatalogTableAvailable(db)) {
        return {
            ok: false,
            code: "phase-catalog-unavailable",
            message: "kit_phase_catalog requires planning SQLite user_version 23+ (upgrade workspace-kit / reopen DB)"
        };
    }
    let nextShort;
    if (remove) {
        nextShort = null;
    }
    else if (descNorm.omit) {
        const row = db
            .prepare("SELECT short_description FROM kit_phase_catalog WHERE phase_key = ?")
            .get(phaseKey);
        nextShort = row?.short_description ?? null;
    }
    else {
        nextShort = descNorm.value;
    }
    const payloadDigest = digestPayload({
        command: "upsert-phase-catalog-entry",
        phaseKey,
        remove,
        shortDescription: nextShort
    });
    if (clientMutationId) {
        const cacheKey = phaseCatalogMutationCacheKey(ctx.workspacePath, phaseKey, clientMutationId);
        const cachedDigest = phaseCatalogMutationCache.get(cacheKey);
        if (cachedDigest !== undefined) {
            if (cachedDigest !== payloadDigest) {
                return {
                    ok: false,
                    code: "idempotency-key-conflict",
                    message: `clientMutationId '${clientMutationId}' was already used for a different upsert-phase-catalog-entry payload on ${phaseKey}`
                };
            }
            const wsReplay = readKitWorkspaceStatusRow(db);
            const taskHintsReplay = collectPhaseCatalogHintsFromTasks(dual.taskDocument.tasks);
            const phasesRawReplay = buildOrderedPhaseCatalogList(db, wsReplay, taskHintsReplay);
            const phasesReplay = enrichFuturePhaseCatalogWithTaskSummaries(phasesRawReplay, dual.taskDocument.tasks, wsReplay?.currentKitPhase ?? null);
            const replayData = {
                schemaVersion: 1,
                phaseKey,
                removed: remove,
                shortDescription: remove ? null : nextShort,
                phases: phasesReplay,
                workspaceStatus: wsReplay ? kitWorkspaceStatusPublicToSnapshot(wsReplay) : null,
                replayed: true
            };
            attachPolicyMeta(replayData, ctx, dual.getPlanningGeneration());
            return {
                ok: true,
                code: remove ? "phase-catalog-entry-remove-idempotent-replay" : "phase-catalog-entry-upsert-idempotent-replay",
                message: `Idempotent upsert-phase-catalog-entry replay for '${phaseKey}'`,
                data: replayData
            };
        }
        const prior = findIdempotentMutation(store, "upsert-phase-catalog-entry", phaseKey, clientMutationId);
        if (prior) {
            if (prior.payloadDigest !== payloadDigest) {
                return {
                    ok: false,
                    code: "idempotency-key-conflict",
                    message: `clientMutationId '${clientMutationId}' was already used for a different upsert-phase-catalog-entry payload on ${phaseKey}`
                };
            }
            const wsReplay = readKitWorkspaceStatusRow(db);
            const taskHintsReplay = collectPhaseCatalogHintsFromTasks(dual.taskDocument.tasks);
            const phasesRawReplay = buildOrderedPhaseCatalogList(db, wsReplay, taskHintsReplay);
            const phasesReplay = enrichFuturePhaseCatalogWithTaskSummaries(phasesRawReplay, dual.taskDocument.tasks, wsReplay?.currentKitPhase ?? null);
            const replayData = {
                schemaVersion: 1,
                phaseKey,
                removed: remove,
                shortDescription: remove ? null : nextShort,
                phases: phasesReplay,
                workspaceStatus: wsReplay ? kitWorkspaceStatusPublicToSnapshot(wsReplay) : null,
                replayed: true
            };
            attachPolicyMeta(replayData, ctx, dual.getPlanningGeneration());
            return {
                ok: true,
                code: remove ? "phase-catalog-entry-remove-idempotent-replay" : "phase-catalog-entry-upsert-idempotent-replay",
                message: `Idempotent upsert-phase-catalog-entry replay for '${phaseKey}'`,
                data: replayData
            };
        }
    }
    try {
        const gitCanonical = isPlanningGitSyncPublishActive(ctx, "phase_catalog");
        const draftCtx = {
            commandName: "upsert-phase-catalog-entry",
            moduleId: "task-engine",
            actorId: actor,
            clientMutationId,
            phaseKey
        };
        const updatedAt = nowIso();
        const planningEvent = remove
            ? draftPlanningPhaseCatalogRemovedEvent({ phaseKey, ctx: draftCtx })
            : draftPlanningPhaseCatalogUpsertedEvent({
                phaseKey,
                shortDescription: nextShort,
                updatedAt,
                ctx: draftCtx
            });
        if (!gitCanonical) {
            store.addMutationEvidence(mutationEvidence("upsert-phase-catalog-entry", phaseKey, actor, {
                phaseKey,
                remove,
                shortDescription: nextShort,
                clientMutationId,
                payloadDigest
            }));
            await store.save({
                expectedPlanningGeneration: readOptionalExpectedPlanningGeneration(rawArgs),
                beforePersistInSqliteTransaction: () => {
                    if (remove) {
                        deletePhaseCatalogRow(db, phaseKey);
                    }
                    else {
                        upsertPhaseCatalogRow(db, phaseKey, nextShort, updatedAt);
                    }
                }
            });
        }
        else {
            const canonical = await commitCanonicalPlanningEvents({
                ctx,
                store,
                planning,
                events: [planningEvent],
                policyApproval: rawArgs.policyApproval
            });
            if (canonical && !canonical.ok) {
                return canonical;
            }
            await store.load();
        }
        if (clientMutationId) {
            phaseCatalogMutationCache.set(phaseCatalogMutationCacheKey(ctx.workspacePath, phaseKey, clientMutationId), payloadDigest);
        }
    }
    catch (e) {
        if (e instanceof TaskEngineError) {
            return { ok: false, code: e.code, message: e.message, data: e.details };
        }
        return {
            ok: false,
            code: "storage-write-error",
            message: `upsert-phase-catalog-entry failed: ${e.message}`
        };
    }
    const wsAfter = readKitWorkspaceStatusRow(db);
    const taskHintsAfter = collectPhaseCatalogHintsFromTasks(dual.taskDocument.tasks);
    const phasesRaw = buildOrderedPhaseCatalogList(db, wsAfter, taskHintsAfter);
    const phases = enrichFuturePhaseCatalogWithTaskSummaries(phasesRaw, dual.taskDocument.tasks, wsAfter?.currentKitPhase ?? null);
    const data = {
        schemaVersion: 1,
        phaseKey,
        removed: remove,
        shortDescription: remove ? null : nextShort,
        phases,
        workspaceStatus: wsAfter ? kitWorkspaceStatusPublicToSnapshot(wsAfter) : null
    };
    mergePlanningGenerationPolicyWarnings(data, gate.warnings);
    attachPolicyMeta(data, ctx, dual.getPlanningGeneration());
    return {
        ok: true,
        code: remove ? "phase-catalog-entry-removed" : "phase-catalog-entry-upserted",
        message: remove ? `Removed phase catalog entry '${phaseKey}'` : `Upserted phase catalog entry '${phaseKey}'`,
        data
    };
}
