import log from 'loglevel';
const KEY = Symbol.for('brightspot.onFind');
let _state;
function getState() {
    if (_state)
        return _state;
    _state = globalThis[KEY];
    if (!_state) {
        _state = {
            calledIndex: 0,
            callbacks: [],
            blacklist: [],
            triggerPaused: false,
            triggerOnResume: false,
            triggerAll: false,
            triggerElements: [],
            triggerFrame: 0,
            initialized: false,
        };
        globalThis[KEY] = _state;
    }
    return _state;
}
class Callback {
    #root;
    #called;
    #selectors;
    #fn;
    #matches;
    constructor(root, selectors, fn) {
        this.#root = root;
        this.#called = `data-ofc${getState().calledIndex++}`;
        if (!Array.isArray(selectors)) {
            selectors = [selectors];
        }
        this.#selectors = selectors
            .map(selectors.some(s => s.indexOf(',') > -1) ? s => s : s => `${s}:not([${this.#called}])`)
            .join(',');
        this.#fn = fn;
        const matches = selectors.map(s => s.trim()).filter(s => s.indexOf(' ') < 0 && s.indexOf(',') < 0);
        if (matches.length === selectors.length) {
            this.#matches = matches;
        }
    }
    addTriggerElements(triggerElements, element) {
        if (this.#root.contains(element)) {
            if (element.matches(this.#selectors)) {
                triggerElements.push(element);
            }
            for (const e of element.querySelectorAll(this.#selectors)) {
                triggerElements.push(e);
            }
        }
    }
    trigger(elements) {
        if (document.readyState === 'loading') {
            return;
        }
        if (this.#matches && Array.isArray(elements)) {
            for (const element of elements) {
                if (this.#matches.some(m => element.matches(m))) {
                    this.#callCallback(element);
                }
            }
        }
        else {
            for (const element of this.#root.querySelectorAll(this.#selectors)) {
                this.#callCallback(element);
            }
        }
    }
    #callCallback(element) {
        if (inBlacklist(element)) {
            return;
        }
        if (!element.hasAttribute(this.#called)) {
            element.setAttribute(this.#called, '');
            try {
                this.#fn(element);
            }
            catch (error) {
                log.error('Failed callback!', element, error);
            }
        }
    }
}
function inBlacklist(node) {
    if (!(node instanceof Element))
        return false;
    const { blacklist } = getState();
    return blacklist.length > 0 && blacklist.some(s => node.closest(s) !== null);
}
const every = Array.prototype.every;
const isText = (node) => node.nodeType === Node.TEXT_NODE;
function triggerCallbacks(mutations) {
    const state = getState();
    if (state.triggerPaused) {
        state.triggerOnResume = true;
        return;
    }
    const checkMutations = Array.isArray(mutations);
    const validMutations = [];
    if (checkMutations) {
        for (const mutation of mutations) {
            const target = mutation.target;
            if (inBlacklist(target)) {
                continue;
            }
            switch (mutation.type) {
                case 'attributes':
                    if (mutation.oldValue !== target.getAttribute(mutation.attributeName)) {
                        validMutations.push(mutation);
                    }
                    break;
                case 'childList':
                    if (!every.call(mutation.addedNodes, isText) || !every.call(mutation.removedNodes, isText)) {
                        validMutations.push(mutation);
                    }
                    break;
            }
        }
        if (validMutations.length === 0) {
            return;
        }
    }
    if (validMutations.length > 500) {
        state.triggerAll = true;
    }
    if (!state.triggerAll) {
        if (checkMutations) {
            for (const mutation of validMutations) {
                const element = mutation.target;
                switch (mutation.type) {
                    case 'attributes':
                        state.triggerElements.push(element);
                        break;
                    case 'childList':
                        for (const callback of state.callbacks) {
                            callback.addTriggerElements(state.triggerElements, element);
                        }
                        break;
                }
            }
        }
        else {
            state.triggerAll = true;
        }
    }
    if ((state.triggerAll || state.triggerElements.length > 0) && !state.triggerFrame) {
        state.triggerFrame = window.requestAnimationFrame(() => {
            const elements = state.triggerAll ? undefined : state.triggerElements;
            state.triggerAll = false;
            state.triggerElements = [];
            state.triggerFrame = 0;
            state.callbacks.forEach(callback => callback.trigger(elements));
        });
    }
}
function initialize() {
    triggerCallbacks();
    new MutationObserver(triggerCallbacks).observe(document, {
        attributes: true,
        attributeFilter: [
            'class',
            'data-bsp-autosubmit',
            'data-chart-type',
            'data-code-type',
            'data-internal-name',
            'data-tab',
            'name',
            'rel',
            'target',
        ],
        attributeOldValue: true,
        childList: true,
        subtree: true,
    });
}
function ensureInitialized() {
    const state = getState();
    if (state.initialized)
        return;
    state.initialized = true;
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    }
    else {
        initialize();
    }
}
function onFind(rootOrSelectors, selectorsOrFn, fn) {
    ensureInitialized();
    let root;
    let selectors;
    if (typeof fn !== 'undefined') {
        root = rootOrSelectors;
        selectors = selectorsOrFn;
    }
    else {
        root = document;
        selectors = rootOrSelectors;
        fn = selectorsOrFn;
    }
    const callback = new Callback(root, selectors, fn);
    callback.trigger();
    getState().callbacks.push(callback);
}
onFind.triggerCallbacks = triggerCallbacks;
onFind.pause = () => {
    getState().triggerPaused = true;
};
onFind.resume = () => {
    const state = getState();
    state.triggerPaused = false;
    if (state.triggerOnResume) {
        state.triggerOnResume = false;
        setTimeout(triggerCallbacks, 100);
    }
};
onFind.ignore = (...selectors) => {
    const { blacklist } = getState();
    for (const s of selectors) {
        try {
            document.querySelector(s);
        }
        catch {
            continue;
        }
        if (!blacklist.includes(s)) {
            blacklist.push(s);
        }
    }
};
export default onFind;
//# sourceMappingURL=onFind.js.map