import { CertificateAttributeOIDDB } from '../../services/kyc/iso20022.generated.js';
import { SensitiveAttribute } from '../certificates.js';
import { KeetaAnchorError } from '../error.js';
/**
 * Redacted message shown when attempting to log or serialize PIIStore
 */
const REDACTED = '[PII: REDACTED]';
/**
 * Error class for PII-related errors
 */
export class PIIError extends KeetaAnchorError {
    static name = 'PIIError';
    PIIErrorObjectTypeID;
    static PIIErrorObjectTypeID = 'b8e3c7a1-5d2f-4e6b-9a1c-3f8d2e7b4c5a';
    code;
    attributeName;
    constructor(code, attributeName, message) {
        super(message);
        Object.defineProperty(this, 'PIIErrorObjectTypeID', {
            value: PIIError.PIIErrorObjectTypeID,
            enumerable: false
        });
        this.code = code;
        this.attributeName = attributeName;
    }
    static isInstance(input, code) {
        if (!this.hasPropWithValue(input, 'PIIErrorObjectTypeID', PIIError.PIIErrorObjectTypeID)) {
            return (false);
        }
        if (code && !this.hasPropWithValue(input, 'code', code)) {
            return (false);
        }
        return (true);
    }
}
/**
 * PIIStore is a secure container for Personally Identifiable Information (PII).
 *
 * It encapsulates sensitive data and prevents accidental logging or serialization
 * by overriding common output methods to return redacted placeholders.
 *
 * @example
 * ```typescript
 * const store = new PIIStore();
 * store.setAttribute('firstName', 'John');
 * store.setAttribute('lastName', 'Doe');
 *
 * console.log(store); // '[PIIStore: REDACTED]'
 * JSON.stringify(store); // '{"type":"PIIStore","message":"REDACTED"}'
 * ```
 */
export class PIIStore {
    #attributes = new Map();
    constructor() {
        // Define Node.js util.inspect custom formatter to prevent PII exposure
        Object.defineProperty(this, Symbol.for('nodejs.util.inspect.custom'), {
            value: () => REDACTED,
            enumerable: false,
            writable: false,
            configurable: false
        });
    }
    /**
     * Create a PIIStore from a Certificate, extracting all attributes
     *
     * @param certificate - The certificate to extract attributes from
     *
     * @returns A new PIIStore populated with the certificate's attributes
     */
    static fromCertificate(certificate) {
        const store = new PIIStore();
        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
        const attributeNames = Object.keys(certificate.attributes);
        for (const name of attributeNames) {
            const attr = certificate.attributes[name];
            if (attr) {
                store.#attributes.set(name, {
                    value: attr.value,
                    sensitive: attr.sensitive
                });
            }
        }
        return (store);
    }
    setAttribute(name, value, sensitive = true) {
        this.#attributes.set(name, { value, sensitive });
    }
    /**
     * Check if an attribute exists in the store
     */
    hasAttribute(name) {
        return (this.#attributes.has(name));
    }
    /**
     * Get all attribute names currently stored
     */
    getAttributeNames() {
        return (Array.from(this.#attributes.keys()));
    }
    /**
     * Execute a function with scoped access to PII values
     *
     * Provides controlled access to all attribute values. Known certificate attributes
     * are automatically typed; external attributes require an explicit type parameter.
     *
     * @param fn - Function that receives a getter for accessing attribute values
     * @returns The return value of the callback function
     *
     * @throws PIIError with PII_ATTRIBUTE_NOT_FOUND if accessing a missing attribute
     */
    run(fn) {
        const attributes = this.#attributes;
        const get = (name) => {
            if (!this.hasAttribute(name)) {
                throw (new PIIError('PII_ATTRIBUTE_NOT_FOUND', name, `Attribute '${name}' not found in PIIStore`));
            }
            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
            return attributes.get(name)?.value;
        };
        return (fn(get));
    }
    /**
     * Create a SensitiveAttribute for a known certificate attribute
     *
     * Only known certificate attributes are supported. External attributes
     * cannot be converted to SensitiveAttributes.
     *
     * @param name - The attribute name to convert (must be a known certificate attribute)
     * @param subjectKey - The account to encrypt the attribute for
     * @returns A SensitiveAttribute containing the encrypted value
     *
     * @throws PIIError with PII_ATTRIBUTE_NOT_FOUND if the attribute is not set
     * @throws Error if the attribute is not a known certificate attribute
     */
    async toSensitiveAttribute(name, subjectKey) {
        if (!this.hasAttribute(name)) {
            throw (new PIIError('PII_ATTRIBUTE_NOT_FOUND', name, `Attribute '${name}' not found in PIIStore`));
        }
        if (!this.#isKnownAttribute(name)) {
            throw (new PIIError('PII_EXTERNAL_ATTRIBUTE', name, `Cannot convert external attribute '${name}' to SensitiveAttribute`));
        }
        const stored = this.#attributes.get(name);
        const storedValue = stored?.value;
        if (SensitiveAttribute.isInstance(storedValue)) {
            // If already a SensitiveAttribute, return it directly
            // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
            return storedValue;
        }
        // @ts-expect-error storedValue type is validated at setAttribute time
        const result = await SensitiveAttribute.create(subjectKey, name, storedValue);
        return (result);
    }
    /**
     * Apply known attributes to a CertificateBuilder
     *
     * External attributes are not included in the certificate.
     *
     * @param builder - The certificate builder to apply attributes to
     * @param subjectKey - The subject's account key (required to encrypt sensitive attributes)
     * @returns The certificate builder with the attributes applied
     */
    async toCertificateBuilder(builder, subjectKey) {
        for (const [name, attr] of this.#attributes.entries()) {
            if (this.#isKnownAttribute(name) && attr.value !== undefined && attr.value !== null) {
                if (attr.sensitive) {
                    const sensitiveAttr = await this.toSensitiveAttribute(name, subjectKey);
                    builder.setSensitiveAttribute(name, sensitiveAttr);
                }
                else {
                    builder.setAttribute(name, false, attr.value);
                }
            }
        }
        return (builder);
    }
    /**
     * Prevent logging of PII data via string coercion
     */
    toString() {
        return (REDACTED);
    }
    /**
     * Serialize to JSON with redacted values
     *
     * Shows attribute names for debugging, but all values are redacted.
     */
    toJSON() {
        const attributes = {};
        for (const name of this.#attributes.keys()) {
            attributes[name] = '[REDACTED]';
        }
        return ({ type: 'PIIStore', attributes });
    }
    #isKnownAttribute(name) {
        return (name in CertificateAttributeOIDDB);
    }
}
//# sourceMappingURL=pii.js.map