/*!
 * Custom elements for Nanopore-Digital Web applications
 */
import { i as getRenderingRef, g as getElement, r as registerInstance, c as createEvent, d as writeTask, e as readTask, a as Host } from './index-BM3Om9WE.js';
import { a as algoliasearch } from './algoliasearch-lite.esm.browser-BTIloVU8.js';
import { g as getDirectChildren } from './dom-dlicJTEJ.js';
import { C as ComponentStore } from './component-store-JYVuxwvK.js';
import { d as debounce } from './throttle-C93FMm2Z.js';
import { t as transformTag, h } from './renderer-DpNDfhNy.js';
import { S as SearchChangeEvent } from './algolia-data-Kh0vPn3V.js';

var multiverse = new Map();
var updateConsumer = function (_a, state) {
    var fields = _a.fields, updater = _a.updater;
    fields.forEach(function (field) { updater(field, state[field]); });
};
var Universe = {
    create: function (creator, initialState) {
        var el = getElement(creator);
        var wormholes = new Map();
        var universe = { wormholes: wormholes, state: initialState };
        multiverse.set(creator, universe);
        var connectedCallback = creator.connectedCallback;
        creator.connectedCallback = function () {
            multiverse.set(creator, universe);
            if (connectedCallback) {
                connectedCallback.call(creator);
            }
        };
        var disconnectedCallback = creator.disconnectedCallback;
        creator.disconnectedCallback = function () {
            multiverse.delete(creator);
            if (disconnectedCallback) {
                disconnectedCallback.call(creator);
            }
        };
        el.addEventListener('openWormhole', function (event) {
            event.stopPropagation();
            var _a = event.detail, consumer = _a.consumer, onOpen = _a.onOpen;
            if (wormholes.has(consumer))
                return;
            if (typeof consumer !== 'symbol') {
                var connectedCallback_1 = consumer.connectedCallback, disconnectedCallback_1 = consumer.disconnectedCallback;
                consumer.connectedCallback = function () {
                    wormholes.set(consumer, event.detail);
                    if (connectedCallback_1) {
                        connectedCallback_1.call(consumer);
                    }
                };
                consumer.disconnectedCallback = function () {
                    wormholes.delete(consumer);
                    if (disconnectedCallback_1) {
                        disconnectedCallback_1.call(consumer);
                    }
                };
            }
            wormholes.set(consumer, event.detail);
            updateConsumer(event.detail, universe.state);
            onOpen === null || onOpen === void 0 ? void 0 : onOpen.resolve(function () { wormholes.delete(consumer); });
        });
        el.addEventListener('closeWormhole', function (event) {
            var consumer = event.detail;
            wormholes.delete(consumer);
        });
    },
    Provider: function (_a, children) {
        var state = _a.state;
        var creator = getRenderingRef();
        if (multiverse.has(creator)) {
            var universe = multiverse.get(creator);
            universe.state = state;
            universe.wormholes.forEach(function (opening) { updateConsumer(opening, state); });
        }
        return children;
    }
};

class PromiseQueue {
    queue = [];
    workingOnPromise = false;
    static pendingPromise = false;
    stop = false;
    limit = null;
    enqueue(promise) {
        return new Promise((resolve, reject) => {
            this.queue.push({
                promise,
                resolve,
                reject,
            });
            this.dequeue();
        });
    }
    dequeue() {
        if (this.workingOnPromise) {
            return false;
        }
        if (this.stop) {
            this.queue = [];
            this.stop = false;
            return;
        }
        const item = this.queue.shift();
        if (!item) {
            return false;
        }
        try {
            this.workingOnPromise = true;
            item
                .promise()
                .then((value) => {
                this.workingOnPromise = false;
                item.resolve(value);
                this.dequeue();
            })
                .catch((err) => {
                this.workingOnPromise = false;
                item.reject(err);
                this.dequeue();
            });
        }
        catch (err) {
            this.workingOnPromise = false;
            item.reject(err);
            this.dequeue();
        }
        return true;
    }
}

function setPrototypeOf(obj, proto) {
    if (Object.setPrototypeOf) {
        Object.setPrototypeOf(obj, proto);
    }
    else {
        obj.__proto__ = proto;
    }
}
function SqrlErr(message) {
    const err = new Error(message);
    setPrototypeOf(err, SqrlErr.prototype);
    return err;
}
SqrlErr.prototype = Object.create(Error.prototype, {
    name: { value: 'Squirrelly Error', enumerable: false },
});
// TODO: Class transpilation adds a lot to the bundle size
function ParseErr(message, str, indx) {
    const whitespace = str.slice(0, indx).split(/\n/);
    const lineNo = whitespace.length;
    const colNo = whitespace[lineNo - 1].length + 1;
    message +=
        ' at line ' +
            lineNo +
            ' col ' +
            colNo +
            ':\n\n' +
            '  ' +
            str.split(/\n/)[lineNo - 1] +
            '\n' +
            '  ' +
            Array(colNo).join(' ') +
            '^';
    throw SqrlErr(message);
}

// TODO: allow '-' to trim up until newline. Use [^\S\n\r] instead of \s
// TODO: only include trimLeft polyfill if not in ES6
/* END TYPES */
const promiseImpl = new Function('return this')().Promise;
let asyncFunc = false;
try {
    asyncFunc = new Function('return (async function(){}).constructor')();
}
catch (e) {
    // We shouldn't actually ever have any other errors, but...
    if (!(e instanceof SyntaxError)) {
        throw e;
    }
}
function hasOwnProp(obj, prop) {
    return Object.prototype.hasOwnProperty.call(obj, prop);
}
function copyProps(toObj, fromObj, notConfig) {
    for (const key in fromObj) {
        if (hasOwnProp(fromObj, key)) {
            if (fromObj[key] != null &&
                typeof fromObj[key] == 'object' &&
                (key === 'storage' || key === 'prefixes') &&
                !notConfig // not called from Cache.load
            ) {
                // plugins or storage
                // Note: this doesn't merge from initial config!
                // Deep clone instead of assigning
                // TODO: run checks on this
                toObj[key] = copyProps(
                /*toObj[key] ||*/ {}, fromObj[key]);
            }
            else {
                toObj[key] = fromObj[key];
            }
        }
    }
    return toObj;
}
function trimWS(str, env, wsLeft, wsRight) {
    let leftTrim;
    let rightTrim;
    if (typeof env.autoTrim === 'string') {
        leftTrim = rightTrim = env.autoTrim;
        // Don't need to check if env.autoTrim is false
        // Because leftTrim, rightTrim are initialized as falsy
    }
    else if (Array.isArray(env.autoTrim)) {
        // kinda confusing
        // but _}} will trim the left side of the following string
        leftTrim = env.autoTrim[1];
        rightTrim = env.autoTrim[0];
    }
    if (wsLeft || wsLeft === false) {
        leftTrim = wsLeft;
    }
    if (wsRight || wsRight === false) {
        rightTrim = wsRight;
    }
    if (leftTrim === 'slurp' && rightTrim === 'slurp') {
        return str.trim();
    }
    if (leftTrim === '_' || leftTrim === 'slurp') {
        // console.log('trimming left' + leftTrim)
        // full slurp
        // eslint-disable-next-line no-extra-boolean-cast
        if (!!String.prototype.trimLeft) {
            str = str.trimLeft();
        }
        else {
            str = str.replace(/^[\s\uFEFF\xA0]+/, '');
        }
    }
    else if (leftTrim === '-' || leftTrim === 'nl') {
        // console.log('trimming left nl' + leftTrim)
        // nl trim
        str = str.replace(/^(?:\n|\r|\r\n)/, '');
    }
    if (rightTrim === '_' || rightTrim === 'slurp') {
        // console.log('trimming right' + rightTrim)
        // full slurp
        // eslint-disable-next-line no-extra-boolean-cast
        if (!!String.prototype.trimRight) {
            str = str.trimRight();
        }
        else {
            str = str.replace(/[\s\uFEFF\xA0]+$/, '');
        }
    }
    else if (rightTrim === '-' || rightTrim === 'nl') {
        // console.log('trimming right nl' + rightTrim)
        // nl trim
        str = str.replace(/(?:\n|\r|\r\n)$/, ''); // TODO: make sure this gets \r\n
    }
    return str;
}

/* END TYPES */
const asyncRegExp = /^async +/;
const templateLitReg = /`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})*}|(?!\${)[^\\`])*`/g;
const singleQuoteReg = /'(?:\\[\s\w"'\\`]|[^\n\r'\\])*?'/g;
const doubleQuoteReg = /"(?:\\[\s\w"'\\`]|[^\n\r"\\])*?"/g;
const specialCharsReg = /[.*+\-?^${}()|[\]\\]/g;
function escapeRegExp(string) {
    // From MDN
    return specialCharsReg.test(string)
        ? string.replace(specialCharsReg, '\\$&') // $& means the whole matched string
        : string;
}
function parse(str, env) {
    /* Adding for EJS compatibility */
    if (env.rmWhitespace) {
        // Code taken directly from EJS
        // Have to use two separate replaces here as `^` and `$` operators don't
        // work well with `\r` and empty lines don't work well with the `m` flag.
        // Essentially, this replaces the whitespace at the beginning and end of
        // each line and removes multiple newlines.
        str = str.replace(/[\r\n]+/g, '\n').replace(/^\s+|\s+$/gm, '');
    }
    /* End rmWhitespace option */
    templateLitReg.lastIndex = 0;
    singleQuoteReg.lastIndex = 0;
    doubleQuoteReg.lastIndex = 0;
    const envPrefixes = env.prefixes;
    const prefixes = [
        envPrefixes.h,
        envPrefixes.b,
        envPrefixes.i,
        envPrefixes.r,
        envPrefixes.c,
        envPrefixes.e,
    ].reduce(function (accumulator, prefix) {
        if (accumulator && prefix) {
            return accumulator + '|' + escapeRegExp(prefix);
        }
        else if (prefix) {
            // accumulator is empty
            return escapeRegExp(prefix);
        }
        else {
            // prefix and accumulator are both empty strings
            return accumulator;
        }
    }, '');
    const parseCloseReg = new RegExp('([|()]|=>)|' + // powerchars
        '(\'|"|`|\\/\\*)|\\s*((\\/)?(-|_)?' + // comments, strings
        escapeRegExp(env.tags[1]) +
        ')', 'g');
    const tagOpenReg = new RegExp('([^]*?)' + escapeRegExp(env.tags[0]) + '(-|_)?\\s*(' + prefixes + ')?\\s*', 'g');
    let startInd = 0;
    let trimNextLeftWs = false;
    function parseTag(tagOpenIndex, currentType) {
        const currentObj = { f: [] };
        let numParens = 0;
        let currentAttribute = 'c'; // default - Valid values: 'c'=content, 'f'=filter, 'fp'=filter params, 'p'=param, 'n'=name
        if (currentType === 'h' || currentType === 'b' || currentType === 'c') {
            currentAttribute = 'n';
        }
        else if (currentType === 'r') {
            currentObj.raw = true;
            currentType = 'i';
        }
        function addAttrValue(indx) {
            const valUnprocessed = str.slice(startInd, indx);
            // console.log(valUnprocessed)
            let val = valUnprocessed.trim();
            if (currentAttribute === 'f') {
                if (val === 'safe') {
                    currentObj.raw = true;
                }
                else {
                    if (env.async && asyncRegExp.test(val)) {
                        val = val.replace(asyncRegExp, '');
                        currentObj.f.push([val, '', true]);
                    }
                    else {
                        currentObj.f.push([val, '']);
                    }
                }
            }
            else if (currentAttribute === 'fp') {
                currentObj.f[currentObj.f.length - 1][1] += val;
            }
            else if (currentAttribute === 'err') {
                if (val) {
                    const found = valUnprocessed.search(/\S/);
                    ParseErr('invalid syntax', str, startInd + found);
                }
            }
            else {
                // if (currentObj[currentAttribute]) { // TODO make sure no errs
                //   currentObj[currentAttribute] += val
                // } else {
                currentObj[currentAttribute] = val;
                // }
            }
            startInd = indx + 1;
        }
        parseCloseReg.lastIndex = startInd;
        let m;
        // tslint:disable-next-line:no-conditional-assignment
        while ((m = parseCloseReg.exec(str)) !== null) {
            const char = m[1];
            const punctuator = m[2];
            const tagClose = m[3];
            const slash = m[4];
            const wsControl = m[5];
            const i = m.index;
            if (char) {
                // Power character
                if (char === '(') {
                    if (numParens === 0) {
                        if (currentAttribute === 'n') {
                            addAttrValue(i);
                            currentAttribute = 'p';
                        }
                        else if (currentAttribute === 'f') {
                            addAttrValue(i);
                            currentAttribute = 'fp';
                        }
                    }
                    numParens++;
                }
                else if (char === ')') {
                    numParens--;
                    if (numParens === 0 && currentAttribute !== 'c') {
                        // Then it's closing a filter, block, or helper
                        addAttrValue(i);
                        currentAttribute = 'err'; // Reset the current attribute
                    }
                }
                else if (numParens === 0 && char === '|') {
                    addAttrValue(i); // this should actually always be whitespace or empty
                    currentAttribute = 'f';
                }
                else if (char === '=>') {
                    addAttrValue(i);
                    startInd += 1; // this is 2 chars
                    currentAttribute = 'res';
                }
            }
            else if (punctuator) {
                if (punctuator === '/*') {
                    const commentCloseInd = str.indexOf('*/', parseCloseReg.lastIndex);
                    if (commentCloseInd === -1) {
                        ParseErr('unclosed comment', str, m.index);
                    }
                    parseCloseReg.lastIndex = commentCloseInd + 2; // since */ is 2 characters, and we're using indexOf rather than a RegExp
                }
                else if (punctuator === "'") {
                    singleQuoteReg.lastIndex = m.index;
                    const singleQuoteMatch = singleQuoteReg.exec(str);
                    if (singleQuoteMatch) {
                        parseCloseReg.lastIndex = singleQuoteReg.lastIndex;
                    }
                    else {
                        ParseErr('unclosed string', str, m.index);
                    }
                }
                else if (punctuator === '"') {
                    doubleQuoteReg.lastIndex = m.index;
                    const doubleQuoteMatch = doubleQuoteReg.exec(str);
                    if (doubleQuoteMatch) {
                        parseCloseReg.lastIndex = doubleQuoteReg.lastIndex;
                    }
                    else {
                        ParseErr('unclosed string', str, m.index);
                    }
                }
                else if (punctuator === '`') {
                    templateLitReg.lastIndex = m.index;
                    const templateLitMatch = templateLitReg.exec(str);
                    if (templateLitMatch) {
                        parseCloseReg.lastIndex = templateLitReg.lastIndex;
                    }
                    else {
                        ParseErr('unclosed string', str, m.index);
                    }
                }
            }
            else if (tagClose) {
                addAttrValue(i);
                startInd = i + m[0].length;
                tagOpenReg.lastIndex = startInd;
                // console.log('tagClose: ' + startInd)
                trimNextLeftWs = wsControl;
                if (slash && currentType === 'h') {
                    currentType = 's';
                } // TODO throw err
                currentObj.t = currentType;
                return currentObj;
            }
        }
        ParseErr('unclosed tag', str, tagOpenIndex);
        return currentObj; // To prevent TypeScript from erroring
    }
    function parseContext(parentObj, firstParse) {
        parentObj.b = []; // assume there will be blocks // TODO: perf optimize this
        parentObj.d = [];
        let lastBlock = false;
        let buffer = [];
        function pushString(strng, shouldTrimRightOfString) {
            if (strng) {
                // if string is truthy it must be of type 'string'
                // TODO: benchmark replace( /(\\|')/g, '\\$1')
                strng = trimWS(strng, env, trimNextLeftWs, // this will only be false on the first str, the next ones will be null or undefined
                shouldTrimRightOfString);
                if (strng) {
                    // replace \ with \\, ' with \'
                    strng = strng.replace(/\\|'/g, '\\$&').replace(/\r\n|\n|\r/g, '\\n');
                    // we're going to convert all CRLF to LF so it doesn't take more than one replace
                    buffer.push(strng);
                }
            }
        }
        // Random TODO: parentObj.b doesn't need to have t: #
        let tagOpenMatch;
        // tslint:disable-next-line:no-conditional-assignment
        while ((tagOpenMatch = tagOpenReg.exec(str)) !== null) {
            const precedingString = tagOpenMatch[1];
            const shouldTrimRightPrecedingString = tagOpenMatch[2];
            const prefix = tagOpenMatch[3] || '';
            let prefixType;
            for (const key in envPrefixes) {
                if (envPrefixes[key] === prefix) {
                    prefixType = key;
                    break;
                }
            }
            pushString(precedingString, shouldTrimRightPrecedingString);
            startInd = tagOpenMatch.index + tagOpenMatch[0].length;
            if (!prefixType) {
                ParseErr('unrecognized tag type: ' + prefix, str, startInd);
            }
            let currentObj = parseTag(tagOpenMatch.index, prefixType);
            // ===== NOW ADD THE OBJECT TO OUR BUFFER =====
            const currentType = currentObj.t;
            if (currentType === 'h') {
                const hName = currentObj.n || '';
                if (env.async && asyncRegExp.test(hName)) {
                    currentObj.a = true;
                    currentObj.n = hName.replace(asyncRegExp, '');
                }
                currentObj = parseContext(currentObj); // currentObj is the parent object
                buffer.push(currentObj);
            }
            else if (currentType === 'c') {
                // tag close
                if (parentObj.n === currentObj.n) {
                    if (lastBlock) {
                        // If there's a previous block
                        lastBlock.d = buffer;
                        parentObj.b.push(lastBlock);
                    }
                    else {
                        parentObj.d = buffer;
                    }
                    // console.log('parentObj: ' + JSON.stringify(parentObj))
                    return parentObj;
                }
                else {
                    ParseErr("Helper start and end don't match", str, tagOpenMatch.index + tagOpenMatch[0].length);
                }
            }
            else if (currentType === 'b') {
                // block
                // TODO: make sure async stuff inside blocks are recognized
                if (lastBlock) {
                    // If there's a previous block
                    lastBlock.d = buffer;
                    parentObj.b.push(lastBlock);
                }
                else {
                    parentObj.d = buffer;
                }
                const blockName = currentObj.n || '';
                if (env.async && asyncRegExp.test(blockName)) {
                    currentObj.a = true;
                    currentObj.n = blockName.replace(asyncRegExp, '');
                }
                lastBlock = currentObj; // Set the 'lastBlock' object to the value of the current block
                buffer = [];
            }
            else if (currentType === 's') {
                const selfClosingHName = currentObj.n || '';
                if (env.async && asyncRegExp.test(selfClosingHName)) {
                    currentObj.a = true;
                    currentObj.n = selfClosingHName.replace(asyncRegExp, '');
                }
                buffer.push(currentObj);
            }
            else {
                buffer.push(currentObj);
            }
            // ===== DONE ADDING OBJECT TO BUFFER =====
        }
        if (firstParse) {
            pushString(str.slice(startInd, str.length), false);
            parentObj.d = buffer;
        }
        else {
            throw SqrlErr('unclosed helper "' + parentObj.n + '"');
            // It should have returned by now
        }
        return parentObj;
    }
    const parseResult = parseContext({ f: [] }, true);
    // console.log(JSON.stringify(parseResult))
    if (env.plugins) {
        for (let i = 0; i < env.plugins.length; i++) {
            const plugin = env.plugins[i];
            if (plugin.processAST) {
                parseResult.d = plugin.processAST(parseResult.d, env);
            }
        }
    }
    return parseResult.d; // Parse the very outside context
}

// import SqrlErr from './err'
/* END TYPES */
function compileToString(str, env) {
    const buffer = parse(str, env);
    let res = "var tR='';" +
        (env.useWith ? 'with(' + env.varName + '||{}){' : '') +
        compileScope(buffer, env) +
        'if(cb){cb(null,tR)} return tR' +
        (env.useWith ? '}' : '');
    if (env.plugins) {
        for (let i = 0; i < env.plugins.length; i++) {
            const plugin = env.plugins[i];
            if (plugin.processFnString) {
                res = plugin.processFnString(res, env);
            }
        }
    }
    return res;
    // TODO: is `return cb()` necessary, or could we just do `cb()`
}
function filter(str, filters) {
    for (let i = 0; i < filters.length; i++) {
        const name = filters[i][0];
        const params = filters[i][1];
        const isFilterAsync = filters[i][2];
        // if (isFilterAsync && !env.async) {
        //   throw SqrlErr("Async filter '" + name + "' in non-async env")
        // }
        // Let the JS compiler do this, compile() will catch it
        str = (isFilterAsync ? 'await ' : '') + "c.l('F','" + name + "')(" + str;
        if (params) {
            str += ',' + params;
        }
        str += ')';
    }
    return str;
}
// TODO: Use type intersections for TemplateObject, etc.
// so I don't have to make properties mandatory
function compileHelper(env, res, descendants, params, isAsync, name) {
    let ret = '{exec:' +
        (isAsync ? 'async ' : '') +
        compileScopeIntoFunction(descendants, res, env) +
        ',params:[' +
        params +
        ']';
    if (name) {
        ret += ",name:'" + name + "'";
    }
    if (isAsync) {
        ret += ',async:true';
    }
    ret += '}';
    return ret;
}
function compileBlocks(blocks, env) {
    let ret = '[';
    for (let i = 0; i < blocks.length; i++) {
        const block = blocks[i];
        ret += compileHelper(env, block.res || '', block.d, block.p || '', block.a, block.n);
        if (i < blocks.length) {
            ret += ',';
        }
    }
    ret += ']';
    return ret;
}
function compileScopeIntoFunction(buff, res, env) {
    return ('function(' + res + "){var tR='';" + compileScope(buff, env) + 'return tR}');
}
function compileScope(buff, env) {
    let i = 0;
    const buffLength = buff.length;
    let returnStr = '';
    for (i; i < buffLength; i++) {
        const currentBlock = buff[i];
        if (typeof currentBlock === 'string') {
            const str = currentBlock;
            // we know string exists
            returnStr += "tR+='" + str + "';";
        }
        else {
            const type = currentBlock.t; // h, s, e, i
            let content = currentBlock.c || '';
            const filters = currentBlock.f;
            const name = currentBlock.n || '';
            const params = currentBlock.p || '';
            const res = currentBlock.res || '';
            const blocks = currentBlock.b;
            const isAsync = !!currentBlock.a; // !! is to booleanize it
            // if (isAsync && !env.async) {
            //   throw SqrlErr("Async block or helper '" + name + "' in non-async env")
            // }
            // Let compiler do this
            if (type === 'i') {
                if (env.defaultFilter) {
                    content = "c.l('F','" + env.defaultFilter + "')(" + content + ')';
                }
                let filtered = filter(content, filters);
                if (!currentBlock.raw && env.autoEscape) {
                    filtered = "c.l('F','e')(" + filtered + ')';
                }
                returnStr += 'tR+=' + filtered + ';';
                // reference
            }
            else if (type === 'h') {
                // helper
                if (env.storage.nativeHelpers.get(name)) {
                    returnStr += env.storage.nativeHelpers.get(name)(currentBlock, env);
                }
                else {
                    let helperReturn = (isAsync ? 'await ' : '') +
                        "c.l('H','" +
                        name +
                        "')(" +
                        compileHelper(env, res, currentBlock.d, params, isAsync);
                    if (blocks) {
                        helperReturn += ',' + compileBlocks(blocks, env);
                    }
                    else {
                        helperReturn += ',[]';
                    }
                    helperReturn += ',c)';
                    returnStr += 'tR+=' + filter(helperReturn, filters) + ';';
                }
            }
            else if (type === 's') {
                // self-closing helper
                returnStr +=
                    'tR+=' +
                        filter((isAsync ? 'await ' : '') +
                            "c.l('H','" +
                            name +
                            "')({params:[" +
                            params +
                            ']},[],c)', filters) +
                        ';';
            }
            else if (type === 'e') {
                // execute
                returnStr += content + '\n';
            }
        }
    }
    return returnStr;
}

/* END TYPES */
class Cacher {
    cache;
    constructor(cache) {
        this.cache = cache;
    }
    define(key, val) {
        this.cache[key] = val;
    }
    get(key) {
        // string | array.
        // TODO: allow array of keys to look down
        // TODO: create plugin to allow referencing helpers, filters with dot notation
        return this.cache[key];
    }
    remove(key) {
        delete this.cache[key];
    }
    reset() {
        this.cache = {};
    }
    load(cacheObj) {
        // TODO: this will err with deep objects and `storage` or `plugins` keys.
        // Update Feb 26: EDITED so it shouldn't err
        copyProps(this.cache, cacheObj, true);
    }
}

function errWithBlocksOrFilters(name, blocks, // false means don't check
filters, native) {
    if (blocks && blocks.length > 0) {
        throw SqrlErr((native ? 'Native' : '') + "Helper '" + name + "' doesn't accept blocks");
    }
    if (filters && filters.length > 0) {
        throw SqrlErr((native ? 'Native' : '') + "Helper '" + name + "' doesn't accept filters");
    }
}
/* ASYNC LOOP FNs */
function asyncArrLoop(arr, index, fn, res, cb) {
    fn(arr[index], index).then(function (val) {
        res += val;
        if (index === arr.length - 1) {
            cb(res);
        }
        else {
            asyncArrLoop(arr, index + 1, fn, res, cb);
        }
    });
}
function asyncObjLoop(obj, keys, index, fn, res, cb) {
    fn(keys[index], obj[keys[index]]).then(function (val) {
        res += val;
        if (index === keys.length - 1) {
            cb(res);
        }
        else {
            asyncObjLoop(obj, keys, index + 1, fn, res, cb);
        }
    });
}
const escMap = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;',
};
function replaceChar(s) {
    return escMap[s];
}
function XMLEscape(str) {
    // To deal with XSS. Based on Escape implementations of Mustache.JS and Marko, then customized.
    const newStr = String(str);
    if (/[&<>"']/.test(newStr)) {
        return newStr.replace(/[&<>"']/g, replaceChar);
    }
    else {
        return newStr;
    }
}

/* END TYPES */
const templates = new Cacher({});
/* ASYNC LOOP FNs */
const helpers = new Cacher({
    each: function (content, blocks) {
        let res = '';
        const arr = content.params[0];
        errWithBlocksOrFilters('each', blocks, false);
        if (content.async) {
            return new Promise(function (resolve) {
                asyncArrLoop(arr, 0, content.exec, res, resolve);
            });
        }
        else {
            for (let i = 0; i < arr.length; i++) {
                res += content.exec(arr[i], i);
            }
            return res;
        }
    },
    foreach: function (content, blocks) {
        const obj = content.params[0];
        errWithBlocksOrFilters('foreach', blocks, false);
        if (content.async) {
            return new Promise(function (resolve) {
                asyncObjLoop(obj, Object.keys(obj), 0, content.exec, '', resolve);
            });
        }
        else {
            let res = '';
            for (const key in obj) {
                if (!hasOwnProp(obj, key))
                    continue;
                res += content.exec(key, obj[key]); // todo: check on order
            }
            return res;
        }
    },
    include: function (content, blocks, config) {
        errWithBlocksOrFilters('include', blocks, false);
        const template = config.storage.templates.get(content.params[0]);
        if (!template) {
            throw SqrlErr('Could not fetch template "' + content.params[0] + '"');
        }
        return template(content.params[1], config);
    },
    extends: function (content, blocks, config) {
        const data = content.params[1] || {};
        data.content = content.exec();
        for (let i = 0; i < blocks.length; i++) {
            const currentBlock = blocks[i];
            data[currentBlock.name] = currentBlock.exec();
        }
        const template = config.storage.templates.get(content.params[0]);
        if (!template) {
            throw SqrlErr('Could not fetch template "' + content.params[0] + '"');
        }
        return template(data, config);
    },
    useScope: function (content, blocks) {
        errWithBlocksOrFilters('useScope', blocks, false);
        return content.exec(content.params[0]);
    },
});
const nativeHelpers = new Cacher({
    if: function (buffer, env) {
        errWithBlocksOrFilters('if', false, buffer.f, true);
        let returnStr = 'if(' + buffer.p + '){' + compileScope(buffer.d, env) + '}';
        if (buffer.b) {
            for (let i = 0; i < buffer.b.length; i++) {
                const currentBlock = buffer.b[i];
                if (currentBlock.n === 'else') {
                    returnStr += 'else{' + compileScope(currentBlock.d, env) + '}';
                }
                else if (currentBlock.n === 'elif') {
                    returnStr +=
                        'else if(' +
                            currentBlock.p +
                            '){' +
                            compileScope(currentBlock.d, env) +
                            '}';
                }
            }
        }
        return returnStr;
    },
    try: function (buffer, env) {
        errWithBlocksOrFilters('try', false, buffer.f, true);
        if (!buffer.b || buffer.b.length !== 1 || buffer.b[0].n !== 'catch') {
            throw SqrlErr("native helper 'try' only accepts 1 block, 'catch'");
        }
        let returnStr = 'try{' + compileScope(buffer.d, env) + '}';
        const currentBlock = buffer.b[0];
        returnStr +=
            'catch' +
                (currentBlock.res ? '(' + currentBlock.res + ')' : '') +
                '{' +
                compileScope(currentBlock.d, env) +
                '}';
        return returnStr;
    },
    block: function (buffer, env) {
        errWithBlocksOrFilters('block', buffer.b, buffer.f, true);
        const returnStr = 'if(!' +
            env.varName +
            '[' +
            buffer.p +
            ']){tR+=(' +
            compileScopeIntoFunction(buffer.d, '', env) +
            ')()}else{tR+=' +
            env.varName +
            '[' +
            buffer.p +
            ']}';
        return returnStr;
    },
});
const filters = new Cacher({ e: XMLEscape });

/* END TYPES */
const defaultConfig = {
    varName: 'it',
    autoTrim: [false, 'nl'],
    autoEscape: true,
    defaultFilter: false,
    tags: ['{{', '}}'],
    l: function (container, name) {
        if (container === 'H') {
            const hRet = this.storage.helpers.get(name);
            if (hRet) {
                return hRet;
            }
            else {
                throw SqrlErr("Can't find helper '" + name + "'");
            }
        }
        else if (container === 'F') {
            const fRet = this.storage.filters.get(name);
            if (fRet) {
                return fRet;
            }
            else {
                throw SqrlErr("Can't find filter '" + name + "'");
            }
        }
    },
    async: false,
    storage: {
        helpers: helpers,
        nativeHelpers: nativeHelpers,
        filters: filters,
        templates: templates,
    },
    prefixes: {
        h: '@',
        b: '#',
        i: '',
        r: '*',
        c: '/',
        e: '!',
    },
    cache: false,
    plugins: [],
    useWith: false,
};
defaultConfig.l.bind(defaultConfig);
function getConfig(override, baseConfig) {
    // TODO: run more tests on this
    const res = {}; // Linked
    copyProps(res, defaultConfig); // Creates deep clone of res, 1 layer deep
    if (baseConfig) {
        copyProps(res, baseConfig);
    }
    if (override) {
        copyProps(res, override);
    }
    res.l.bind(res);
    return res;
}

/* END TYPES */
function compile(str, env) {
    const options = getConfig(env || {});
    let ctor = Function; // constructor
    /* ASYNC HANDLING */
    // The below code is modified from mde/ejs. All credit should go to them.
    if (options.async) {
        // Have to use generated function for this, since in envs without support,
        // it breaks in parsing
        if (asyncFunc) {
            ctor = asyncFunc;
        }
        else {
            throw SqrlErr("This environment doesn't support async/await");
        }
    }
    /* END ASYNC HANDLING */
    try {
        return new ctor(options.varName, 'c', // SqrlConfig
        'cb', // optional callback
        compileToString(str, options));
    }
    catch (e) {
        if (e instanceof SyntaxError) {
            throw SqrlErr('Bad template syntax\n\n' +
                e.message +
                '\n' +
                Array(e.message.length + 1).join('=') +
                '\n' +
                compileToString(str, options));
        }
        else {
            throw e;
        }
    }
}

/* TYPES */
/* END TYPES */
function handleCache(template, options) {
    let templateFunc;
    if (options.cache &&
        options.name &&
        options.storage.templates.get(options.name)) {
        return options.storage.templates.get(options.name);
    }
    if (typeof template === 'function') {
        templateFunc = template;
    }
    else {
        templateFunc = compile(template, options);
    }
    if (options.cache && options.name) {
        options.storage.templates.define(options.name, templateFunc);
    }
    return templateFunc;
}
function render(template, data, env, cb) {
    const options = getConfig(env || {});
    if (options.async) {
        let result;
        if (!cb) {
            // No callback, try returning a promise
            if (typeof promiseImpl === 'function') {
                return new promiseImpl(function (resolve, reject) {
                    try {
                        result = handleCache(template, options)(data, options);
                        resolve(result);
                    }
                    catch (err) {
                        reject(err);
                    }
                });
            }
            else {
                throw SqrlErr("Please provide a callback function, this env doesn't support Promises");
            }
        }
        else {
            try {
                handleCache(template, options)(data, options, cb);
            }
            catch (err) {
                return cb(err);
            }
        }
    }
    else {
        return handleCache(template, options)(data, options);
    }
}

var squirrel = /*#__PURE__*/Object.freeze({
    __proto__: null,
    compile: compile,
    compileScope: compileScope,
    compileScopeIntoFunction: compileScopeIntoFunction,
    compileToString: compileToString,
    defaultConfig: defaultConfig,
    filters: filters,
    getConfig: getConfig,
    helpers: helpers,
    nativeHelpers: nativeHelpers,
    parse: parse,
    render: render,
    templates: templates
});

filters.define('date_long', (str, options = {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
}) => {
    if (!str)
        return;
    const date = new Date(parseInt(str) * 1000);
    str = date.toLocaleDateString('en-GB', options);
    return str;
});
filters.define('date_short', (str, format = 'd/m/y') => {
    if (!str)
        return;
    const date = new Date(parseInt(str) * 1000);
    str = format.replace(/(d)/, date.getDate().toString());
    str = str.replace(/(m)/, (date.getMonth() + 1).toString());
    str = str.replace(/(y)/, date.getFullYear().toString());
    return str;
});
filters.define('public_name', (str) => {
    if (!str)
        return;
    str = str
        .split('_')
        .map((val) => {
        return val.length < 3 ? val.toUpperCase() : val;
    })
        .join(' ');
    return str.charAt(0).toUpperCase() + str.slice(1);
});
filters.define('trim_to', (str, length = 100) => {
    if (!str)
        return;
    if (str.length <= length)
        return str;
    return str.substr(0, length) + '...';
});
filters.define('classname', (str) => {
    if (!str)
        return;
    return str.replace(/[\W]+/g, '');
});
filters.define('abs_url', (str, index = null, result) => {
    if (!index)
        return str;
    if (!str)
        return;
    let domain = undefined;
    if (index.domains && result && result.origin) {
        const found = index.domains.find((dm) => dm.origin === result.origin);
        domain = found ? found.domain : '';
    }
    else if (index.domain)
        domain = index.domain;
    if (str.match(/^http/) || !domain)
        return str;
    if (!str.startsWith('/'))
        str = '/' + str;
    return location.protocol + '//' + domain + str;
});
filters.define('replace', (str, search, replace) => str.replace(search, replace));
filters.define('remove_spaces', (str) => str.replace(/ /g, '_'));
filters.define('add_spaces', (str) => (str = str.replace(/[_\-]/g, ' ')));
filters.define('capitalise', (str) => (str = str.charAt(0).toUpperCase() + str.slice(1)));
filters.define('lowercase', (str) => (str = str.toLowerCase()));
filters.define('tag_tidy', (str) => {
    const arr = str.split('>');
    return arr.map((s) => s.trim())[arr.length - 1];
});
const Squirrel = squirrel;

const algoliaCss = ":host,*,*::before,*::after{box-sizing:border-box}[hidden]{display:none !important}@media (prefers-reduced-motion: reduce){:host,*,*::before,*::after{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important;scroll-behavior:auto !important}}:host,*,*::before,*::after{box-sizing:border-box}[hidden]{display:none !important}@media (prefers-reduced-motion: reduce){:host,*,*::before,*::after{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important;scroll-behavior:auto !important}}:host{position:relative;display:block}:host ::slotted([slot=search-template]){display:none !important}.loader{opacity:0;position:fixed;block-size:100%;inline-size:100%;min-block-size:100%;max-block-size:100vh !important;max-inline-size:100vw !important;inset-inline-start:0;inset-block-start:0;z-index:-10;font-size:60px;display:flex;align-items:center;justify-content:center;transition:opacity 0.2s ease, z-index 0.01s ease 0.2s}.loader.loading{opacity:1;z-index:10;transition:z-index 0.01s ease, opacity 0.2s ease 0.01s}.default{block-size:100%}.default:focus{outline:none}.results{opacity:0;outline:none;overflow:auto;transition:transform 0.3s ease, opacity 0.3s ease, visibility 0.01s ease 0.3s, z-index 0.01s ease 0.3s;transform:translate(0, 100px);visibility:hidden;z-index:-1;position:absolute;inset-block-start:0;inset-inline-start:0;display:none}.results-container{position:relative}.results.show{transition:z-index 0.01s ease, visibility 0.01s ease, transform 0.3s ease 0.01s, opacity 0.3s ease 0.01s, overflow 0.01s ease 0.31s;transform:translate(0, 0);visibility:visible;opacity:1;z-index:10;overflow:hidden}.results.shown{block-size:auto !important;inline-size:auto !important;display:block;position:static;transform:none;z-index:auto;visibility:inherit;opacity:initial;overflow:visible}";

const Algolia = class {
    constructor(hostRef) {
        registerInstance(this, hostRef);
        this.nanoDidLoad = createEvent(this, "nanoDidLoad", 7);
        this.nanoResultsShown = createEvent(this, "nanoResultsShown", 3);
        this.nanoBeforeQuery = createEvent(this, "nanoBeforeQuery", 7);
        this.nanoAfterQuery = createEvent(this, "nanoAfterQuery", 7);
        this.nanoNewResults = createEvent(this, "nanoNewResults", 7);
    }
    /* eslint-disable @stencil-community/strict-mutable */
    // filters that have an initial value
    staticFacetFilters = {};
    dynFacetFilters = {};
    tplSlot;
    outputSlot;
    slottedInput;
    hasLoadSlot = false;
    resultsDiv;
    defaultDiv;
    tpl;
    facets;
    placedAlgoliaEles = [];
    algoliaClient;
    // lots of stuff happens asynchronously. This makes sure they fire in the correct order
    promiseQueue = new PromiseQueue();
    get host() { return getElement(this); }
    // Internal state
    // filters that change via a template. They have no initial value
    _dynFacetFilters = {};
    inputField;
    algoliaIndex;
    indeces = [];
    indexResults;
    filterChanged = '';
    currentHits;
    changeEvent = SearchChangeEvent.Init;
    isLoading = false;
    wormholeState = this;
    // Public props
    /**
     * Show the result panel. By default it will automatically show when the result receives new hits.
     */
    showResults = false;
    /**
     * The current algolia page of results currently being viewed or to get.
     */
    resultsPage = 0;
    /**
     * An Algolia App ID key. Used in conjunction with 'searchIndex' & 'apiKey'.
     */
    appId;
    /**
     * An Algolia API Key. Used in conjunction with 'searchIndex' & 'appId'.
     */
    apiKey;
    /**
     * Algolia search index. Used in conjunction with 'apiKey' & 'appId'.
     * ```
     * { index: string, // algolia index name
     *   domain?: string, // used in template filters to resolve incomplete url fields
     *   filters?: [string], // algolia facets to retrive
     *   hitsPerPage?: number,
     *   name?: string // human readable index name
     * }
     * ```
     */
    searchIndex = {};
    /**
     * Algolia search index name. Alternative to 'searchIndex'.
     * A simplified shortcut for setting an index.
     * Properties such as hitsPerPage and facets will come from the algolia index
     */
    searchIndexName = '';
    /**
     * JS querySelector string of a `nano-input` / `input` element from which
     * to listen to `nano-change` or `change` events and change the `query` prop.
     * Not required if an appropriate field is in the 'search-input' slot
     */
    listenTo = null;
    handleListenToChange() {
        if (!this.listenTo)
            return;
        const foundInput = document.querySelector(this.listenTo);
        if (['nano-input', 'input'].includes(foundInput.tagName.toLocaleLowerCase())) {
            this.inputField = foundInput;
        }
    }
    /**
     * The query string used to search the index. Will automatically be set with any slotted
     * `search-input` input elements or from elements linked to from `listen-to`
     */
    query = '';
    /**
     * The external operator joining filter values.
     */
    operator = 'and';
    /**
     * Query filters to apply to all searches.
     * Refer to https://www.algolia.com/doc/api-reference/api-parameters/filters for format.
     */
    filters = [];
    /**
     * Template engine render function to use when rendering results and filters.
     * Defaults to Squirrel v8's render (https://squirrelly.js.org/).
     */
    tplRenderFn = Squirrel.render;
    /**
     * Changes the index without removing any current filters. Most appropriate for algolia replicas.
     */
    replicaIndex = '';
    /**
     * Will maintain a search all / '*' on init and reset allowing for a view
     * then filter experience rather than a search first experience
     */
    browseIndex = false;
    /**
     * Minimum number of characters before performing a search
     */
    minChars = 2;
    /**
     * 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';
    // Events
    /**
     * Emitted when the algolia element has been created.
     */
    nanoDidLoad;
    /**
     * Emitted when results and all filters are shown and have loaded.
     */
    nanoResultsShown;
    /**
     * Emitted before an algolia search.
     */
    nanoBeforeQuery;
    /**
     * Emitted after an algolia search.
     */
    nanoAfterQuery;
    /**
     * Emitted after an results have been updated
     */
    nanoNewResults;
    // Public methods
    /**
     * @param facetName - will clear all filter selections of that type
     * @param facetValue - will remove a single filter selection
     * Remove facet filters (as set from nano-algolia-filter elements).
     * The omission or both will remove all facet filters
     */
    async removeFilters(facetName, facetValue) {
        if (!facetName && !facetValue)
            this.clearAllFacetFilters();
        else {
            Object.keys(this._dynFacetFilters).forEach((filterId) => {
                if (facetName &&
                    this._dynFacetFilters[filterId].facetName === facetName) {
                    if (!facetValue)
                        this._dynFacetFilters[filterId].element.value = '';
                    else {
                        this._dynFacetFilters[filterId].element.value =
                            this._dynFacetFilters[filterId].value.filter((val) => val !== facetValue);
                    }
                }
            });
        }
    }
    // Listeners to external events
    // a child nano-algolia-... updated their template
    onAllTplUpdate(ev) {
        ev.stopPropagation();
        if (ev.target.tagName.toLowerCase() === transformTag('nano-algolia-results')) {
            requestAnimationFrame(() => {
                this.nanoNewResults.emit(this.indexResults);
            });
        }
    }
    // a nano-algolia-pagination or nano-algolia-results changed the page
    async onPageChange(ev) {
        if (!this.indexResults)
            return;
        let page = this.resultsPage;
        if (ev) {
            ev.stopPropagation();
            page = ev.detail.page;
        }
        page = Math.min(this.indexResults.totalPages - 1, page);
        page = Math.max(page, 0);
        if (this.indexResults.currentPage === page)
            return;
        this.indexResults.currentPage = page;
        await this.algoliaSearch(SearchChangeEvent.Page);
    }
    /**
     * nano-algolia-filter changed value.
     * If that happened before a indexResults were setup, it was a static filter.
     * Static filters persist accross search changes
     */
    async onFilterChange(ev) {
        ev.stopPropagation();
        const dynFilterObj = {};
        const filter = ev.detail;
        filter.element = ev.target;
        if (this.indexResults) {
            if (this.staticFacetFilters[filter.filterId])
                this.staticFacetFilters[filter.filterId] = filter;
            else {
                dynFilterObj[filter.filterId] = filter;
                this._dynFacetFilters = { ...this._dynFacetFilters, ...dynFilterObj };
            }
            this.indexResults.appliedFilters = this.makeAppliedFilters();
        }
        else
            this.staticFacetFilters[filter.filterId] = filter;
        this.filterChanged =
            filter.value && filter.value.length ? filter.filterId : 'all';
        this.resetPage();
        this.algoliaSearch(SearchChangeEvent.Filter);
    }
    // Internal state watchers
    // Filters can fire a change event, cause a re-render then get detached.
    // This can cause filter dupes. Check to see if the original element is still in the DOM.
    dynFacetFiltersChange() {
        Object.values(this._dynFacetFilters).forEach((filter) => {
            if (this.host.ownerDocument === filter.element.ownerDocument)
                this.dynFacetFilters[filter.filterId] = filter;
            else
                delete this.dynFacetFilters[filter.filterId];
        });
    }
    browseIndexChange() {
        if (this.browseIndex &&
            (!this.query || this.query.length < this.minChars)) {
            this.query = '*';
        }
    }
    inputFieldSet() {
        if (!this.inputField)
            return;
        if (this.query !== '*' &&
            this.inputField &&
            this.inputField.value !== this.query)
            this.inputField.value = this.query;
    }
    queryChange() {
        this.searchChange();
    }
    searchChange = async () => {
        if (!this.algoliaIndex)
            return;
        if (!this.query || this.query.length < this.minChars) {
            if (!this.browseIndex) {
                if (!this.query || this.query.length === 0) {
                    this.handleSearchReset();
                    return;
                }
                else
                    return;
            }
            else if (this.query !== '*') {
                this.query = '*';
                return;
            }
        }
        // Do an initialise search (setup searchIndex)
        this.isLoading = true;
        setTimeout(async () => {
            // Clear dynamic filters and reset page
            this.resetPage();
            await this.algoliaSearch(SearchChangeEvent.Init);
            // Populate master content - optionally from template
            await this.updateContent();
            setTimeout((_) => (this.isLoading = false), 800);
            this.showResults = true;
            this.nanoResultsShown.emit({
                meta: this.indexResults,
                client: { appId: this.appId, apiKey: this.apiKey },
            });
        }, 200);
    };
    async internalIndexSwitch() {
        if (!this.replicaIndex ||
            !this.indexResults ||
            this.indexResults.index === this.replicaIndex)
            return;
        this.indexResults.index = this.replicaIndex;
        this.algoliaIndex = this.algoliaClient.initIndex(this.indexResults.index);
        this.resetPage();
        this.algoliaSearch(SearchChangeEvent.Replica);
    }
    pageChange() {
        this.onPageChange();
    }
    searchIndexNameChange() {
        if (!this.searchIndexName ||
            !this.searchIndexName.length ||
            (this.searchIndex && this.searchIndex.index === this.searchIndexName))
            return;
        this.searchIndex = { index: this.searchIndexName };
    }
    initAlgoliaClient() {
        if (!this.appId || !this.apiKey)
            return;
        this.algoliaClient = algoliasearch(this.appId, this.apiKey);
    }
    initAlgoliaIndex() {
        if (!this.algoliaClient || !this.searchIndex)
            return;
        if (this.searchIndex.index) {
            if (!this.searchIndex.hitsPerPage)
                this.searchIndex.hitsPerPage = 100;
            if (this.replicaIndex)
                this.searchIndex.index = this.replicaIndex;
            this.searchIndexName = this.searchIndex.index;
            this.algoliaIndex = this.algoliaClient.initIndex(this.searchIndex.index);
            this.searchChange();
        }
    }
    showResultsChange() {
        if (!this.resultsDiv || !this.defaultDiv)
            return;
        if (this.showResults) {
            this.resultsDiv.style.width = this.host.scrollWidth + 'px';
            this.resultsDiv.style.minHeight = this.host.scrollHeight + 'px';
            writeTask(() => {
                this.resultsDiv.addEventListener('transitionend', this.onResultDisplay);
                this.resultsDiv.style.display = 'block';
                setTimeout(() => this.resultsDiv.classList.add('show'), 50);
            });
        }
        else {
            writeTask(() => {
                this.resultsDiv.classList.remove('shown');
                this.defaultDiv.style.display = 'block';
                setTimeout(() => {
                    this.resultsDiv.addEventListener('transitionend', this.onResultDisplay);
                    this.resultsDiv.classList.remove('show');
                    if (this.slottedInput)
                        this.slottedInput.focus({ preventScroll: true });
                    else
                        this.defaultDiv.focus({ preventScroll: true });
                }, 50);
            });
        }
    }
    handleFilterpropChange() {
        this.algoliaSearch(SearchChangeEvent.Init);
    }
    // Internal methods
    fixDomain(results) {
        if (!this.searchIndex || !this.searchIndex.domain)
            return results;
        results.hits.map((hit) => {
            if (hit.url && !hit.url.match(/^http/))
                hit.url = location.protocol + '//' + this.searchIndex.domain + hit.url;
        });
        return results;
    }
    // Takes all current filters (dynamic and static)
    // and merges common filters' values togther
    makeAppliedFilters = () => {
        const appliedFtrs = [];
        const makeAppliedFilter = (facetFilters) => {
            Object.values(facetFilters).forEach((filter) => {
                if (filter.value && filter.value.length) {
                    const found = appliedFtrs.find((appFilter) => appFilter.name === filter.facetName);
                    if (!found)
                        appliedFtrs.push({
                            name: filter.facetName,
                            values: [...filter.value],
                        });
                    else
                        found.values = [...found.values, ...filter.value];
                }
            });
        };
        makeAppliedFilter(this.dynFacetFilters);
        makeAppliedFilter(this.staticFacetFilters);
        if (appliedFtrs.length)
            return appliedFtrs;
        return null;
    };
    // Queue up a search
    algoliaSearch = async (eventType) => {
        return this.promiseQueue.enqueue(() => this.queueSearch(eventType));
    };
    // Do a search
    queueSearch = async (eventType) => {
        if (!this.algoliaIndex ||
            !this.query ||
            !this.searchIndex ||
            !this.searchIndex.index)
            return;
        this.nanoBeforeQuery.emit(this.indexResults);
        // clear all facet filters on init (new query)
        if (SearchChangeEvent.Init === eventType)
            this.clearAllFacetFilters();
        const searchParams = {
            filters: this.algoliaFilterStr(),
            page: this.indexResults ? this.indexResults.currentPage : 0,
            clickAnalytics: true,
        };
        if (this.searchIndex.filters)
            searchParams.facets = this.searchIndex.filters;
        if (this.searchIndex.hitsPerPage)
            searchParams.hitsPerPage = this.searchIndex.hitsPerPage;
        let result = (await this.algoliaIndex.search(this.query, searchParams));
        result = this.fixDomain(result);
        // set event type - this informs children components why things changed
        this.changeEvent = eventType;
        // Setup initial meta on new queries. Some of it should not change on filter / pagination
        if (eventType === SearchChangeEvent.Init || !this.indexResults) {
            this.indexResults = {
                queryID: result.queryID,
                currentPage: 0,
                totalHits: result.nbHits,
                hitsPerPage: null,
                origFilters: result.facets,
                query: this.query,
                domain: this.searchIndex.domain || null,
                totalHitsWithFilters: 0,
                results: [],
                totalPages: 0,
                appliedFilters: this.makeAppliedFilters(),
                indexName: this.searchIndex.name || null,
                index: this.searchIndex.index,
                searchIndex: this.searchIndex,
            };
        }
        this.indexResults.dynFilters = result.facets;
        this.indexResults.results = result.hits;
        this.indexResults.totalHitsWithFilters = result.nbHits;
        this.indexResults.currentPage = result.page;
        this.indexResults.totalPages = result.nbPages;
        this.indexResults.hitsPerPage = result.hitsPerPage;
        this.indexResults.query = result.query;
        // console.log('I GOT NEW RESULTS', eventType, this.query, this.algoliaFilterStr(), this.indexResults.results.length, this.indexResults.currentPage )
        if (this.changeEvent !== SearchChangeEvent.Page &&
            this.changeEvent !== SearchChangeEvent.Replica) {
            this.facets = result.facets;
        }
        this.nanoAfterQuery.emit(this.indexResults);
        // current hits and page will cause re-renders in children components.
        // Wait a tick before firing otherwise events can fire in the wrong order
        return new Promise((resolve) => {
            setTimeout(() => resolve('tpl updated'), 20);
        }).then((_) => {
            this.currentHits = this.indexResults.results;
            this.resultsPage = this.indexResults.currentPage;
        });
    };
    resetPage() {
        if (!this.indexResults)
            return;
        this.indexResults.currentPage = 0;
    }
    clearAllFacetFilters() {
        Object.values(this._dynFacetFilters).forEach((filter) => {
            filter.element.value = '';
        });
        this._dynFacetFilters = {};
    }
    algoliaFilterStr() {
        // groups applied facet filters (from algolia-filter components)
        // with their applied, 'internal' operators (and, or, not)
        const facetFilterObj = {};
        const groupFilters = (facetFilters) => {
            let filter;
            let filterOp;
            Object.keys(facetFilters).forEach((filterId) => {
                filter = facetFilters[filterId];
                facetFilterObj[filterId] = facetFilterObj[filterId] || {
                    name: filter.facetName,
                    or: [],
                    and: [],
                    not: [],
                };
                filterOp = facetFilterObj[filterId][filter.operator];
                if (filter.value && filter.value.length) {
                    facetFilterObj[filterId][filter.operator] = [
                        ...filterOp,
                        ...filter.value,
                    ];
                }
            });
        };
        groupFilters(this.dynFacetFilters);
        groupFilters(this.staticFacetFilters);
        const formatVal = (name, val) => {
            if (val.indexOf(' ') > -1)
                return name + ':"' + val + '"';
            else
                return name + ':' + val;
        };
        let filterStrParts = [];
        let operatorGrp;
        let filterName;
        // let processedFilters: string[] = [];
        // joins all applied facet filter operator groups with the set 'outer'
        // operator into an algolia filter query string
        Object.values(facetFilterObj).forEach(function (filter) {
            Object.keys(filter)
                .filter((filterProp) => filterProp.match(/(or|and|not)/))
                .forEach((operator) => {
                operatorGrp = filter;
                filterName = filter.name;
                if (operator === 'or' || operator === 'and') {
                    if (operatorGrp[operator].length) {
                        filterStrParts.push('( \
                ' +
                            operatorGrp[operator]
                                .map((val) => {
                                if (val.length)
                                    return formatVal(filterName, val);
                            })
                                .join(' ' + operator.toLocaleUpperCase() + ' ') +
                            ' \
              )');
                    }
                }
                else {
                    if (operatorGrp.not.length) {
                        filterStrParts.push('( \
                ' +
                            operatorGrp[operator]
                                .map((val) => {
                                if (val.length)
                                    return 'NOT ' + formatVal(filterName, val);
                            })
                                .join(' AND ') +
                            ' \
              )');
                    }
                }
            });
        });
        // add in any static applied filters from filters prop
        filterStrParts = [...this.filters, ...filterStrParts];
        // console.log('filterStr:', filterStrParts.join(' ' + this.operator.toLocaleUpperCase() + ' '))
        return filterStrParts.join(' ' + this.operator.toLocaleUpperCase() + ' ');
    }
    handleInputChange(ev) {
        if (ev.target !== this.inputField)
            return;
        this.query = this.inputField.value;
    }
    handleSearchReset = () => {
        this.showResults = false;
        this.indexResults = null;
    };
    onResultDisplay = () => {
        writeTask(() => {
            this.resultsDiv.removeEventListener('transitionend', this.onResultDisplay);
            if (!this.showResults)
                this.resultsDiv.style.display = 'none';
            else {
                this.defaultDiv.style.display = 'none';
                this.resultsDiv.classList.add('shown');
                this.resultsDiv.style.minHeight = '';
                this.resultsDiv.style.width = '';
            }
        });
    };
    async updateContent() {
        return this.promiseQueue.enqueue(() => this.updateContentQueue());
    }
    async updateContentQueue() {
        const outputTo = this.outputSlot || this.resultsDiv;
        if (!outputTo || !this.tpl)
            return 'no tpl';
        const tmpCont = document.createElement('div');
        tmpCont.innerHTML = this.tpl;
        const algoliaEles = [];
        this.placedAlgoliaEles = [];
        // take out children algolia elements 'cos they have their own templates
        // don't want to swap in their templates
        Array.from(tmpCont.querySelectorAll(`${transformTag('nano-algolia')}, ${transformTag('nano-algolia-filter')}, ${transformTag('nano-algolia-results')}`)).forEach((el, i) => {
            const placeholder = document.createElement('div');
            placeholder.dataset.placeholderId = i.toString();
            placeholder.classList.add('nano-ele-placeholder');
            algoliaEles.push(el);
            el.parentNode.replaceChild(placeholder, el);
        });
        // swap in template content
        // (all this nonsense htmlencodes any < or >)
        outputTo.innerHTML = this.tplRenderFn(tmpCont.innerHTML
            .replace(/&gt;/gm, '>')
            .replace(/&lt;/gm, '<')
            .replace(/&amp;/gm, '&'), this.indexResults);
        // put back in algolia elements
        Array.from(outputTo.querySelectorAll('div.nano-ele-placeholder')).forEach((el) => {
            el.parentNode.replaceChild(algoliaEles[parseInt(el.dataset.placeholderId)], el);
            this.placedAlgoliaEles.push(algoliaEles[parseInt(el.dataset.placeholderId)]);
        });
        if (!this.placedAlgoliaEles.length)
            return Promise.resolve('algolia setup');
        // wait for all children algolia elements to resolve before calling 'done'
        return new Promise((resolve) => {
            const childrenResolver = (ev) => {
                this.placedAlgoliaEles = this.placedAlgoliaEles.filter((ele) => ele !== ev.detail);
                if (!this.placedAlgoliaEles.length) {
                    setTimeout(() => resolve('tpl updated'), 500);
                    outputTo.removeEventListener('nanoTplUpdated', childrenResolver);
                }
            };
            outputTo.addEventListener('nanoTplUpdated', childrenResolver);
        });
    }
    // Stencil hooks
    componentWillLoad() {
        readTask((_) => {
            this.tplSlot = getDirectChildren(this.host, '[slot="template"]')[0];
            this.outputSlot = getDirectChildren(this.host, '[slot="output"]')[0];
            this.slottedInput = getDirectChildren(this.host, '[slot="search-input"]')[0];
            this.hasLoadSlot = !!getDirectChildren(this.host, '[slot="loader"]')[0];
            if (this.tplSlot)
                this.tpl = this.tplSlot.innerHTML || this.tplSlot.innerText;
            // get input field to listen to
            if (this.slottedInput &&
                (this.slottedInput.tagName.toLocaleLowerCase() ===
                    transformTag('nano-input') ||
                    this.slottedInput instanceof HTMLInputElement)) {
                this.inputField = this.slottedInput;
            }
            else if (this.slottedInput) {
                this.inputField = this.slottedInput.querySelector(`${transformTag('nano-input')}, input:not([type="radio"]):not([type="checkbox"])`);
            }
            else
                this.handleListenToChange();
            writeTask((_) => {
                if (this.showResults && this.tpl)
                    this.searchChange();
            });
        });
        this.searchIndexNameChange();
        this.initAlgoliaClient();
        this.initAlgoliaIndex();
        // Create wormwhole shared 'universe' (https://github.com/mihar-22/stencil-wormhole)
        Universe.create(this, this.wormholeState);
        if (this.storeId) {
            ComponentStore.init(this, ['query', 'replicaIndex'], this.storeMethod, this.storeId);
            ComponentStore.init(this, ['apiKey', 'appId', 'searchIndex'], 'session', this.storeId + '_api');
        }
        this.internalIndexSwitch();
    }
    connectedCallback() {
        this.searchChange = debounce(this.searchChange, 300);
        this.browseIndexChange();
        this.host.dispatchEvent(new CustomEvent('nanoDidLoad', {
            detail: this.host,
        }));
    }
    render() {
        const wormholeState = {
            indexResults: this.indexResults,
            results: this.currentHits,
            filterChanged: this.filterChanged,
            tplRenderFn: this.tplRenderFn,
            resultsPage: this.resultsPage,
            changeEvent: this.changeEvent,
            facets: this.facets,
            algoliaIndex: this.algoliaIndex,
            searchIndex: this.searchIndex,
            globalStoreMethod: this.storeMethod,
            isLoading: this.isLoading,
        };
        return (h(Host, { key: 'e5a080bf3b11c991465f1b44521b75539f80d5ec', class: {
                'show-results': this.showResults,
                'is-loading': this.isLoading,
                'nano-algolia': true,
            }, "aria-busy": this.isLoading }, this.hasLoadSlot && (h("div", { key: '586696526af9ba7b721ebfe8aeba2b5a45b8a19a', hidden: !this.isLoading }, h("slot", { key: '40556633911954fd20b756fedf71aea7e15b15e1', name: "loader" }))), !this.hasLoadSlot && (h("div", { key: 'e5ee4968b6864b656b3537ec46b69e64ebe76370', class: {
                loader: true,
                loading: this.isLoading,
            } }, h("nano-spinner", { key: '420a97d058127ea029329d6eca5055c0c54f4c1f', overlay: true }))), h(Universe.Provider, { key: '9c271e35ea7f850eb1ec2f99415ab2eebf014387', state: wormholeState }, h("slot", { key: '7a7a5ac869321d6db0a2f6f784a28e638cbe1a3f', name: "search-input" }), h("div", { key: '91b799022bee756066f1eb60038fdb428c98d3dc', class: "results-container", "aria-live": "polite" }, h("div", { key: '4209e438e0cefbc244dde4c3a9c10e3e5bf56137', ref: (div) => (this.resultsDiv = div), class: "results" }, h("slot", { key: '44b89a4cae72f8ff38c08dd605df32df03a2fc9a', name: "output" })))), h("div", { key: 'a584e0f2f10c38ffc46decc5b4b6bd2a1e14118a', ref: (div) => (this.defaultDiv = div), class: "default", tabindex: "-1", part: "default-content" }, h("slot", { key: 'dcf3c87479d48774b63bf32b4e2de2bfe9a6b1ea' }))));
    }
    static get watchers() { return {
        "listenTo": ["handleListenToChange"],
        "_dynFacetFilters": ["dynFacetFiltersChange"],
        "browseIndex": ["browseIndexChange"],
        "inputField": ["inputFieldSet"],
        "query": ["queryChange"],
        "replicaIndex": ["internalIndexSwitch"],
        "resultsPage": ["pageChange"],
        "searchIndexName": ["searchIndexNameChange"],
        "apiKey": ["initAlgoliaClient"],
        "appId": ["initAlgoliaClient"],
        "searchIndex": ["initAlgoliaIndex"],
        "showResults": ["showResultsChange"],
        "filters": ["handleFilterpropChange"]
    }; }
};
Algolia.style = algoliaCss;

export { Algolia as nano_algolia };
