import crypto from "node:crypto";
/** Task id pattern `T` + digits (create-task, convert-wishlist validation). */
export const TASK_ID_RE = /^T\d+$/;
/** Numeric ordering for `T###` ids (falls back to lexicographic). */
export function compareTaskIdNumeric(a, b) {
    const ma = /^T(\d+)$/.exec(a);
    const mb = /^T(\d+)$/.exec(b);
    if (ma && mb) {
        const da = BigInt(ma[1]);
        const db = BigInt(mb[1]);
        if (da < db) {
            return -1;
        }
        if (da > db) {
            return 1;
        }
        return 0;
    }
    return a.localeCompare(b);
}
/** Next monotonic `T` id from the full task set (includes archived — ids are not reused). */
export function allocateNextTaskId(allTasks) {
    let max = 0n;
    for (const t of allTasks) {
        const m = /^T(\d+)$/.exec(t.id);
        if (m) {
            const v = BigInt(m[1]);
            if (v > max) {
                max = v;
            }
        }
    }
    return `T${(max + 1n).toString()}`;
}
/** Idempotent replay for server-allocated ids: locate prior `create-task` row by clientMutationId. */
export function findIdempotentAllocatedCreate(store, mutationType, clientMutationId) {
    const log = store.getMutationLog();
    for (let idx = log.length - 1; idx >= 0; idx -= 1) {
        const entry = log[idx];
        if (entry.mutationType !== mutationType) {
            continue;
        }
        if (!entry.details || entry.details.clientMutationId !== clientMutationId) {
            continue;
        }
        if (entry.details.allocateId !== true) {
            continue;
        }
        return {
            taskId: entry.taskId,
            payloadDigest: typeof entry.details.payloadDigest === "string" ? entry.details.payloadDigest : undefined
        };
    }
    return null;
}
export const SAFE_METADATA_PATH_RE = /^[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*$/;
export function isRecordLike(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
export function readMetadataPath(metadata, path) {
    if (!metadata || !SAFE_METADATA_PATH_RE.test(path)) {
        return undefined;
    }
    const parts = path.split(".");
    let current = metadata;
    for (const part of parts) {
        if (!isRecordLike(current)) {
            return undefined;
        }
        if (!Object.prototype.hasOwnProperty.call(current, part)) {
            return undefined;
        }
        current = current[part];
    }
    return current;
}
export function stableStringify(value) {
    if (Array.isArray(value)) {
        return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
    }
    if (value && typeof value === "object") {
        const record = value;
        const keys = Object.keys(record).sort();
        return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
    }
    return JSON.stringify(value);
}
export function digestPayload(value) {
    return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
}
export function readIdempotencyValue(args) {
    const raw = args.clientMutationId;
    if (typeof raw !== "string") {
        return undefined;
    }
    const trimmed = raw.trim();
    return trimmed.length > 0 ? trimmed : undefined;
}
export function findIdempotentMutation(store, mutationType, taskId, clientMutationId) {
    const log = store.getMutationLog();
    for (let idx = log.length - 1; idx >= 0; idx -= 1) {
        const entry = log[idx];
        if (entry.mutationType !== mutationType || entry.taskId !== taskId) {
            continue;
        }
        if (!entry.details || entry.details.clientMutationId !== clientMutationId) {
            continue;
        }
        return {
            payloadDigest: typeof entry.details.payloadDigest === "string" ? entry.details.payloadDigest : undefined
        };
    }
    return null;
}
export function nowIso() {
    return new Date().toISOString();
}
export function mutationEvidence(mutationType, taskId, actor, details) {
    return {
        mutationId: `${mutationType}-${taskId}-${nowIso()}-${crypto.randomUUID().slice(0, 8)}`,
        mutationType,
        taskId,
        timestamp: nowIso(),
        actor,
        details
    };
}
export function parseConversionDecomposition(raw) {
    if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
        return { ok: false, message: "convert-wishlist requires 'decomposition' object" };
    }
    const o = raw;
    const rationale = typeof o.rationale === "string" ? o.rationale.trim() : "";
    const boundaries = typeof o.boundaries === "string" ? o.boundaries.trim() : "";
    const dependencyIntent = typeof o.dependencyIntent === "string" ? o.dependencyIntent.trim() : "";
    if (!rationale || !boundaries || !dependencyIntent) {
        return {
            ok: false,
            message: "decomposition requires non-empty rationale, boundaries, and dependencyIntent"
        };
    }
    return { ok: true, value: { rationale, boundaries, dependencyIntent } };
}
export function buildTaskFromConversionPayload(row, timestamp) {
    const id = typeof row.id === "string" ? row.id.trim() : "";
    if (!TASK_ID_RE.test(id)) {
        return { ok: false, message: "Each converted task requires 'id' matching T<number>" };
    }
    const title = typeof row.title === "string" ? row.title.trim() : "";
    if (!title) {
        return { ok: false, message: `Task '${id}' requires non-empty title` };
    }
    const phase = typeof row.phase === "string" ? row.phase.trim() : "";
    if (!phase) {
        return { ok: false, message: `Task '${id}' requires 'phase' for workable tasks` };
    }
    const type = typeof row.type === "string" && row.type.trim() ? row.type.trim() : "workspace-kit";
    const priority = typeof row.priority === "string" && ["P1", "P2", "P3"].includes(row.priority)
        ? row.priority
        : undefined;
    const approach = typeof row.approach === "string" ? row.approach.trim() : "";
    if (!approach) {
        return { ok: false, message: `Task '${id}' requires 'approach'` };
    }
    const technicalScope = Array.isArray(row.technicalScope)
        ? row.technicalScope.filter((x) => typeof x === "string")
        : [];
    const acceptanceCriteria = Array.isArray(row.acceptanceCriteria)
        ? row.acceptanceCriteria.filter((x) => typeof x === "string")
        : [];
    if (technicalScope.length === 0) {
        return { ok: false, message: `Task '${id}' requires non-empty technicalScope array` };
    }
    if (acceptanceCriteria.length === 0) {
        return { ok: false, message: `Task '${id}' requires non-empty acceptanceCriteria array` };
    }
    const task = {
        id,
        title,
        type,
        status: "proposed",
        createdAt: timestamp,
        updatedAt: timestamp,
        priority,
        dependsOn: Array.isArray(row.dependsOn) ? row.dependsOn.filter((x) => typeof x === "string") : undefined,
        unblocks: Array.isArray(row.unblocks) ? row.unblocks.filter((x) => typeof x === "string") : undefined,
        phase,
        approach,
        technicalScope,
        acceptanceCriteria
    };
    if (row.metadata && typeof row.metadata === "object" && !Array.isArray(row.metadata)) {
        task.metadata = { ...row.metadata };
    }
    return { ok: true, task };
}
/** Optional optimistic concurrency token from mutating command JSON (`expectedPlanningGeneration`). */
export function readOptionalExpectedPlanningGeneration(args) {
    const v = args.expectedPlanningGeneration;
    if (typeof v === "number" && Number.isInteger(v) && v >= 0) {
        return v;
    }
    if (typeof v === "string" && /^\d+$/.test(v.trim())) {
        return Number(v.trim());
    }
    return undefined;
}
export function planningConcurrencySaveOpts(args) {
    const g = readOptionalExpectedPlanningGeneration(args);
    return g !== undefined ? { expectedPlanningGeneration: g } : undefined;
}
