/*
 * vault1984 — shared crypto module
 * Runs in both QuickJS (CLI) and browser (extension).
 *
 * In CLI (QuickJS): native_* functions provided by jsbridge.c via BearSSL.
 *   All calls are synchronous.
 * In browser: Web Crypto API used directly (async).
 *
 * This file is the single source of truth for L2/L3 field crypto.
 */

/* Detect environment */
const IS_BROWSER = typeof globalThis.crypto !== 'undefined'
    && typeof globalThis.crypto.subtle !== 'undefined';

/* --- base64 helpers --- */

function uint8_to_base64(bytes) {
    if (IS_BROWSER) {
        return btoa(String.fromCharCode.apply(null, bytes));
    } else {
        return native_base64_encode(bytes);
    }
}

function base64_to_uint8(str) {
    if (IS_BROWSER) {
        const bin = atob(str);
        const bytes = new Uint8Array(bin.length);
        for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
        return bytes;
    } else {
        return native_base64_decode(str);
    }
}

/* --- AES-GCM --- */

/**
 * Encrypt plaintext with AES-GCM.
 * @param {Uint8Array} key - 16 bytes (AES-128) or 32 bytes (AES-256)
 * @param {Uint8Array} plaintext
 * @returns {Uint8Array|Promise<Uint8Array>} nonce(12) || ciphertext || tag(16)
 */
function aes_gcm_encrypt(key, plaintext) {
    if (IS_BROWSER) {
        const iv = crypto.getRandomValues(new Uint8Array(12));
        return crypto.subtle.importKey(
            'raw', key, { name: 'AES-GCM' }, false, ['encrypt']
        ).then(function(cryptoKey) {
            return crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, cryptoKey, plaintext);
        }).then(function(ct) {
            const result = new Uint8Array(12 + ct.byteLength);
            result.set(iv, 0);
            result.set(new Uint8Array(ct), 12);
            return result;
        });
    } else {
        /* QuickJS: synchronous BearSSL binding */
        return native_aes_gcm_encrypt(key, plaintext);
    }
}

/**
 * Decrypt AES-GCM ciphertext.
 * @param {Uint8Array} key - 16 or 32 bytes
 * @param {Uint8Array} data - nonce(12) || ciphertext || tag(16)
 * @returns {Uint8Array|Promise<Uint8Array>} plaintext
 */
function aes_gcm_decrypt(key, data) {
    if (data.length < 28) throw new Error('ciphertext too short');

    /* Use subarray for typed array compatibility (QuickJS) */
    var iv, ct;
    if (typeof data.subarray === 'function') {
        iv = new Uint8Array(data.subarray(0, 12));
        ct = new Uint8Array(data.subarray(12));
    } else {
        iv = data.slice(0, 12);
        ct = data.slice(12);
    }

    if (IS_BROWSER) {
        return crypto.subtle.importKey(
            'raw', key, { name: 'AES-GCM' }, false, ['decrypt']
        ).then(function(cryptoKey) {
            return crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv }, cryptoKey, ct);
        }).then(function(pt) {
            return new Uint8Array(pt);
        });
    } else {
        /* Pass full data blob to native — C splits nonce/ct internally */
        return native_aes_gcm_decrypt_blob(key, data);
    }
}

/* --- HKDF-SHA256 --- */

/**
 * HKDF-SHA256 extract + expand.
 * @param {Uint8Array} ikm - input key material
 * @param {Uint8Array|null} salt - optional salt
 * @param {Uint8Array} info - context info
 * @param {number} length - output length in bytes
 * @returns {Uint8Array|Promise<Uint8Array>}
 */
function hkdf_sha256(ikm, salt, info, length) {
    if (IS_BROWSER) {
        return crypto.subtle.importKey(
            'raw', ikm, 'HKDF', false, ['deriveBits']
        ).then(function(cryptoKey) {
            return crypto.subtle.deriveBits(
                { name: 'HKDF', hash: 'SHA-256', salt: salt || new Uint8Array(0), info: info },
                cryptoKey, length * 8
            );
        }).then(function(bits) {
            return new Uint8Array(bits);
        });
    } else {
        return native_hkdf_sha256(ikm, salt, info, length);
    }
}

/* --- Field encryption/decryption --- */

/**
 * Encrypt a field value.
 * Key length determines tier: 16 bytes = L2 (AES-128), 32 bytes = L3 (AES-256).
 * @param {Uint8Array} key - 16 or 32 bytes
 * @param {string} field_label - field label (for per-field key derivation)
 * @param {string} plaintext - field value to encrypt
 * @returns {string|Promise<string>} base64-encoded ciphertext
 */
/*
 * Normalize key for AES: 8-byte keys are doubled to 16 bytes.
 * AES requires 16, 24, or 32 byte keys.
 * HKDF output length matches the (normalized) key length.
 */
function normalize_key(key) {
    if (key.length === 8) {
        var doubled = new Uint8Array(16);
        doubled.set(key, 0);
        doubled.set(key, 8);
        return doubled;
    }
    return key;
}

function encrypt_field(key, field_label, plaintext) {
    var info_str = 'vault1984-field-' + field_label;
    var nkey = normalize_key(key);
    var aes_len = nkey.length; /* 16 or 32 */

    if (IS_BROWSER) {
        var enc = new TextEncoder();
        var info = enc.encode(info_str);
        return hkdf_sha256(nkey, null, info, aes_len).then(function(field_key) {
            return aes_gcm_encrypt(field_key, enc.encode(plaintext));
        }).then(function(ct) {
            return uint8_to_base64(ct);
        });
    } else {
        var info = native_encode_utf8(info_str);
        var field_key = native_hkdf_sha256(nkey, null, info, aes_len);
        var pt_bytes = native_encode_utf8(plaintext);
        var ct = native_aes_gcm_encrypt(field_key, pt_bytes);
        return native_base64_encode(ct);
    }
}

/**
 * Decrypt a field value.
 * Key length determines tier: 16 bytes = L2, 32 bytes = L3.
 * @param {Uint8Array} key - 16 or 32 bytes
 * @param {string} field_label - field label
 * @param {string} ciphertext_b64 - base64-encoded ciphertext
 * @returns {string|Promise<string>} plaintext
 */
function decrypt_field(key, field_label, ciphertext_b64) {
    var info_str = 'vault1984-field-' + field_label;
    var nkey = normalize_key(key);
    var aes_len = nkey.length;

    if (IS_BROWSER) {
        var enc = new TextEncoder();
        var dec = new TextDecoder();
        var info = enc.encode(info_str);
        return hkdf_sha256(nkey, null, info, aes_len).then(function(field_key) {
            var ct = base64_to_uint8(ciphertext_b64);
            return aes_gcm_decrypt(field_key, ct);
        }).then(function(pt) {
            return dec.decode(pt);
        });
    } else {
        var info = native_encode_utf8(info_str);
        var field_key = native_hkdf_sha256(nkey, null, info, aes_len);
        var ct = native_base64_decode(ciphertext_b64);
        var pt = native_aes_gcm_decrypt_blob(field_key, ct);
        return native_decode_utf8(pt);
    }
}

/* Backward compat aliases */
function l2_encrypt_field(key, entry_id, label, pt) { return encrypt_field(key, label, pt); }
function l2_decrypt_field(key, entry_id, label, ct) { return decrypt_field(key, label, ct); }

/* Export for both environments */
if (typeof globalThis.vault1984 === 'undefined') globalThis.vault1984 = {};
globalThis.vault1984.crypto = {
    aes_gcm_encrypt: aes_gcm_encrypt,
    aes_gcm_decrypt: aes_gcm_decrypt,
    hkdf_sha256: hkdf_sha256,
    encrypt_field: encrypt_field,
    decrypt_field: decrypt_field,
    l2_encrypt_field: l2_encrypt_field,
    l2_decrypt_field: l2_decrypt_field,
    uint8_to_base64: uint8_to_base64,
    base64_to_uint8: base64_to_uint8
};
