import type { Dexie, Table } from 'dexie';
import { browser } from '$app/environment';

/**
 * Checks IDB table content versions and clears any tables whose version has
 * changed since the last check.
 *
 * Version numbers live in IDB_CONTENT_VERSIONS (store_versions.ts). State is
 * tracked in localStorage (ae_idb_ver__{module}__{table}) so each table is
 * cleared exactly once per version bump, not on every page load.
 *
 * Call once at module init in each db_*.ts, after the singleton is created.
 * The clear is intentionally fire-and-forget — the SWR pattern repopulates
 * from the API naturally after a cache miss.
 *
 * A null stored version (never tracked) is treated as outdated so existing
 * installs with stale cached data are cleaned up on first run.
 */
export async function check_and_clear_idb_tables({
    db_instance,
    module_name,
    table_versions,
    log_lvl = 0
}: {
    db_instance: Dexie;
    module_name: string;
    table_versions: Record<string, number>;
    log_lvl?: number;
}): Promise<void> {
    if (!browser) return;

    for (const [table_name, expected_version] of Object.entries(table_versions)) {
        const ls_key = `ae_idb_ver__${module_name}__${table_name}`;
        const stored_raw = localStorage.getItem(ls_key);
        const stored_version = stored_raw !== null ? parseInt(stored_raw, 10) : null;

        if (stored_version === expected_version) continue;

        try {
            await db_instance.table(table_name).clear();
            localStorage.setItem(ls_key, String(expected_version));
            console.log(
                `[IDB] "${module_name}.${table_name}" cleared — v${stored_version ?? 'new'} → v${expected_version}`
            );
        } catch (e) {
            console.warn(`[IDB] Failed to clear "${module_name}.${table_name}":`, e);
        }
    }
}

/**
 * Extracts the primary key from an object using a prioritized list of possible key names.
 * @param obj The object to extract the ID from.
 * @param table_name The name of the table, used to construct legacy key names.
 * @param log_lvl The logging level.
 * @returns The found ID, or undefined if no ID could be found.
 */
function find_object_id(
    obj: any,
    table_name: string,
    log_lvl: number
): string | number | undefined {
    const potential_keys = [
        'id',
        'id_random',
        `${table_name}_id`,
        `${table_name}_id_random`,
        `event_${table_name}_id`,
        `event_${table_name}_id_random`
    ];

    for (const key of potential_keys) {
        if (obj[key]) {
            if (key !== 'id' && log_lvl > 1) {
                console.warn(
                    `Found legacy ID key "${key}" for table "${table_name}". Consider standardizing to "id".`,
                    obj
                );
            }
            return obj[key];
        }
    }

    if (log_lvl) {
        console.error(
            `Object is missing a valid ID for table "${table_name}". It will be skipped. Checked keys: ${potential_keys.join(', ')}`,
            obj
        );
    }
    return undefined;
}

/**
 * Saves an array of objects to a Dexie database table using bulkPut.
 * This function handles both creating new records and updating existing ones.
 *
 * @param db_instance The Dexie database instance.
 * @param table_name The name of the table in the database.
 * @param obj_li The array of objects to save.
 * @param properties_to_save The list of property names to include in the saved object.
 * @param log_lvl The logging level for debugging.
 * @returns A promise that resolves with the keys of the saved objects.
 * @throws An error if the database operation fails.
 *
 * @version 2.0.0
 * @since 2025-11-13
 */
export async function db_save_ae_obj_li__ae_obj<T extends Record<string, any>>({
    db_instance,
    table_name,
    obj_li,
    properties_to_save,
    log_lvl = 0
}: {
    db_instance: Dexie;
    table_name: string;
    obj_li: T[];
    properties_to_save: (keyof T)[];
    log_lvl?: number;
}) {
    if (!browser) return [];

    if (log_lvl > 0) {
        console.log(
            `*** db_save_ae_obj_li__ae_obj: Attempting to save ${obj_li.length} objects to table "${table_name}" ***`
        );
    }

    if (!obj_li || obj_li.length === 0) {
        if (log_lvl > 0) console.log('No objects to save.');
        return [];
    }

    const db_table: Table<T> = db_instance.table(table_name);
    if (!db_table) {
        const error_msg = `Table not found in Dexie instance: ${table_name}`;
        console.error(error_msg);
        throw new Error(error_msg);
    }

    const data_to_save = obj_li
        .map((obj) => {
            const record: Partial<T> = {};

            // Extract only the specified properties to save.
            for (const prop of properties_to_save) {
                record[prop] = obj[prop];
            }

            // Ensure the primary key is included, attempting to find it from various legacy keys.
            const id = find_object_id(obj, table_name, log_lvl);

            if (id === undefined) {
                return null; // Skip objects without a valid ID.
            }

            const primary_key_name = db_table.schema.primKey.name;
            (record as any)[primary_key_name] = id;
            return record;
        })
        .filter(Boolean) as T[];

    if (data_to_save.length === 0) {
        if (log_lvl > 0) {
            console.warn(
                'All objects were skipped, likely due to missing IDs.'
            );
        }
        return [];
    }

    try {
        // bulkPut efficiently handles both inserts and updates.
        const keys = await db_table.bulkPut(data_to_save);
        if (log_lvl > 0) {
            console.log(
                `Successfully saved ${data_to_save.length} objects to "${table_name}".`
            );
        }
        return keys;
    } catch (error) {
        console.error(`Failed to save objects to "${table_name}":`, error);
        // Re-throw the error to let the caller handle it.
        throw error;
    }
}
