import { requestContext } from "@fastify/request-context";
import { kvsEnvStorage } from "@kvs/env";
import { kvsMemoryStorage } from "@kvs/memorystorage";
import { createRedisKeyValueStore } from "./redis-client.js";
import { isLike, ok } from "../../is.js";
import { isRedis } from "./redis-client-helpers.js";
import { getConfig } from "../../config/index.js";
import { addKeyValueStoreIndex, deleteKeyValueStoreIndex } from "./store-index.js";
import { createKVConnectStore, isKVConnect } from "./kv-connect.js";
const DATABASE_VERSION = 1;
const META_STORE_PREFIX = "meta";
const { STORAGE_IN_MEMORY } = process.env;
export const STORE_NAMES = new Set();
export function getBaseKeyValueStore(name, options) {
    STORE_NAMES.add(name);
    const key = `kvStore#${name}${options?.prefix || ""}`;
    const existing = get();
    if (existing)
        return existing;
    let store = create();
    const config = getConfig();
    if (config.store) {
        const configured = config.store(store);
        if (isLike(configured)) {
            store = configured;
        }
    }
    requestContext.set(key, store);
    return store;
    function get() {
        return requestContext.get(key);
    }
    function create() {
        let store;
        let kv;
        const nextOptions = {
            ...options,
            meta: options?.meta ?? meta
        };
        if (isKVConnect()) {
            kv = createKVConnectStore(name, nextOptions);
        }
        else if (isRedis()) {
            kv = createRedisKeyValueStore(name, nextOptions);
        }
        else {
            kv = createKeyValueStore(name, nextOptions, () => {
                if (store) {
                    return store;
                }
                const options = {
                    // prefix === storage partition
                    // for the kvs env, we can get the same effect with a separate name
                    // which is mapped to a prefix for a file name anyway...
                    name: `${name}${nextOptions?.prefix ? `:${nextOptions.prefix}` : ""}`,
                    version: DATABASE_VERSION,
                };
                if (STORAGE_IN_MEMORY) {
                    return (store = kvsMemoryStorage(options));
                }
                return (store = kvsEnvStorage(options));
            });
        }
        return kv;
        function meta(key) {
            return createMetaStore(getBaseKeyValueStore, name, key, options);
        }
    }
}
export function isMetaStoreName(name) {
    return name.includes(`::${META_STORE_PREFIX}`);
}
export function createMetaStore(fn, name, key, options) {
    return fn(`${name}::${META_STORE_PREFIX}`, {
        ...options,
        prefix: [options?.prefix, key]
            .filter(Boolean)
            .join(":")
    });
}
function createKeyValueStore(name, options, storage) {
    const isCounter = name.endsWith("Counter");
    async function postSet() {
        await addKeyValueStoreIndex(name);
    }
    async function noValuesAvailable() {
        await deleteKeyValueStoreIndex(name);
    }
    async function get(key) {
        const store = await storage();
        return store.get(key);
    }
    async function set(key, value) {
        if (isCounter) {
            ok(typeof value === "number", "Expected number value for counter store");
        }
        const store = await storage();
        await store.set(key, value);
        await postSet();
    }
    async function values() {
        const values = [];
        const store = await storage();
        for await (const [, value] of store) {
            values.push(value);
        }
        if (!values.length) {
            await noValuesAvailable();
        }
        return values;
    }
    async function* asyncIterable() {
        const store = await storage();
        let hasValue = false;
        for await (const [, value] of store) {
            yield value;
            hasValue = true;
        }
        if (!hasValue) {
            await noValuesAvailable();
        }
    }
    async function deleteFn(key) {
        const store = await storage();
        await store.delete(key);
    }
    async function has(key) {
        const store = await storage();
        return store.has(key);
    }
    async function keys() {
        const keys = [];
        const store = await storage();
        for await (const [key] of store) {
            if (typeof key === "string") {
                keys.push(key);
            }
        }
        if (!keys.length) {
            await noValuesAvailable();
        }
        return keys;
    }
    async function clear() {
        const store = await storage();
        for await (const [key] of store) {
            await store.delete(key);
        }
        await noValuesAvailable();
    }
    async function increment(key) {
        ok(isCounter, "Expected increment to be used with a counter store only");
        const storedValue = await get(key);
        const currentValue = typeof storedValue === "number" ? storedValue : 0;
        const nextValue = currentValue + 1;
        ok(nextValue);
        await set(key, nextValue);
        return nextValue;
    }
    function meta(key) {
        const fn = options?.meta;
        ok(fn, "expected meta option to be provided if meta is used");
        return fn(key);
    }
    return {
        name,
        get,
        set,
        values,
        delete: deleteFn,
        has,
        keys,
        clear,
        increment,
        meta,
        [Symbol.asyncIterator]() {
            return asyncIterable()[Symbol.asyncIterator]();
        },
    };
}
//# sourceMappingURL=kv-base.js.map