// Branded ID types for the Quality Intelligence contract surface (Epic #270, Issue #277).
//
// Pure types and pure runtime validators only — no IO, no clock reads, no hashing, no
// randomness. Leaf-package rule (ADR-0019 direction 1): no `@oscharko-dev/keiko-*` imports
// may appear in this module.
//
// Branding follows the same `unique symbol` phantom-property style as
// `local-knowledge.ts` (KnowledgeCapsuleId etc.): the brand carrier never lands at
// runtime, so values survive JSON round-trips intact, but a bare `string` is not
// assignable to a branded type without an explicit `asX(...)` construction step.
//
// Each constructor enforces:
//   * non-empty after trim
//   * NFKC normalisation (rejected if normalisation would change the input — callers
//     must normalise themselves, so a brand never silently rewrites a value)
//   * max length 256
//   * no path-traversal segment ("..", "/", "\")
//   * no NUL or other ASCII control characters
//
// These rules deliberately match the audit-ledger `assertValidRunId` shape (ADR-0010,
// memory: keiko-issue10-audit-ledger) so an ID minted in QI can be safely composed
// with that surface in #274 without re-validation.
// ─── Validation primitive ──────────────────────────────────────────────────────
const QI_ID_MAX_LENGTH = 256;
// Path-traversal segments and structural separators. NUL plus C0/C1 control ranges
// are caught by the control-character check below.
const QI_ID_FORBIDDEN_FRAGMENTS = ["..", "/", "\\"];
const hasControlCharacter = (value) => {
    for (let index = 0; index < value.length; index += 1) {
        const code = value.charCodeAt(index);
        // C0 controls 0x00–0x1F, DEL 0x7F, C1 controls 0x80–0x9F.
        if (code <= 0x1f || code === 0x7f || (code >= 0x80 && code <= 0x9f)) {
            return true;
        }
    }
    return false;
};
const hasForbiddenFragment = (value) => QI_ID_FORBIDDEN_FRAGMENTS.some((fragment) => value.includes(fragment));
/**
 * Validates a candidate string is acceptable as a QI branded id, returning a typed
 * error message on rejection. Pure — no IO. Exported only for tests; production
 * callers should use the `asX` constructors.
 */
export const validateQualityIntelligenceIdString = (value, kind) => {
    if (typeof value !== "string") {
        return { ok: false, reason: `${kind} must be a string` };
    }
    if (value.length === 0) {
        return { ok: false, reason: `${kind} must not be empty` };
    }
    if (value.trim().length === 0) {
        return { ok: false, reason: `${kind} must not be whitespace-only` };
    }
    if (value.length > QI_ID_MAX_LENGTH) {
        return { ok: false, reason: `${kind} exceeds max length ${String(QI_ID_MAX_LENGTH)}` };
    }
    if (value.normalize("NFKC") !== value) {
        return { ok: false, reason: `${kind} must be NFKC-normalised` };
    }
    if (hasControlCharacter(value)) {
        return { ok: false, reason: `${kind} contains control characters` };
    }
    if (hasForbiddenFragment(value)) {
        return { ok: false, reason: `${kind} contains forbidden path fragment` };
    }
    return { ok: true };
};
/**
 * Validate `value` and return it unchanged. Throws `TypeError` on rejection. The
 * caller casts the returned string to the appropriate branded type — keeping the
 * cast at the constructor site rather than inside this helper avoids an
 * unnecessary type parameter (lint: no-unnecessary-type-parameters).
 */
const construct = (value, kind) => {
    const result = validateQualityIntelligenceIdString(value, kind);
    if (!result.ok) {
        throw new TypeError(`Invalid ${kind}: ${result.reason}`);
    }
    return value;
};
// ─── Constructors ──────────────────────────────────────────────────────────────
export const asQualityIntelligenceRunId = (value) => construct(value, "QualityIntelligenceRunId");
export const asQualityIntelligenceTestCaseId = (value) => construct(value, "QualityIntelligenceTestCaseId");
export const asQualityIntelligenceCoverageMapId = (value) => construct(value, "QualityIntelligenceCoverageMapId");
export const asQualityIntelligenceValidationFindingId = (value) => construct(value, "QualityIntelligenceValidationFindingId");
export const asQualityIntelligenceReviewRecordId = (value) => construct(value, "QualityIntelligenceReviewRecordId");
export const asQualityIntelligenceExportBundleId = (value) => construct(value, "QualityIntelligenceExportBundleId");
export const asQualityIntelligenceSourceEnvelopeId = (value) => construct(value, "QualityIntelligenceSourceEnvelopeId");
export const asQualityIntelligenceEvidenceAtomId = (value) => construct(value, "QualityIntelligenceEvidenceAtomId");
export const asQualityIntelligenceAuditSummaryId = (value) => construct(value, "QualityIntelligenceAuditSummaryId");
