/*!
 * Custom elements for Nanopore-Digital Web applications
 */
import { Host, writeTask, } from "@stencil/core";
import { ComponentStore, } from "../../utils/store/component-store";
import { h } from "../../utils/renderer";
import { Wormhole, } from "./algolia-data";
/**
 * Displays and syncs algolia filters
 * Must be nested within an nano-algolia component.
 * @slot filter-template - Template string (format from nano-algolia 'tplEngine' property).
 * @slot output - A placeholder for template output.
 */
export class AlgoliaFilter {
    el;
    templateStr;
    templateSlot;
    outputSlot;
    outputEle;
    filterId = null;
    tplDidRnder;
    tplWillRnder = new Promise((resolve) => {
        this.tplDidRnder = resolve;
    });
    changeEvent;
    internalValue = [undefined];
    showFilter = false;
    indexResults = null;
    filterChanged = null;
    facets;
    tplRenderFn;
    globalStoreMethod;
    /**
     * Name of this filter - must match an appropriate algolia facet on a parent nano-algolia index to display
     */
    filterName = null;
    /**
     * Set values of this filter. Will result in form elements being selected. '|' delimited string or array.
     */
    value = '';
    /**
     * The internal operator joining filter values.
     */
    operator = 'or';
    /**
     * Store search queries (against this ID) to the component store.
     * Use in conjunction with storeMethod
     */
    storeId;
    /**
     * The method of storage.
     * Either session storage, url hash (after the '#') or url query (after the '?').
     */
    storeMethod = 'session';
    /**
     * Emitted when the filter value changes
     */
    nanoFilterChanged;
    /**
     * Emitted when templates are updated
     */
    nanoTplUpdated;
    // Public facing. When changed attempts to 'check' any relevant fields in the template.
    valueSet() {
        const valStr = this.value;
        let vals;
        if (typeof valStr === 'string')
            vals = valStr.length ? valStr.split('|') : [];
        else
            vals = this.value;
        if (this.didValueChange(vals)) {
            const foundVals = this.setFieldValues(vals);
            // found in template, set the internal state (this emits the changed event)
            if (foundVals && foundVals.length)
                this.internalValue = vals;
            // not found in template, wait for it to render.
            else {
                this.tplWillRnder.then((_) => {
                    this.setFieldValues(vals);
                    this.internalValue = vals;
                });
            }
        }
    }
    // Value has changed and is reflected in template. Emit event.
    valueChanged() {
        this.value = this.internalValue;
        this.nanoFilterChanged.emit({
            filterId: this.filterId,
            facetName: this.filterName,
            value: this.internalValue,
            operator: this.operator,
        });
    }
    // New facets from algolia. Update template
    appliedFilterChanged() {
        if (this.filterName &&
            this.filterChanged &&
            (this.filterChanged !== this.filterId || this.operator === 'and')) {
            this.updateContent();
        }
    }
    tplUpdate() {
        this.updateContent();
    }
    handleFilterChange() {
        if (this.indexResults && this.indexResults.appliedFilters) {
            const foundValue = this.indexResults.appliedFilters.find((filter) => filter.name === this.filterName);
            if (!foundValue)
                return;
            this.tplWillRnder.then((_) => {
                const foundVals = this.setFieldValues(foundValue.values);
                if (foundVals && foundVals.length)
                    this.value = foundVals;
            });
        }
        this.updateContent();
    }
    handleChangeEvent() {
        const vals = this.getFieldValues();
        if (this.didValueChange(vals)) {
            this.internalValue = vals;
            return true;
        }
        return false;
    }
    updateContent() {
        const outputTo = this.outputSlot || this.outputEle;
        if (!this.el)
            return;
        // there's no template so show output right away
        if (!this.templateStr)
            this.showFilter = true;
        // there's a template but no index set so hide filters atm
        else if (!this.filterName ||
            !outputTo ||
            (this.templateStr &&
                (!this.indexResults || !this.indexResults.origFilters[this.filterName]))) {
            this.showFilter = false;
        }
        // there's no output element so nothing else to do
        if (!outputTo)
            return;
        // there's no index set atm but get any static filter values so we can apply them
        if (!this.indexResults) {
            if (this.templateStr) {
                writeTask(() => {
                    outputTo.innerHTML = this.tplRenderFn(this.templateStr, {
                        orig: {},
                        dyn: {},
                        selected: this.internalValue && this.internalValue.length
                            ? this.internalValue
                            : this.value,
                    });
                    if (!this.value || !this.value.length)
                        this.handleChangeEvent();
                });
            }
            else if (!this.value || !this.value.length)
                this.handleChangeEvent();
            return;
        }
        // everything is in place - render as normal and set values
        writeTask(() => {
            outputTo.innerHTML = this.tplRenderFn(this.templateStr, {
                orig: { ...this.indexResults.origFilters[this.filterName] },
                dyn: { ...this.indexResults.dynFilters[this.filterName] },
                selected: this.internalValue && this.internalValue.length
                    ? this.internalValue
                    : this.value,
            });
            this.tplDidRnder();
            this.setFieldValues();
            if (!this.showFilter)
                this.showFilter = true;
            this.nanoTplUpdated.emit(this.el);
        });
    }
    // go through form elements and extract their current value
    getFieldValues() {
        const values = [];
        const fields = this.outputSlot.querySelectorAll('input[type="checkbox"], input[type="radio"], select');
        for (let i = 0; i < fields.length; i++) {
            const field = fields[i];
            // If a multi-select, get all selections
            if (field.type === 'select-multiple') {
                const select = field;
                for (let n = 0; n < select.options.length; n++) {
                    if (!select.options[n].selected || !select.options[n].value.length)
                        continue;
                    values.push(select.options[n].value);
                }
            }
            else if ((field.type !== 'checkbox' && field.type !== 'radio') ||
                (field.checked && field.value.length)) {
                values.push(field.value);
            }
        }
        return values;
    }
    // go through form elements and select / check them as appropriate
    setFieldValues(vals) {
        vals = vals || this.internalValue;
        if (!vals)
            return;
        const found = [];
        const fields = this.outputSlot.querySelectorAll('input[type="checkbox"], input[type="radio"], select, nano-select, nano-checkbox');
        for (let i = 0; i < fields.length; i++) {
            const field = fields[i];
            if (field.disabled ||
                field.type === 'file' ||
                field.type === 'reset' ||
                field.type === 'submit' ||
                field.type === 'button')
                continue;
            if (field.type === 'select-multiple' || field.type === 'select-one') {
                const select = field;
                for (let n = 0; n < select.options.length; n++) {
                    if (!vals.includes(select.options[n].value))
                        select.options[n].selected = false;
                    else {
                        found.push(select.value);
                        select.options[n].selected = true;
                        if (field.type === 'select-one')
                            select.value = select.options[n].value;
                    }
                }
            }
            else if (['checkbox', 'radio', 'tag', 'segment', 'segment-pill'].includes(field.type)) {
                if (vals.includes(field.value)) {
                    found.push(field.value);
                    field.checked = true;
                }
                else if (!vals.length && !field.value.length) {
                    field.checked = true;
                }
                else
                    field.checked = false;
            }
        }
        return found;
    }
    didValueChange(newVal) {
        if (!!this.internalValue && !newVal)
            return true;
        if (!this.internalValue && !newVal)
            return false;
        if (this.internalValue && newVal.length !== this.internalValue.length)
            return true;
        let hasChanged = false;
        for (let i = 0; i < newVal.length; i++) {
            if (!hasChanged &&
                (!this.internalValue || !this.internalValue.includes(newVal[i])))
                hasChanged = true;
        }
        return hasChanged;
    }
    connectedCallback() {
        this.templateSlot = this.el.querySelector('[slot="filter-template"]');
        this.outputSlot = this.el.querySelector('[slot="output"]');
        this.filterId = this.filterName + '_' + filterIds++;
        if (this.templateSlot) {
            this.templateStr = this.templateSlot.innerHTML;
            this.templateStr = this.templateStr.replace(/=&gt;/gm, '=>');
        }
    }
    componentWillLoad() {
        if (this.storeId)
            ComponentStore.init(this, ['value'], this.storeMethod || this.globalStoreMethod, this.storeId);
    }
    componentDidLoad() {
        this.updateContent();
        if (this.templateStr &&
            this.filterName &&
            this.indexResults &&
            this.indexResults.origFilters[this.filterName])
            this.showFilter = true;
    }
    render() {
        return (h(Host, { key: 'c1d229a764c08c92c4f25093d98bf51e1b4f6bcf', class: {
                'show-filter': this.showFilter,
                'nano-filter': true,
            } }, h("div", { key: '21f66d2dcd03e19aa46face1220459dce136ccd1', ref: (div) => (this.outputEle = div) }, h("slot", { key: '3ec8e00ee477c651e48e96db7888f0a751573a3e', name: "output" }))));
    }
    static get is() { return "nano-algolia-filter"; }
    static get encapsulation() { return "shadow"; }
    static get originalStyleUrls() {
        return {
            "$": ["algolia-filter.scss"]
        };
    }
    static get styleUrls() {
        return {
            "$": ["algolia-filter.css"]
        };
    }
    static get properties() {
        return {
            "filterName": {
                "type": "string",
                "attribute": "filter-name",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Name of this filter - must match an appropriate algolia facet on a parent nano-algolia index to display"
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "null"
            },
            "value": {
                "type": "string",
                "attribute": "value",
                "mutable": true,
                "complexType": {
                    "original": "string | Array<string>",
                    "resolved": "string | string[]",
                    "references": {
                        "Array": {
                            "location": "global",
                            "id": "global::Array"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "Set values of this filter. Will result in form elements being selected. '|' delimited string or array."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "''"
            },
            "operator": {
                "type": "string",
                "attribute": "operator",
                "mutable": false,
                "complexType": {
                    "original": "'or' | 'and'",
                    "resolved": "\"and\" | \"or\"",
                    "references": {}
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The internal operator joining filter values."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "'or'"
            },
            "storeId": {
                "type": "string",
                "attribute": "store-id",
                "mutable": false,
                "complexType": {
                    "original": "string",
                    "resolved": "string",
                    "references": {}
                },
                "required": false,
                "optional": true,
                "docs": {
                    "tags": [],
                    "text": "Store search queries (against this ID) to the component store.\nUse in conjunction with storeMethod"
                },
                "getter": false,
                "setter": false,
                "reflect": false
            },
            "storeMethod": {
                "type": "string",
                "attribute": "store-method",
                "mutable": false,
                "complexType": {
                    "original": "StorageMethods",
                    "resolved": "\"local\" | \"session\" | \"url-hash\" | \"url-hash-push\" | \"url-query\" | \"url-query-push\"",
                    "references": {
                        "StorageMethods": {
                            "location": "import",
                            "path": "../../utils/store/component-store",
                            "id": "src/utils/store/component-store.ts::StorageMethods"
                        }
                    }
                },
                "required": false,
                "optional": false,
                "docs": {
                    "tags": [],
                    "text": "The method of storage.\nEither session storage, url hash (after the '#') or url query (after the '?')."
                },
                "getter": false,
                "setter": false,
                "reflect": false,
                "defaultValue": "'session'"
            }
        };
    }
    static get states() {
        return {
            "changeEvent": {},
            "internalValue": {},
            "showFilter": {},
            "indexResults": {},
            "filterChanged": {},
            "facets": {},
            "tplRenderFn": {},
            "globalStoreMethod": {}
        };
    }
    static get events() {
        return [{
                "method": "nanoFilterChanged",
                "name": "nanoFilterChanged",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when the filter value changes"
                },
                "complexType": {
                    "original": "FilterChangeEventDetail",
                    "resolved": "FilterChangeEventDetail",
                    "references": {
                        "FilterChangeEventDetail": {
                            "location": "import",
                            "path": "./algolia-interface",
                            "id": "src/components/algolia/algolia-interface.ts::FilterChangeEventDetail"
                        }
                    }
                }
            }, {
                "method": "nanoTplUpdated",
                "name": "nanoTplUpdated",
                "bubbles": true,
                "cancelable": true,
                "composed": true,
                "docs": {
                    "tags": [],
                    "text": "Emitted when templates are updated"
                },
                "complexType": {
                    "original": "HTMLNanoAlgoliaFilterElement",
                    "resolved": "HTMLNanoAlgoliaFilterElement",
                    "references": {
                        "HTMLNanoAlgoliaFilterElement": {
                            "location": "global",
                            "id": "global::HTMLNanoAlgoliaFilterElement"
                        }
                    }
                }
            }];
    }
    static get elementRef() { return "el"; }
    static get watchers() {
        return [{
                "propName": "value",
                "methodName": "valueSet"
            }, {
                "propName": "internalValue",
                "methodName": "valueChanged"
            }, {
                "propName": "facets",
                "methodName": "appliedFilterChanged"
            }, {
                "propName": "tplRenderFn",
                "methodName": "tplUpdate"
            }, {
                "propName": "indexResults",
                "methodName": "handleFilterChange"
            }];
    }
    static get listeners() {
        return [{
                "name": "nanoChange",
                "method": "handleChangeEvent",
                "target": undefined,
                "capture": false,
                "passive": false
            }, {
                "name": "change",
                "method": "handleChangeEvent",
                "target": undefined,
                "capture": false,
                "passive": false
            }];
    }
}
Wormhole(AlgoliaFilter, [
    'globalStoreMethod',
    'indexResults',
    'filterChanged',
    'facets',
    'tplRenderFn',
    'changeEvent',
]);
let filterIds = 0;
