/*!
 * Custom elements for Nanopore-Digital Web applications
 */
import { g as getElement } from './index-BM3Om9WE.js';
import { d as debounce } from './throttle-C93FMm2Z.js';

function parse(searchParams, replacer) {
    const params = new URLSearchParams([...new URLSearchParams(searchParams)].map(([k, v]) => [
        k.replace(/([[\]])/g, (_, s) => s === '[' ? '.' : ''),
        v,
    ]));
    const t = {};
    new Set(params.keys()).forEach((paramKey) => {
        const rawValue = params
            .getAll(paramKey)
            .join(',')
            .split(',')
            .map(n => decodeURIComponent(n.replace(/\+/g, ' ')));
        const value = rawValue.length === 1 ? rawValue[0] : rawValue;
        let key = paramKey;
        let v = value;
        if (key.includes('.')) {
            const [f, ...kl] = key.split('.');
            const o = (t[f] || {});
            const l = kl.pop();
            (kl.reduce((p, n) => (p[n] ??= {}), o))[l] = v;
            key = f;
            v = o;
        }
        t[key] = v;
    });
    return t;
}

function stringify(input, replacer) {
    if (!input)
        return '';
    const stack = [['', input]];
    const sp = new URLSearchParams();
    while (stack.length) {
        const [itemKey, itemValue] = stack.pop();
        Object.entries(itemValue).forEach(([key, rawValue]) => {
            if (rawValue == null && replacer == null)
                return;
            const typeOfValue = typeof (rawValue);
            const flattenedKey = itemKey ? `${itemKey}.${key}` : key;
            if (rawValue !== null && typeOfValue === 'object' && !Array.isArray(rawValue)) {
                stack.push([flattenedKey, rawValue]);
            }
            else {
                let value = typeOfValue === 'string' ? rawValue : '';
                if (Array.isArray(rawValue))
                    value = rawValue.join(',');
                else if (rawValue?.toJSON)
                    value = rawValue.toJSON();
                else if (rawValue?.toString)
                    value = rawValue.toString();
                else
                    value = String(rawValue);
                const vv = value;
                vv != null && sp.append(flattenedKey, vv);
            }
        });
    }
    sp.sort();
    return sp.toString();
}

const STORAGE_NAME_SPACE = 'nano-component-store:';
const urlRead = (from = 'hash', win = window) => {
    if (!win || !win.location)
        return {};
    if (from === 'hash' && win.location.hash && win.location.hash.match(/&|=/))
        return parse(win.location.hash.replace(/^#/, ''));
    else if (from === 'query' &&
        win.location.search &&
        win.location.search.match(/&|=/))
        return parse(win.location.search.replace(/^\?/, ''));
    return {};
};
const urlSave = (object, to = 'hash', win = window, method = 'replace') => {
    if (!win || !win.history)
        return;
    const oldQuery = to === 'query' ? win.location.search : win.location.hash;
    const basePath = to === 'query'
        ? win.location.pathname
        : win.location.pathname + win.location.search;
    let queryStr = stringify(object);
    if (!queryStr || !queryStr.length) {
        if (!oldQuery || !oldQuery.length)
            return;
        if (method === 'replace')
            win.history.replaceState(null, null, basePath);
        else
            win.history.pushState(null, null, basePath);
        return;
    }
    if (to === 'hash' && win.location.hash === '#' + queryStr) {
        try {
            document.querySelector('#' + queryStr)?.focus();
            // eslint-disable-next-line no-empty
        }
        catch (_e) { }
        return;
    }
    queryStr = (to === 'hash' ? '#' : '?') + queryStr;
    if (queryStr === oldQuery)
        return;
    if (method === 'replace')
        win.history.replaceState(null, null, basePath + queryStr);
    else
        win.history.pushState(null, null, basePath + queryStr);
};
const urlSet = (id, data, to = 'hash', win = window, method = 'replace') => {
    // sanitize the incoming key
    id = Object.keys(parse(id))[0];
    const object = {};
    // delete all empty incoming values
    for (const key in data) {
        if (!data[key] || (Array.isArray(data[key]) && !data[key].length)) {
            delete data[key];
        }
    }
    object[id] = data;
    let currentData = urlRead(to, win);
    // current data is empty and incoming data is empty
    // let's not do anything
    if (!currentData[id] && !object[id])
        return;
    // let's merge current and incoming data and write it to the url
    currentData = { ...currentData, ...object };
    urlSave(currentData, to, win, method);
};
const urlGet = (key, to = 'hash', win = window) => {
    // sanitize the incoming key
    key = Object.keys(parse(key))[0];
    const currentData = urlRead(to, win);
    return currentData[key] || null;
};
const sessionSet = (key, object, win = window) => {
    if (!object) {
        win.sessionStorage.removeItem(STORAGE_NAME_SPACE + key);
        return;
    }
    win.sessionStorage.setItem(STORAGE_NAME_SPACE + key, JSON.stringify(object));
};
const sessionGet = (key, win = window) => {
    const configStr = win.sessionStorage.getItem(STORAGE_NAME_SPACE + key);
    return configStr !== null ? JSON.parse(configStr) : null;
};
const storageSet = (key, object, win = window) => {
    if (!object) {
        win.localStorage.removeItem(STORAGE_NAME_SPACE + key);
        return;
    }
    win.localStorage.setItem(STORAGE_NAME_SPACE + key, JSON.stringify(object));
};
const storageGet = (key, win = window) => {
    const configStr = win.localStorage.getItem(STORAGE_NAME_SPACE + key);
    return configStr !== null ? JSON.parse(configStr) : null;
};

const store = new Map();
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 urlGet(this.id, 'hash');
            case 'url-query':
            case 'url-query-push':
                return urlGet(this.id, 'query');
            case 'local':
                return storageGet(this.id);
            default:
                return sessionGet(this.id);
        }
    }
    save(data) {
        switch (this.method) {
            case 'url-hash':
                return urlSet(this.id, data, 'hash');
            case 'url-query':
                return urlSet(this.id, data, 'query');
            case 'url-hash-push':
                return urlSet(this.id, data, 'hash', window, 'push');
            case 'url-query-push':
                return urlSet(this.id, data, 'query', window, 'push');
            case 'local':
                return storageSet(this.id, data);
            default:
                return 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);
        }
    };
}

export { ComponentStore as C };
