import { Kysely, sql } from "kysely";
import {} from "../database/schema.js";
import { capture } from "../services/telemetry/capture.js";
import { ENV_VARIABLES } from "../services/env-variables/index.js";
import { createHooks } from "../hooks/create-hooks.js";
import { createObserve } from "../observe/create-observe.js";
import { createEnvironmentDialect } from "../database/sqlite/environment-dialect.js";
import { createDefaultPlugins } from "../database/kysely/index.js";
import { random } from "../engine/functions/random.js";
import { loadPluginFromString } from "../environment/load-from-string.js";
/**
 * Opens a Lix instance.
 *
 * Creates an in-memory database by default. If a blob is provided,
 * the database is initialized with that data. If a database is provided,
 * uses that database directly.
 *
 * @example
 * ```ts
 * // In-memory (default)
 * const lix = await openLix({})
 *
 * // From existing data
 * const lix = await openLix({ blob: existingLixFile })
 *
 * // With custom storage adapter
 * import { MyCustomStorage } from "./my-custom-storage.js"
 * const lix = await openLix({
 *   storage: new MyCustomStorage()
 * })
 * ```
 */
export async function openLix(args) {
    const hooks = createHooks();
    const blob = args.blob;
    let engine;
    const hostPlugins = [];
    const providedPluginObjects = [...(args.providePlugins ?? [])];
    const providedPluginStrings = [...(args.providePluginsRaw ?? [])];
    for (const plugin of providedPluginObjects) {
        hostPlugins.push(plugin);
    }
    for (const plugin of providedPluginStrings) {
        hostPlugins.push(await loadPluginFromString(plugin));
    }
    const environment = args.environment ??
        (await import("../environment/in-memory.js").then((m) => new m.InMemoryEnvironment()));
    // Default behavior: openOrCreate
    // - If a blob is provided, attempt to create from it (environment may refuse if target exists)
    // - If no blob is provided, attempt to create a fresh Lix; if environment reports existing DB,
    //   fall back to open without a blob.
    const boot = {
        args: {
            providePlugins: providedPluginObjects,
            providePluginsRaw: providedPluginStrings,
            account: args.account,
            keyValues: args.keyValues,
        },
    };
    if (blob) {
        await environment.create({ blob: await args.blob.arrayBuffer() });
        const res = await environment.open({
            boot,
            emit: (ev) => hooks._emit(ev.type, ev.payload),
        });
        engine = res?.engine;
    }
    else {
        const exists = await environment.exists();
        if (!exists) {
            const { newLixFile } = await import("./new-lix.js");
            const seed = await newLixFile({ keyValues: args.keyValues });
            const seedBytes = await seed.arrayBuffer();
            await environment.create({ blob: seedBytes });
        }
        const res = await environment.open({
            boot,
            emit: (ev) => hooks._emit(ev.type, ev.payload),
        });
        engine = res?.engine;
    }
    const db = new Kysely({
        dialect: createEnvironmentDialect({ environment }),
        plugins: createDefaultPlugins(),
    });
    const observe = createObserve({ hooks });
    await captureOpened({ lix: { db, call: environment.call } });
    const lix = {
        // sqlite intentionally not exposed in environment mode
        db,
        hooks,
        observe,
        engine: engine,
        plugin: {
            getAll: async () => hostPlugins,
        },
        call: async (name, args) => environment.call(name, args),
        close: async () => {
            await environment.close();
        },
        toBlob: async () => {
            await environment.call("lix_commit_sequence_number");
            await environment.call("lix_commit_deterministic_rng_state");
            return new Blob([await environment.export()]);
        },
    };
    return lix;
}
async function captureOpened(args) {
    try {
        const telemetry = await args.lix.db
            .selectFrom("key_value")
            .select("value")
            .where("key", "=", "lix_telemetry")
            .executeTakeFirst();
        if (telemetry?.value === "off") {
            return;
        }
        const activeAccount = await args.lix.db
            .selectFrom("active_account")
            .select("account_id")
            .executeTakeFirstOrThrow();
        const lixId = await args.lix.db
            .selectFrom("key_value")
            .select("value")
            .where("key", "=", "lix_id")
            .executeTakeFirstOrThrow();
        const fileExtensions = await usedFileExtensions(args.lix.db);
        const sample = await random({ lix: args.lix });
        if (sample > 0.1) {
            // Not awaiting to avoid boot up time and knowing that
            // no database query is performed here. we dont care if the
            // server responds with an error or not.
            void capture("LIX-SDK lix opened", {
                accountId: activeAccount.account_id,
                lixId: lixId.value,
                telemetryKeyValue: (telemetry?.value ?? "on"),
                properties: {
                    lix_sdk_version: ENV_VARIABLES.LIX_SDK_VERSION,
                    stored_file_extensions: fileExtensions,
                },
            });
        }
    }
    catch {
        // ignore
    }
}
/**
 * Get all used file extensions.
 */
export async function usedFileExtensions(db) {
    const result = await sql `
	WITH RECURSIVE numbers(i) AS (
		SELECT 1
		UNION ALL
		SELECT i + 1 FROM numbers WHERE i < 1000 -- Limit to 1000 characters for path length
	),
	REVERSED AS (
		SELECT id,
					GROUP_CONCAT(SUBSTR(path, LENGTH(path) - i + 1, 1), '') AS reversed_path
		FROM file, numbers
		WHERE i <= LENGTH(path)
		GROUP BY id, path
	),
	EXTENSIONS AS (
		SELECT DISTINCT SUBSTR(path, LENGTH(path) - INSTR(reversed_path, '.') + 2) AS extension
		FROM file
		JOIN REVERSED ON file.id = REVERSED.id
		WHERE INSTR(reversed_path, '.') > 0
	)
	SELECT extension FROM EXTENSIONS;
	`.execute(db);
    return result.rows.map((row) => row.extension);
}
//# sourceMappingURL=open-lix.js.map