// SPDX-License-Identifier: Apache-2.0
// Copyright 2024-2026 Vorion LLC
import { DataClassification as Classification, } from './types.js';
// =============================================================================
// Metadata Storage
// =============================================================================
/**
 * Symbol for storing encrypted field metadata
 */
const ENCRYPTED_FIELDS_KEY = Symbol('encryptedFields');
/**
 * Get or create the encrypted fields metadata array for a class
 */
function getOrCreateMetadataArray(target) {
    // Use Object.getOwnPropertyDescriptor to check for own property
    const existing = Object.getOwnPropertyDescriptor(target, ENCRYPTED_FIELDS_KEY);
    if (existing && Array.isArray(existing.value)) {
        return existing.value;
    }
    // Check prototype chain for inherited metadata
    const inherited = target[ENCRYPTED_FIELDS_KEY];
    const baseArray = Array.isArray(inherited)
        ? [...inherited]
        : [];
    // Define own property
    Object.defineProperty(target, ENCRYPTED_FIELDS_KEY, {
        value: baseArray,
        enumerable: false,
        writable: true,
        configurable: true,
    });
    return baseArray;
}
/**
 * Get encrypted fields metadata from a class
 */
function getMetadataArray(target) {
    const metadata = target[ENCRYPTED_FIELDS_KEY];
    return Array.isArray(metadata) ? metadata : [];
}
// =============================================================================
// Decorators
// =============================================================================
/**
 * Decorator to mark a field for encryption
 *
 * Fields marked with this decorator will be automatically encrypted
 * when stored and decrypted when retrieved, based on their classification.
 *
 * @param classification - The data classification level
 * @param options - Additional encryption options
 *
 * @example
 * ```typescript
 * class Patient {
 *   @Encrypted(DataClassification.RESTRICTED)
 *   medicalRecordNumber: string;
 *
 *   @Encrypted(DataClassification.CONFIDENTIAL, {
 *     algorithm: EncryptionAlgorithm.CHACHA20_POLY1305,
 *     description: 'Patient phone number for contact'
 *   })
 *   phoneNumber: string;
 * }
 * ```
 */
export function Encrypted(classification, options = {}) {
    return function (target, propertyKey) {
        if (typeof propertyKey !== 'string') {
            throw new Error('@Encrypted decorator can only be applied to string-named properties');
        }
        const metadata = getOrCreateMetadataArray(target.constructor.prototype);
        // Check if this field already has metadata (from parent class)
        const existingIndex = metadata.findIndex((m) => m.propertyKey === propertyKey);
        const fieldMetadata = {
            propertyKey,
            classification,
            encrypted: true,
            deterministic: false,
            algorithm: options.algorithm,
            keyDerivationSuffix: options.keyDerivationSuffix,
            description: options.description,
        };
        if (existingIndex >= 0) {
            // Override inherited metadata
            metadata[existingIndex] = fieldMetadata;
        }
        else {
            metadata.push(fieldMetadata);
        }
    };
}
/**
 * Decorator to mark a field for searchable (deterministic) encryption
 *
 * Fields marked with this decorator use deterministic encryption,
 * which allows searching for exact matches while still encrypting data.
 *
 * Security note: Deterministic encryption is less secure than random IV
 * encryption because identical plaintexts produce identical ciphertexts.
 * Use only when searchability is required.
 *
 * @param options - Searchable encryption options
 *
 * @example
 * ```typescript
 * class User {
 *   @SearchableEncrypted({
 *     classification: DataClassification.CONFIDENTIAL,
 *     description: 'User email for login lookup'
 *   })
 *   email: string;
 *
 *   @SearchableEncrypted()
 *   externalId: string;
 * }
 * ```
 */
export function SearchableEncrypted(options = {}) {
    return function (target, propertyKey) {
        if (typeof propertyKey !== 'string') {
            throw new Error('@SearchableEncrypted decorator can only be applied to string-named properties');
        }
        const metadata = getOrCreateMetadataArray(target.constructor.prototype);
        // Check if this field already has metadata
        const existingIndex = metadata.findIndex((m) => m.propertyKey === propertyKey);
        const fieldMetadata = {
            propertyKey,
            classification: options.classification ?? Classification.CONFIDENTIAL,
            encrypted: true,
            deterministic: true,
            keyDerivationSuffix: options.keyDerivationSuffix,
            description: options.description,
        };
        if (existingIndex >= 0) {
            metadata[existingIndex] = fieldMetadata;
        }
        else {
            metadata.push(fieldMetadata);
        }
    };
}
/**
 * Decorator to explicitly mark a field as not encrypted
 *
 * Useful when a parent class has encryption but a subclass field should not be encrypted.
 *
 * @param classification - The data classification (for documentation/policy)
 *
 * @example
 * ```typescript
 * class PublicProfile extends User {
 *   @NotEncrypted(DataClassification.PUBLIC)
 *   displayName: string;
 * }
 * ```
 */
export function NotEncrypted(classification) {
    return function (target, propertyKey) {
        if (typeof propertyKey !== 'string') {
            throw new Error('@NotEncrypted decorator can only be applied to string-named properties');
        }
        const metadata = getOrCreateMetadataArray(target.constructor.prototype);
        const existingIndex = metadata.findIndex((m) => m.propertyKey === propertyKey);
        const fieldMetadata = {
            propertyKey,
            classification,
            encrypted: false,
            deterministic: false,
        };
        if (existingIndex >= 0) {
            metadata[existingIndex] = fieldMetadata;
        }
        else {
            metadata.push(fieldMetadata);
        }
    };
}
// =============================================================================
// Metadata Access Functions
// =============================================================================
/**
 * Get all encrypted field metadata from a class
 *
 * @param target - The class constructor or instance
 * @returns Array of encrypted field metadata
 *
 * @example
 * ```typescript
 * class User {
 *   @Encrypted(DataClassification.RESTRICTED)
 *   ssn: string;
 *
 *   @SearchableEncrypted()
 *   email: string;
 * }
 *
 * const fields = getEncryptedFields(User);
 * // [
 * //   { propertyKey: 'ssn', classification: 'restricted', encrypted: true, deterministic: false },
 * //   { propertyKey: 'email', classification: 'confidential', encrypted: true, deterministic: true }
 * // ]
 * ```
 */
export function getEncryptedFields(target) {
    const prototype = typeof target === 'function' ? target.prototype : Object.getPrototypeOf(target);
    return getMetadataArray(prototype);
}
/**
 * Check if a class has any encrypted fields
 *
 * @param target - The class constructor or instance
 * @returns True if the class has encrypted fields
 */
export function hasEncryptedFields(target) {
    const fields = getEncryptedFields(target);
    return fields.some((f) => f.encrypted);
}
/**
 * Get metadata for a specific field
 *
 * @param target - The class constructor or instance
 * @param propertyKey - The property name
 * @returns Field metadata or undefined
 */
export function getFieldMetadata(target, propertyKey) {
    const fields = getEncryptedFields(target);
    return fields.find((f) => f.propertyKey === propertyKey);
}
/**
 * Check if a specific field is encrypted
 *
 * @param target - The class constructor or instance
 * @param propertyKey - The property name
 * @returns True if the field is marked for encryption
 */
export function isFieldEncrypted(target, propertyKey) {
    const metadata = getFieldMetadata(target, propertyKey);
    return metadata?.encrypted ?? false;
}
/**
 * Check if a specific field uses deterministic encryption
 *
 * @param target - The class constructor or instance
 * @param propertyKey - The property name
 * @returns True if the field uses deterministic encryption
 */
export function isFieldSearchable(target, propertyKey) {
    const metadata = getFieldMetadata(target, propertyKey);
    return metadata?.deterministic ?? false;
}
// =============================================================================
// Policy Generation
// =============================================================================
/**
 * Generate a FieldEncryptionPolicy from a decorated class
 *
 * This creates a policy object that can be used with the encryption service
 * to encrypt/decrypt objects based on decorator metadata.
 *
 * @param target - The class constructor
 * @param entityName - Optional entity name (defaults to class name)
 * @returns Field encryption policy
 *
 * @example
 * ```typescript
 * class User {
 *   @Encrypted(DataClassification.RESTRICTED)
 *   ssn: string;
 *
 *   @SearchableEncrypted()
 *   email: string;
 * }
 *
 * const policy = generatePolicy(User);
 * // {
 * //   entityName: 'User',
 * //   fields: [
 * //     { fieldName: 'ssn', classification: 'restricted', encrypted: true },
 * //     { fieldName: 'email', classification: 'confidential', encrypted: true, deterministic: true }
 * //   ]
 * // }
 *
 * // Use with encryption service
 * const encrypted = await service.encryptObject(user, policy);
 * ```
 */
export function generatePolicy(target, entityName) {
    const fields = getEncryptedFields(target);
    const name = entityName ?? target.name;
    const fieldPolicies = fields.map((metadata) => ({
        fieldName: metadata.propertyKey,
        classification: metadata.classification,
        encrypted: metadata.encrypted,
        algorithm: metadata.algorithm,
        deterministic: metadata.deterministic,
        keyDerivationSuffix: metadata.keyDerivationSuffix,
    }));
    return {
        entityName: name,
        fields: fieldPolicies,
    };
}
/**
 * Generate policies for multiple classes
 *
 * @param classes - Array of class constructors
 * @returns Map of entity names to policies
 *
 * @example
 * ```typescript
 * const policies = generatePolicies([User, Patient, Account]);
 * // Map {
 * //   'User' => { entityName: 'User', fields: [...] },
 * //   'Patient' => { entityName: 'Patient', fields: [...] },
 * //   'Account' => { entityName: 'Account', fields: [...] }
 * // }
 * ```
 */
export function generatePolicies(classes) {
    const policies = new Map();
    for (const cls of classes) {
        const policy = generatePolicy(cls);
        policies.set(policy.entityName, policy);
    }
    return policies;
}
//# sourceMappingURL=decorators.js.map