const log_lvl = 0; // 0 = no logging, 1 = some logging, 2 = all logging

// Updated 2025-05-08
async function generate_iv() {
    const data = new Uint8Array(16);
    crypto.getRandomValues(data);
    return data;
}

// Updated 2025-05-08
export const encrypt_content = async function encrypt_content(
    content: string,
    keyData: string
) {
    const iv = await generate_iv();
    const keyBytes = await crypto.subtle.digest(
        'SHA-256',
        new TextEncoder().encode(keyData)
    );
    const key = await crypto.subtle.importKey(
        'raw',
        keyBytes,
        { name: 'AES-CBC' },
        false,
        ['encrypt']
    );
    const encodedContent = await crypto.subtle.encrypt(
        { name: 'AES-CBC', iv: iv.buffer as ArrayBuffer },
        key,
        new TextEncoder().encode(content).buffer as ArrayBuffer
    );
    const base64 = btoa(String.fromCharCode(...new Uint8Array(encodedContent)));
    if (log_lvl) {
        console.log(`IV: ${iv}; Encrypted:`, base64);
    }
    return { base64, iv };
};

// Updated 2025-05-08
export const combine_iv_and_base64 = function combine_iv_and_base64(
    base64: string,
    iv: Uint8Array
) {
    if (log_lvl) {
        console.log(`IV: ${iv}; Encrypted:`, base64);
    }

    // Combine the IV and encrypted content
    const combined =
        Array.from(iv)
            .map((byte) => byte.toString(16).padStart(2, '0'))
            .join('') +
        ':' +
        base64;
    if (log_lvl) {
        console.log('Combined IV and Base64:', combined);
    }
    // const ivBase64 = btoa(String.fromCharCode(...iv));
    // const combined = `${ivBase64}:${base64}`;
    // console.log('Combined IV and Base64 v2:', combined);
    return combined;
};

// Updated 2025-05-08
export const encrypt_wrapper = async function encrypt_wrapper(
    content: string,
    keyData: string
) {
    if (!content) {
        console.error('No content provided. Returning empty string.');
        return '';
    }
    if (!keyData) {
        console.error('No keyData provided. Returning empty string.');
        return '';
    }
    const { base64, iv } = await encrypt_content(content, keyData);
    const combined = combine_iv_and_base64(base64, iv);
    return combined;
};

// This does not handle errors (invalid key/password) well.
// Updated 2025-05-08
export const decrypt_content = async function decrypt_content(
    base64Content: string,
    iv: Uint8Array,
    keyData: string
) {
    const keyBytes = await crypto.subtle.digest(
        'SHA-256',
        new TextEncoder().encode(keyData)
    );
    const key = await crypto.subtle.importKey(
        'raw',
        keyBytes,
        { name: 'AES-CBC' },
        false,
        ['decrypt']
    );
    const encryptedContent = Uint8Array.from(atob(base64Content), (c) =>
        c.charCodeAt(0)
    );
    const decryptedContent = await crypto.subtle.decrypt(
        { name: 'AES-CBC', iv: iv.buffer as ArrayBuffer },
        key,
        encryptedContent.buffer as ArrayBuffer
    );
    const decodedContent = new TextDecoder().decode(decryptedContent);
    // console.log('Decrypted Content:', decodedContent);
    return decodedContent;
};

// Updated 2025-05-08
export const split_iv_and_base64 = function split_iv_and_base64(
    combined: string
) {
    if (!combined) {
        console.error('No combined string provided. Returning empty object.');
        return { iv: new Uint8Array(), base64: '' };
    }
    const [iv_hex, encrypted_base64_string] = combined.split(':');
    const base64 = encrypted_base64_string;
    const match_result = iv_hex.match(/.{1,2}/g);
    const iv = new Uint8Array(
        (match_result || []).map((byte) => parseInt(byte, 16))
    );
    if (log_lvl) {
        console.log(`IV: ${iv}; Encrypted:`, base64);
    }
    return { iv, base64 };
};

// Updated 2025-05-15
export const decrypt_wrapper = async function decrypt_wrapper(
    combined: string,
    keyData: string
) {
    if (!combined) {
        console.error('No combined string provided. Returning empty string.');
        return false;
    }
    const { iv, base64 } = split_iv_and_base64(combined);
    console.log(`IV: ${iv}; Encrypted:`, base64);
    let decrypted;
    try {
        decrypted = await decrypt_content(base64, iv, keyData);
        if (log_lvl > 1) {
            console.log(`IV: ${iv}; Decrypted:`, decrypted);
        } else if (log_lvl) {
            console.log(`IV: ${iv}`);
        }
    } catch (error) {
        console.error('Decryption failed:', error);
        return false;
    }
    return decrypted;
};
