/**
 * @module helpers.web
 *
 * Web-specific helper functions. Handles storage initialization, globalThis
 * wiring, and dynamic script loading.
 *
 * NOTE: initIbGibStorage, initIbGibGlobalThis, and dynamicallyLoadBootstrapScript
 * are candidates for upstreaming into @ibgib/web-gib.
 */

import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
import { storageGet } from "@ibgib/web-gib/dist/storage/storage-helpers.web.mjs";
import { initAppStorage } from "@ibgib/web-gib/dist/helpers.web.mjs";
import { getGlobalMetaspace_waitIfNeeded } from "@ibgib/web-gib/dist/helpers.mjs";
import { IbGibDynamicComponentMetaCtorOpts } from "@ibgib/web-gib/dist/ui/component/component-types.mjs";
import { IbGibGlobalThisInfo } from "@ibgib/web-gib/dist/types.mjs";
import { delay } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";

import { GLOBAL_LOG_A_LOT } from "./constants.mjs";
import { AUTO_GENERATED_VERSION } from "./AUTO-GENERATED-version.mjs";
import { IbGibGlobalThis_{{APP_CLASSNAME_PREFIX}} } from "./types.mjs";
import { 
    IbGibAmbientContextConfig,
    IbGibGlobalThis_Common,
} from "@ibgib/web-gib/dist/app-bootstrap/types.mjs";

const logalot = GLOBAL_LOG_A_LOT;

// ---------------------------------------------------------------------------
// Storage initialization
// ---------------------------------------------------------------------------

/**
 * Initializes the IndexedDB stores required by this app.
 * Call once on DOMContentLoaded, before bootstrap.
 */
export async function initIbGibStorage(config: IbGibAmbientContextConfig): Promise<void> {
    const lc = `[${initIbGibStorage.name}]`;
    try {
        if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
        await initAppStorage({
            infos: [
                {
                    dbName: config.dbName,
                    storeNames: [config.storeName, ...(config.additionalStoreNames ?? [])],
                },
            ],
        });
    } catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    } finally {
        if (logalot) { console.log(`${lc} complete.`); }
    }
}

// ---------------------------------------------------------------------------
// globalThis initialization
// ---------------------------------------------------------------------------

/**
 * Sets up globalThis.ibgib.{{APP_GLOBALTHIS_KEY}} once.
 * Safe to call multiple times (idempotent).
 */
export function initIbGibGlobalThis(config: IbGibAmbientContextConfig): void {
    const lc = `[${initIbGibGlobalThis.name}]`;
    try {
        if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }

        if (!(globalThis as any).ibgib) {
            (globalThis as any).ibgib = {
                dbName_hack: config.dbName,
                apiKeyName_hack: config.apiKeyName,
                storeName_hack: config.storeName,
            } satisfies IbGibGlobalThisInfo;
        }

        if (!(globalThis as any).ibgib.{{APP_GLOBALTHIS_KEY}}) {
            (globalThis as any).ibgib.{{APP_GLOBALTHIS_KEY}} = {
                version: AUTO_GENERATED_VERSION,
                spaceShim: {},
                fnDefaultGetAPIKey: async () => {
                    const apiKey = await storageGet({
                        dbName: config.dbName,
                        storeName: config.storeName,
                        key: config.apiKeyName,
                    });
                    return apiKey ?? '';
                },
                dbName_hack: config.dbName,
                apiKeyName_hack: config.apiKeyName,
                storeName_hack: config.storeName,
            } satisfies IbGibGlobalThis_{{APP_CLASSNAME_PREFIX}};
        }
    } catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    } finally {
        if (logalot) { console.log(`${lc} complete.`); }
    }
}

// ---------------------------------------------------------------------------
// GlobalThis accessors
// ---------------------------------------------------------------------------

export function getIbGibGlobalThis_{{APP_CLASSNAME_PREFIX}}(
    config?: IbGibAmbientContextConfig
): IbGibGlobalThis_{{APP_CLASSNAME_PREFIX}} {
    if (!(globalThis as any).ibgib?.{{APP_GLOBALTHIS_KEY}}) {
        if (!config) {
            throw new Error(`Global context not initialized and config not provided.`);
        }
        initIbGibGlobalThis(config);
    }
    return (globalThis as any).ibgib.{{APP_GLOBALTHIS_KEY}} as IbGibGlobalThis_{{APP_CLASSNAME_PREFIX}};
}

export function getIbGibGlobalThis_Common(): IbGibGlobalThis_Common {
    const g = (globalThis as any).ibgib?.{{APP_GLOBALTHIS_KEY}};
    if (!g) {
        throw new Error(`(UNEXPECTED) globalThis.ibgib.{{APP_GLOBALTHIS_KEY}} not initialized.`);
    }
    return g as IbGibGlobalThis_Common;
}

// ---------------------------------------------------------------------------
// API key helper
// ---------------------------------------------------------------------------

export function getDefaultFnGetAPIKey(): () => Promise<string> {
    return async () => {
        const gb = getIbGibGlobalThis_{{APP_CLASSNAME_PREFIX}}();
        return (await storageGet({
            dbName: gb.dbName_hack,
            storeName: gb.storeName_hack,
            key: gb.apiKeyName_hack,
        })) ?? '';
    };
}

// ---------------------------------------------------------------------------
// Dynamic bootstrap loading
// ---------------------------------------------------------------------------

/**
 * Dynamically imports the bootstrap module and calls the app's bootstrap
 * function. Called after DOMContentLoaded so the initial page render is not
 * blocked by the heavy ibgib import graph.
 */
export async function dynamicallyLoadBootstrapScript(path: string): Promise<void> {
    const lc = `[${dynamicallyLoadBootstrapScript.name}]`;
    try {
        if (logalot) { console.log(`${lc} starting... (I: genuuid)`); }
        const module = await import(path);
        await module.bootstrap{{APP_CLASSNAME_PREFIX}}App();
    } catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    } finally {
        if (logalot) { console.log(`${lc} complete.`); }
    }
}

// ---------------------------------------------------------------------------
// Component constructor args helper
// ---------------------------------------------------------------------------

/**
 * Returns the standard ctor options for IbGib dynamic components, including
 * a bootstrap promise wrapper so components can await app readiness.
 */
export function getComponentCtorArg(): IbGibDynamicComponentMetaCtorOpts {
    const fnGetMetaspace = async () => getGlobalMetaspace_waitIfNeeded();

    const bootstrapPromiseWrapper = new Promise<void>(async (resolve, reject) => {
        try {
            let maxTries = 100_000;
            let counter = 0;
            let bootstrapPromise: Promise<void> | undefined;
            do {
                bootstrapPromise =
                    getIbGibGlobalThis_{{APP_CLASSNAME_PREFIX}}()?.bootstrapPromise;
                counter++;
                if (counter > maxTries) { break; }
                await delay(10);
            } while (bootstrapPromise === undefined);
            if (!bootstrapPromise) {
                throw new Error(`Timed out waiting for bootstrapPromise. (E: genuuid)`);
            }
            await bootstrapPromise;
            resolve();
        } catch (error) {
            reject(error);
        }
    });

    return { fnGetMetaspace, bootstrapPromise: bootstrapPromiseWrapper };
}
