import EncryptionError from "../../domain/errors/EncryptionError.js";
const LEGACY_SCHEME_VALUES = new Set([
    'whole-v1',
    'whole-v2',
    'framed-v1',
    'framed-v2',
    'convergent-v1',
]);
const REQUIRED_KEY_BYTES = 32;
export default class CasContentEncryptionPolicy {
    _enabled;
    _encryptionKey;
    _scheme;
    _frameBytes;
    _diagnostics;
    constructor(fields) {
        this._enabled = fields.enabled;
        this._encryptionKey = fields.encryptionKey;
        this._scheme = fields.scheme;
        this._frameBytes = fields.frameBytes;
        this._diagnostics = fields.diagnostics;
        Object.freeze(this);
    }
    static disabled() {
        return new CasContentEncryptionPolicy({ enabled: false });
    }
    static fromResolvedVaultKey(options) {
        const scheme = normalizeScheme(options.scheme);
        const frameBytes = normalizeFrameBytes(scheme, options.frameBytes);
        const diagnostics = validateVaultWitness(options.vault);
        return new CasContentEncryptionPolicy(enabledFields({
            encryptionKey: copyValidatedKey(options.encryptionKey, diagnostics),
            scheme,
            frameBytes,
            diagnostics,
        }));
    }
    /** @internal Raw keys may only enter here after a caller-owned boundary resolved them. */
    static fromInternalResolvedKey(options) {
        const scheme = normalizeScheme(options.scheme ?? 'whole');
        const frameBytes = normalizeFrameBytes(scheme, options.frameBytes);
        return new CasContentEncryptionPolicy(enabledFields({
            encryptionKey: copyValidatedKey(options.encryptionKey, null),
            scheme,
            frameBytes,
            diagnostics: undefined,
        }));
    }
    get enabled() {
        return this._enabled;
    }
    get scheme() {
        return this._scheme ?? null;
    }
    vaultDiagnostics() {
        return this._diagnostics ?? null;
    }
    toStoreOptions() {
        if (!this._enabled) {
            return {};
        }
        return {
            encryptionKey: this._copyKey(),
            encryption: this._storeEncryptionOptions(),
        };
    }
    toRestoreOptions() {
        if (!this._enabled) {
            return {};
        }
        return { encryptionKey: this._copyKey() };
    }
    _copyKey() {
        if (this._encryptionKey === undefined) {
            throw encryptionPolicyError('CAS content encryption is enabled without a resolved key', 'E_CAS_ENCRYPTION_KEY_MISSING');
        }
        return new Uint8Array(this._encryptionKey);
    }
    _storeEncryptionOptions() {
        const scheme = this._requireScheme();
        if (scheme === 'convergent') {
            return { scheme, convergent: true };
        }
        if (scheme === 'framed' && this._frameBytes !== undefined) {
            return { scheme, frameBytes: this._frameBytes };
        }
        return { scheme };
    }
    _requireScheme() {
        if (this._scheme === undefined) {
            throw encryptionPolicyError('CAS content encryption is enabled without a current scheme', 'E_CAS_ENCRYPTION_SCHEME_MISSING');
        }
        return this._scheme;
    }
}
export function mapCasContentEncryptionError(error, surface) {
    if (error instanceof EncryptionError) {
        return error;
    }
    const code = errorCode(error);
    const message = errorMessage(error);
    const kind = classifyCasContentEncryptionError(code, message);
    if (kind === 'legacy-scheme') {
        return encryptionPolicyError('Legacy git-cas encryption schemes require migration before git-warp can restore this CAS content', 'E_CAS_LEGACY_ENCRYPTION_SCHEME', { surface, upstreamCode: code, upstreamMessage: message });
    }
    if (kind === 'wrong-passphrase') {
        return encryptionPolicyError('git-cas vault passphrase verification failed while resolving CAS content encryption', 'E_CAS_VAULT_PASSPHRASE_FAILED', { surface, upstreamCode: code, upstreamMessage: message });
    }
    if (kind === 'missing-vault-metadata') {
        return encryptionPolicyError('git-cas vault metadata is missing or invalid for encrypted CAS content', 'E_CAS_VAULT_METADATA_MISSING', { surface, upstreamCode: code, upstreamMessage: message });
    }
    return null;
}
function enabledFields(input) {
    const base = input.diagnostics === undefined
        ? { enabled: true, encryptionKey: input.encryptionKey, scheme: input.scheme }
        : { enabled: true, encryptionKey: input.encryptionKey, scheme: input.scheme, diagnostics: input.diagnostics };
    if (input.frameBytes === undefined) {
        return base;
    }
    return { ...base, frameBytes: input.frameBytes };
}
function normalizeScheme(scheme) {
    if (scheme === 'whole' || scheme === 'framed' || scheme === 'convergent') {
        return scheme;
    }
    if (LEGACY_SCHEME_VALUES.has(scheme)) {
        throw encryptionPolicyError(`Legacy git-cas encryption scheme "${scheme}" is not accepted by git-warp current writes`, 'E_CAS_LEGACY_ENCRYPTION_SCHEME', { scheme, migration: 'Run the git-cas legacy encryption migration before writing through git-warp.' });
    }
    throw encryptionPolicyError(`Unsupported git-cas encryption scheme "${scheme}"`, 'E_CAS_ENCRYPTION_SCHEME_UNSUPPORTED', { scheme });
}
function normalizeFrameBytes(scheme, frameBytes) {
    if (frameBytes === undefined) {
        return undefined;
    }
    if (scheme !== 'framed') {
        throw encryptionPolicyError(`encryption.frameBytes is only valid for framed CAS content encryption, not ${scheme}`, 'E_CAS_ENCRYPTION_FRAME_BYTES_UNSUPPORTED', { scheme, frameBytes });
    }
    if (!Number.isSafeInteger(frameBytes) || frameBytes < 1) {
        throw encryptionPolicyError('encryption.frameBytes must be a positive safe integer', 'E_CAS_ENCRYPTION_FRAME_BYTES_INVALID', { scheme, frameBytes });
    }
    return frameBytes;
}
function validateVaultWitness(witness) {
    assertVaultVerification(witness);
    assertNonEmpty(witness.vaultSlug, 'vaultSlug', 'E_CAS_VAULT_SLUG_INVALID');
    assertNonEmpty(witness.keyId, 'keyId', 'E_CAS_VAULT_KEY_ID_INVALID');
    assertVaultRotationCounters(witness);
    assertVaultRotationOpen(witness);
    return vaultDiagnosticsFrom(witness);
}
function assertVaultVerification(witness) {
    if (witness.verification === 'failed-passphrase') {
        throw encryptionPolicyError('git-cas vault passphrase verification failed while resolving CAS content encryption', 'E_CAS_VAULT_PASSPHRASE_FAILED', { vaultSlug: witness.vaultSlug, keyId: witness.keyId });
    }
    if (witness.verification === 'missing-metadata') {
        throw encryptionPolicyError('git-cas vault metadata is required before enabling CAS content encryption', 'E_CAS_VAULT_METADATA_MISSING', { vaultSlug: witness.vaultSlug, keyId: witness.keyId });
    }
}
function assertVaultRotationCounters(witness) {
    assertNonNegativeInteger(witness.rotationEpoch, 'rotationEpoch', 'E_CAS_VAULT_ROTATION_INVALID');
    assertNonNegativeInteger(witness.encryptionCount, 'encryptionCount', 'E_CAS_VAULT_ROTATION_INVALID');
    assertPositiveInteger(witness.encryptionCountLimit, 'encryptionCountLimit', 'E_CAS_VAULT_ROTATION_INVALID');
}
function assertVaultRotationOpen(witness) {
    if (witness.encryptionCount >= witness.encryptionCountLimit) {
        throw encryptionPolicyError('git-cas vault encryption count reached its rotation limit; rotate before writing more encrypted CAS content', 'E_CAS_VAULT_ROTATION_REQUIRED', {
            vaultSlug: witness.vaultSlug,
            keyId: witness.keyId,
            encryptionCount: witness.encryptionCount,
            encryptionCountLimit: witness.encryptionCountLimit,
        });
    }
}
function vaultDiagnosticsFrom(witness) {
    return Object.freeze({
        vaultSlug: witness.vaultSlug,
        keyId: witness.keyId,
        rotationEpoch: witness.rotationEpoch,
        encryptionCount: witness.encryptionCount,
        encryptionCountLimit: witness.encryptionCountLimit,
        privacyMode: witness.privacyMode,
    });
}
function validateKey(encryptionKey, diagnostics) {
    if (!(encryptionKey instanceof Uint8Array) || encryptionKey.byteLength !== REQUIRED_KEY_BYTES) {
        throw encryptionPolicyError(`CAS content encryption requires a ${REQUIRED_KEY_BYTES}-byte resolved key`, 'E_CAS_ENCRYPTION_KEY_INVALID', diagnostics === null ? {} : { vaultSlug: diagnostics.vaultSlug, keyId: diagnostics.keyId });
    }
    return encryptionKey;
}
function copyValidatedKey(encryptionKey, diagnostics) {
    return new Uint8Array(validateKey(encryptionKey, diagnostics));
}
function assertNonEmpty(value, field, code) {
    if (value.length === 0) {
        throw encryptionPolicyError(`git-cas vault ${field} must not be empty`, code, { field });
    }
}
function assertNonNegativeInteger(value, field, code) {
    if (!Number.isSafeInteger(value) || value < 0) {
        throw encryptionPolicyError(`git-cas vault ${field} must be a non-negative safe integer`, code, { field, value });
    }
}
function assertPositiveInteger(value, field, code) {
    if (!Number.isSafeInteger(value) || value < 1) {
        throw encryptionPolicyError(`git-cas vault ${field} must be a positive safe integer`, code, { field, value });
    }
}
function errorCode(error) {
    return hasStringCode(error) ? error.code : null;
}
function errorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
function isLegacySchemeMessage(message) {
    const normalized = message.toLowerCase();
    return normalized.includes('legacy encryption scheme') || normalized.includes('legacy_scheme');
}
function isWrongPassphraseMessage(message) {
    return message.toLowerCase().includes('vault passphrase verification failed');
}
function isMissingVaultMetadataMessage(message) {
    const normalized = message.toLowerCase();
    return normalized.includes('.vault.json')
        || normalized.includes('vault metadata')
        || normalized.includes('privacy index metadata is missing');
}
function classifyCasContentEncryptionError(code, message) {
    if (isLegacyCasEncryptionError(code, message)) {
        return 'legacy-scheme';
    }
    if (isWrongPassphraseMessage(message)) {
        return 'wrong-passphrase';
    }
    if (isMissingVaultMetadataError(code, message)) {
        return 'missing-vault-metadata';
    }
    return 'none';
}
function isLegacyCasEncryptionError(code, message) {
    if (code === 'LEGACY_SCHEME') {
        return true;
    }
    return isLegacySchemeMessage(message);
}
function isMissingVaultMetadataError(code, message) {
    if (code === 'VAULT_METADATA_INVALID') {
        return true;
    }
    return isMissingVaultMetadataMessage(message);
}
function hasStringCode(error) {
    return typeof error === 'object'
        && error !== null
        && 'code' in error
        && typeof error.code === 'string';
}
function encryptionPolicyError(message, code, context = {}) {
    return new EncryptionError(message, { code, context });
}
