import type { PartializedState } from "./createConfig.js";
import type { Evaluate } from "./types/utils.js";

import { deserialize as deserialize_ } from "./utils/deserialize.js";
import { serialize as serialize_ } from "./utils/serialize.js";

// key-values for loose autocomplete and typing
export type StorageItemMap = {
    recentConnectorId: string;
    state: PartializedState;
};

export type Storage<
    itemMap extends Record<string, unknown> = Record<string, unknown>,
    ///
    storageItemMap extends StorageItemMap = StorageItemMap & itemMap,
> = {
    key: string;
    getItem<
        key extends keyof storageItemMap,
        value extends storageItemMap[key],
        defaultValue extends value | null | undefined,
    >(
        key: key,
        defaultValue?: defaultValue | undefined,
    ):
        | (defaultValue extends null ? value | null : value)
        | Promise<defaultValue extends null ? value | null : value>;
    setItem<key extends keyof storageItemMap, value extends storageItemMap[key] | null>(
        key: key,
        value: value,
    ): void | Promise<void>;
    removeItem(key: keyof storageItemMap): void | Promise<void>;
};

export type BaseStorage = {
    getItem(key: string): string | null | undefined | Promise<string | null | undefined>;
    setItem(key: string, value: string): void | Promise<void>;
    removeItem(key: string): void | Promise<void>;
};

export type CreateStorageParameters = {
    deserialize?: (<T>(value: string) => T) | undefined;
    key?: string | undefined;
    serialize?: (<T>(value: T) => string) | undefined;
    storage?: Evaluate<BaseStorage> | undefined;
};

export function createStorage<
    itemMap extends Record<string, unknown> = Record<string, unknown>,
    storageItemMap extends StorageItemMap = StorageItemMap & itemMap,
>(parameters: CreateStorageParameters): Evaluate<Storage<storageItemMap>> {
    const {
        deserialize = deserialize_,
        key: prefix = "trade.renegade",
        serialize = serialize_,
        storage = noopStorage,
    } = parameters;

    function unwrap<type>(value: type): type | Promise<type> {
        if (value instanceof Promise) return value.then((x) => x).catch(() => null);
        return value;
    }

    return {
        ...storage,
        key: prefix,
        async getItem(key, defaultValue) {
            const value = storage.getItem(`${prefix}.${key as string}`);
            const unwrapped = await unwrap(value);
            if (unwrapped) return deserialize(unwrapped) ?? null;
            return (defaultValue ?? null) as any;
        },
        async setItem(key, value) {
            const storageKey = `${prefix}.${key as string}`;
            if (value === null) await unwrap(storage.removeItem(storageKey));
            else await unwrap(storage.setItem(storageKey, serialize(value)));
        },
        async removeItem(key) {
            await unwrap(storage.removeItem(`${prefix}.${key as string}`));
        },
    };
}

export const noopStorage = {
    getItem: () => null,
    setItem: () => {},
    removeItem: () => {},
} satisfies BaseStorage;
