import { randomUUID } from "node:crypto";
import { canonicalJson, sha256 } from "./canonical.js";
import { HatchError } from "./errors.js";
const GAP_KINDS = new Set([
    "platform-adapter-missing",
    "update-incompatible",
    "rollback-unavailable",
    "verification-failed",
    "workflow-gap",
]);
const SAFE_CODE = /^[a-z][a-z0-9._-]{0,79}$/u;
export function createCapabilityGapDraft(input) {
    if (!GAP_KINDS.has(input.gapKind))
        throw new HatchError("gap_invalid", "gapKind is unsupported.");
    const productId = safeString(input.productId, "productId", 120);
    const platform = safeString(input.platform, "platform", 80);
    const architecture = safeString(input.architecture, "architecture", 80);
    if (!Number.isSafeInteger(input.installedBuild) || input.installedBuild <= 0) {
        throw new HatchError("gap_invalid", "installedBuild must be a positive safe integer.");
    }
    const candidateBuild = input.candidateBuild ?? null;
    if (candidateBuild !== null && (!Number.isSafeInteger(candidateBuild) || candidateBuild <= 0)) {
        throw new HatchError("gap_invalid", "candidateBuild must be null or a positive safe integer.");
    }
    if (!Array.isArray(input.evidence) || input.evidence.length === 0 || input.evidence.length > 16) {
        throw new HatchError("gap_invalid", "evidence must contain between one and sixteen coarse classifications.");
    }
    const evidence = input.evidence.map(({ code, count }) => {
        if (!SAFE_CODE.test(code) || !Number.isSafeInteger(count) || count <= 0 || count > 1_000_000) {
            throw new HatchError("gap_invalid", "Gap evidence must contain safe classification codes and positive counts.");
        }
        return { code, count };
    }).sort((left, right) => left.code.localeCompare(right.code));
    if (new Set(evidence.map(({ code }) => code)).size !== evidence.length) {
        throw new HatchError("gap_invalid", "Gap evidence codes must be unique.");
    }
    const digestable = {
        type: "ok.hatch.capability-gap.v1",
        schemaVersion: 1,
        gapId: input.gapId ?? `gap_${randomUUID()}`,
        gapKind: input.gapKind,
        productId,
        platform,
        architecture,
        installedBuild: input.installedBuild,
        candidateBuild,
        evidence,
        reporting: "local-draft",
        observedAt: (input.observedAt ?? new Date()).toISOString(),
    };
    return { ...digestable, gapDigest: sha256(canonicalJson(digestable)) };
}
function safeString(value, label, max) {
    const normalized = String(value ?? "").trim();
    if (!normalized || normalized.length > max
        || /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u.test(normalized)) {
        throw new HatchError("gap_invalid", `${label} is invalid.`);
    }
    return normalized;
}
