export function listen(...listenerSpecs) {
    return new Promise((resolve) => {
        let removeAllEventListeners;

        let listeners = listenerSpecs.map(([element, eventName]) => {
            let listener = (event) => {
                removeAllEventListeners();
                resolve(event);
            };
            element.addEventListener(eventName, listener);
            return { element, eventName, func: listener };
        });

        removeAllEventListeners = () => {
            for (let listener of listeners) {
                listener.element.removeEventListener(listener.eventName, listener.func);
            }
        };
    });
}

export function debounce(time, fn) {
    // This function is kind of tricky, but basically:
    //
    // - we want to guarantee `fn` is called at most once every `time` milliseconds
    // - at the same time, in case debounced `fn` is called during an ongoing timeout, we want to
    //   queue up another run, and run it immediately after `time` passes
    //   - at the same time, in case this catch-up condition occurs, we must also ensure there's a
    //     delay after `fn` is called
    //
    // yielding the recursive solution below.

    let timeout = null;
    let queued = null;

    const callFn = (args) => {
        fn(...args);

        timeout = setTimeout(() => {
            timeout = null;
            if (queued != null) {
                callFn(queued);
                queued = null;
            }
        }, time);
    };

    return (...args) => {
        if (timeout == null) {
            callFn(args);
        } else {
            queued = args;
        }
    };
}

export class SaveData {
    constructor(prefix) {
        this.prefix = `rkgk.${prefix}`;
        this.elementId = "<global>";
    }

    #localStorageKey(key) {
        return `${this.prefix}.${this.elementId}.${key}`;
    }

    attachToElement(element) {
        this.elementId = element.dataset.storageId;
    }

    getRaw(key) {
        return localStorage.getItem(this.#localStorageKey(key));
    }

    setRaw(key, value) {
        localStorage.setItem(this.#localStorageKey(key), value);
    }

    get(key, defaultValue) {
        let value = this.getRaw(key);
        if (value == null) {
            return defaultValue;
        } else {
            try {
                return JSON.parse(value);
            } catch (e) {
                throw new Error(`${this.#localStorageKey(key)}`, { cause: e });
            }
        }
    }

    set(key, value) {
        this.setRaw(key, JSON.stringify(value));
    }
}
