/*!
 * Custom elements for Nanopore-Digital Web applications
 */
import { getElement } from "@stencil/core";
import { debounce } from "../throttle";
import * as getset from "./get-set";
const store = new Map();
export const ComponentStore = {
    init(component, keys, method, id) {
        const getId = id || component.storeId || getElement(component).id;
        if (!getId) {
            console.error('No id set. You can only instantiate a new component store with an id. ');
            return;
        }
        if (store.get(getId)) {
            console.error('Duplicate store id usage. A store ID must be unique!', getId);
            return;
        }
        const initialState = keys.reduce((acc, key) => {
            if (component[key] !== undefined)
                acc[key] = component[key];
            return acc;
        }, {});
        store.set(getId, new Store(getId, component, keys, initialState, method));
        const componentStore = store.get(getId);
        componentStore.dispatchToProps();
        const connectedCallback = component.connectedCallback;
        component.connectedCallback = function () {
            store.set(getId, new Store(getId, component, keys, initialState, method));
            const componentStore = store.get(getId);
            if (connectedCallback) {
                connectedCallback.call(component);
            }
            if (componentStore)
                componentStore.dispatchToProps();
        };
        const componentDidRender = component.componentDidRender;
        component.componentDidRender = function () {
            const componentStore = store.get(getId);
            if (componentDidRender) {
                componentDidRender.call(component);
            }
            if (componentStore)
                componentStore.savePropsToStore();
        };
        const disconnectedCallback = component.disconnectedCallback;
        component.disconnectedCallback = function () {
            const componentStore = store.get(getId);
            if (componentStore)
                componentStore.delete();
            store.delete(getId);
            if (disconnectedCallback) {
                disconnectedCallback.call(component);
            }
        };
    },
    update(id) {
        const componentStore = store.get(id);
        if (componentStore) {
            componentStore.savePropsToStore();
        }
        else {
            console.error(`No store found for id: ${id}`);
        }
    },
    get(id) {
        return store.get(id);
    },
};
class Store {
    method = 'session';
    keys;
    id;
    component;
    updateStore = true;
    initialState = {};
    constructor(id, component, keys, initialState, method) {
        if (!id)
            throw new Error('No id set. You can only instantiate a new component store with an id');
        this.id = id;
        this.keys = keys;
        this.component = component;
        if (initialState)
            this.initialState = initialState;
        if (method)
            this.method = method;
        this.savePropsToStore = debounce(this.savePropsToStore, 500);
        if (this.method === 'url-hash-push' || this.method === 'url-query-push') {
            window.addEventListener('popstate', this.onUrlChange);
        }
    }
    onUrlChange = () => {
        this.dispatchToProps(false);
    };
    equality(val1, val2) {
        if (typeof val1 !== typeof val2)
            return false;
        if (typeof val1 !== 'object')
            return val1 === val2;
        return JSON.stringify(val1) === JSON.stringify(val2);
    }
    get getData() {
        const data = this.keys.reduce((acc, key) => {
            const isEqual = this.equality(this.component[key], this.initialState[key]);
            if (typeof this.component[key] !== 'undefined' && !isEqual) {
                acc[key] = this.component[key];
            }
            else if (isEqual)
                delete acc[key];
            return acc;
        }, {});
        return Object.keys(data).length ? data : undefined;
    }
    retrieve() {
        switch (this.method) {
            case 'url-hash':
            case 'url-hash-push':
                return getset.urlGet(this.id, 'hash');
            case 'url-query':
            case 'url-query-push':
                return getset.urlGet(this.id, 'query');
            case 'local':
                return getset.storageGet(this.id);
            default:
                return getset.sessionGet(this.id);
        }
    }
    save(data) {
        switch (this.method) {
            case 'url-hash':
                return getset.urlSet(this.id, data, 'hash');
            case 'url-query':
                return getset.urlSet(this.id, data, 'query');
            case 'url-hash-push':
                return getset.urlSet(this.id, data, 'hash', window, 'push');
            case 'url-query-push':
                return getset.urlSet(this.id, data, 'query', window, 'push');
            case 'local':
                return getset.storageSet(this.id, data);
            default:
                return getset.sessionSet(this.id, data);
        }
    }
    savePropsToStore = () => {
        if (this.updateStore) {
            this.save(this.getData);
        }
        else if (!this.updateStore)
            this.updateStore = true;
    };
    dispatchToProps = (updateStore = true) => {
        const savedData = this.retrieve();
        if (!savedData && updateStore)
            return;
        // console.log('dispatching', savedData, this.id, this.initialState);
        this.updateStore = updateStore;
        this.keys.forEach((key) => {
            this.component[key] = savedData ? savedData[key] : undefined;
        });
    };
    delete = () => {
        if (this.method === 'url-hash-push' || this.method === 'url-query-push') {
            window.removeEventListener('popstate', this.onUrlChange);
        }
    };
}
