import { determineSchemaKeys, extractLiteralFilters, } from "../observe/determine-schema-keys.js";
const runtimeCaches = new WeakMap();
const BIGINT_SAFE_SERIALIZER = (_key, value) => typeof value === "bigint" ? value.toString() : value;
function serialiseParameters(parameters) {
    if (!parameters || parameters.length === 0)
        return "[]";
    try {
        return JSON.stringify(parameters, BIGINT_SAFE_SERIALIZER);
    }
    catch {
        // Fall back to stringifying via String() to avoid runtime crashes
        return `[${parameters.map(String).join(",")}]`;
    }
}
function getCacheContainer(engine) {
    let cache = runtimeCaches.get(engine.runtimeCacheRef);
    if (!cache) {
        cache = new Map();
        runtimeCaches.set(engine.runtimeCacheRef, cache);
    }
    return cache;
}
function buildCacheKey(compiled) {
    const paramsKey = serialiseParameters(compiled.parameters);
    return `${compiled.sql}\u0000${paramsKey}`;
}
function computeDependencies(compiled) {
    const schemaKeys = determineSchemaKeys(compiled) ?? [];
    const filters = extractLiteralFilters(compiled);
    return {
        schemaKeys: Array.from(new Set([...(filters.schemaKeys ?? []), ...schemaKeys])),
        versionIds: filters.versionIds ?? [],
        entityIds: filters.entityIds ?? [],
    };
}
function shouldInvalidate(changes, dependencies) {
    if (!changes || changes.length === 0) {
        return true;
    }
    const { schemaKeys, versionIds, entityIds } = dependencies;
    // No dependency information => be conservative and invalidate
    if (schemaKeys.length === 0 &&
        versionIds.length === 0 &&
        entityIds.length === 0) {
        return true;
    }
    for (const change of changes) {
        const schemaKey = change.schema_key;
        const entityId = change.entity_id;
        const changeVersionId = change.version_id ?? change.lixcol_version_id;
        if (schemaKeys.length > 0) {
            if (!schemaKey) {
                return true;
            }
            if (!schemaKeys.includes(schemaKey)) {
                continue;
            }
        }
        if (entityIds.length > 0) {
            if (!entityId) {
                return true;
            }
            if (!entityIds.includes(entityId)) {
                continue;
            }
        }
        if (versionIds.length > 0) {
            if (!changeVersionId) {
                return true;
            }
            if (!versionIds.includes(changeVersionId)) {
                continue;
            }
        }
        return true;
    }
    return false;
}
function attachInvalidation(engine, cache, entry) {
    let released = false;
    let unsubscribeHook = () => { };
    const teardown = () => {
        if (released)
            return;
        released = true;
        unsubscribeHook();
        cache.delete(entry.key);
    };
    unsubscribeHook = engine.hooks.onStateCommit(({ changes }) => {
        if (shouldInvalidate(changes, entry.dependencies)) {
            teardown();
        }
    });
    entry.unsubscribe = teardown;
}
/**
 * Executes an internal compiled query once per engine instance and serves future callers from cache.
 *
 * Uses `engine.runtimeCacheRef` as a stable WeakMap key, subscribes to `onStateCommit`, and
 * invalidates cached results when relevant entities, schema keys, or version IDs change.
 *
 * @example
 * ```ts
 * const [config] = withRuntimeCache(
 *   engine,
 *   internalQueryBuilder
 *     .selectFrom("lix_internal_state_vtable")
 *     .where("entity_id", "=", "lix_deterministic_mode")
 *     .where("schema_key", "=", "lix_key_value")
 *     .select(sql`json_extract(snapshot_content, '$.value.enabled')`.as("enabled"))
 *     .limit(1)
 *     .compile()
 * ).rows;
 * ```
 */
export function withRuntimeCache(engine, compiled) {
    const cache = getCacheContainer(engine);
    const key = buildCacheKey(compiled);
    const existing = cache.get(key);
    if (existing) {
        return existing.result;
    }
    const result = engine.executeSync({
        sql: compiled.sql,
        parameters: compiled.parameters,
    });
    const dependencies = computeDependencies(compiled);
    const cacheEntry = {
        key,
        result,
        unsubscribe: () => {
            /* replaced below */
        },
        dependencies,
    };
    attachInvalidation(engine, cache, cacheEntry);
    cache.set(key, cacheEntry);
    return result;
}
//# sourceMappingURL=with-runtime-cache.js.map