import { isPromise, ok, isLike } from "../../is.js";
import { getKeyValueStore } from "./kv.js";
export const STORAGE_HOOK_ON_FUNCTION = [
    "clear",
    "delete",
    "get",
    "has",
    "increment",
    "keys",
    "set",
    "values"
];
const STRING_FUNCTIONS = STORAGE_HOOK_ON_FUNCTION;
export function isStorageHookOnFunction(key) {
    return typeof key === "string" && STRING_FUNCTIONS.includes(key);
}
export const STORAGE_HOOK_BEFORE = "before";
export const STORAGE_HOOK_AFTER = "after";
export const STORAGE_HOOK_DEFAULT = STORAGE_HOOK_AFTER;
function createHooks() {
    return {
        clear: [],
        delete: [],
        get: [],
        has: [],
        increment: [],
        keys: [],
        set: [],
        values: []
    };
}
export function builder(hooks = createHooks()) {
    const fn = builderFn;
    ok(fn);
    fn.hooks = hooks;
    fn.build = build;
    ok(fn);
    fn.use = fn;
    const builder = fn;
    return fn;
    function build(...args) {
        const coreStore = getStore();
        let store = coreStore;
        ok(store);
        for (const on of STORAGE_HOOK_ON_FUNCTION) {
            store = buildOn(store, on);
            ok(store);
        }
        return store;
        function getStore() {
            if (args.length === 1) {
                const [store] = args;
                if (typeof store !== "string") {
                    ok(store);
                    ok(store.get);
                    ok(store.set);
                    return store;
                }
            }
            const [name, options] = args;
            ok(typeof name === "string");
            return getKeyValueStore(name, isLike(options) ? options : undefined);
        }
        function buildOn(store, on) {
            if (!isStorageHookOnFunction(on)) {
                return store;
            }
            const onHooks = hooks[on];
            if (!onHooks.length) {
                return store;
            }
            const before = onHooks.filter(hook => isStage(hook, STORAGE_HOOK_BEFORE));
            const after = onHooks.filter(hook => isStage(hook, STORAGE_HOOK_AFTER));
            let fn = store[on];
            for (const { handler } of before) {
                ok(handler);
                fn = createBeforeFn(fn, handler);
            }
            for (const { handler } of after) {
                ok(handler);
                fn = createAfterFn(fn, handler);
            }
            let assigning = store;
            if (assigning === coreStore) {
                // Make a clone the first time we assign
                assigning = {
                    ...assigning
                };
            }
            return Object.assign(assigning, { [on]: fn });
            function createAfterFn(fn, handler) {
                const name = `after${on}Hook`;
                const namedFn = {
                    // Maybe this will actually show up as the name for the call... idk
                    async [name](...args) {
                        const returnedValue = await fn.call(store, ...args);
                        return handler.call(store, ...args, returnedValue) ?? returnedValue;
                    }
                };
                return namedFn[name];
            }
            function createBeforeFn(fn, handler) {
                const name = `before${on}Hook`;
                const namedFn = {
                    async [name](...args) {
                        const returnedValue = handler.call(store, ...args);
                        if (isPromise(returnedValue)) {
                            // Ignore the returned value
                            // The handler can directly modify arguments if super needed
                            await returnedValue;
                        }
                        return fn.call(store, ...args);
                    }
                };
                return namedFn[name];
            }
            function isStage(hook, stage) {
                if (!hook.stage) {
                    return stage === STORAGE_HOOK_DEFAULT;
                }
                return hook.stage === stage;
            }
        }
    }
    function builderFn(...args) {
        const hook = get();
        push(hooks[hook.on], hook);
        return builder;
        function push(hooks, hook) {
            ok(hooks);
            const was = hook;
            assertHook(was);
            hooks.push(was);
            function assertHook(hook) {
                ok(hook);
                if (hooks.length >= 1) {
                    ok(hook.on === hooks[0].on);
                }
            }
        }
        function get() {
            if (args.length === 1) {
                const hook = args[0];
                ok(hook);
                ok(hook.on);
                ok(hook.handler);
                return hook;
            }
            ok(args.length >= 2);
            const [on, handler, options] = args;
            ok(typeof on === "string");
            ok(on);
            ok(handler);
            const hook = {
                on,
                handler,
                stage: STORAGE_HOOK_DEFAULT
            };
            if (!options)
                return hook;
            ok(options);
            return {
                ...hook,
                ...options
            };
        }
    }
}
//# sourceMappingURL=hook.js.map