import { isFunction } from "../utils/is-function";
import { handlers, subscriptions, waitingElementHanlders } from "../internal-state";
import { errorSink } from "../sinks/error-sink";
import { DOMSinks } from "../sinks/index";
import { terminationSink } from "../sinks/termination-sink";
const debug = {
    leave_refs: false,
};
export const nonBubblingAttributes = new Set(['ended', 'play', 'pause', 'volumechange']); // TODO: add more?
const elementNodes = (n) => n.nodeType == 1;
export const transferAttributes = (node) => {
    // ([].concat(...node.attributes) || []))
    (Array.from(node.attributes) || [])
        .forEach(attr => {
        const key = attr.nodeName;
        const value = attr.nodeValue;
        const eventName = key.replace(/^on/, '');
        const isEventSource = eventName !== key;
        const splat = /^#REF\d+$/.test(value) && node.innerHTML.split(value) || [];
        let intermediateInteractiveNode; // Text;
        if (splat.length == 2) {
            intermediateInteractiveNode = document.createTextNode(''); // +initial value?
            node.innerHTML = splat[0];
            node.append(intermediateInteractiveNode);
            node.insertAdjacentHTML('beforeend', splat[1]);
        }
        if (/^#REF/.test(value) || key == 'onmount' || isEventSource) {
            debug.leave_refs || node.removeAttribute(key);
            (waitingElementHanlders.get(value) || []).forEach(conf => {
                const hand = conf.handler;
                const boundHandler = isFunction(hand) ? hand.bind(node) :
                    isFunction(hand.next) ? hand.next.bind(hand) :
                        null;
                if (boundHandler && nonBubblingAttributes.has(eventName)) {
                    node.addEventListener(eventName, boundHandler, { capture: true });
                }
                else if (conf.type == 'event' || conf.type == 'source' || key == 'onmount') {
                    // if it's an event source (like onclick, etc)
                    Object.keys(conf).length && handlers.set(node, [].concat(handlers.get(node) || [], { ...conf, handler: boundHandler }));
                    //} else if(key != 'onmount' && (key != 'resolve' || ['innerHTML', 'innerText', 'attribute', 'attributeset', 'class', 'classset', 'dataset'].includes(conf.type))) {
                    //} else if(conf.type == 'attributeset') {
                    //	attributesSink(node)(hand) // merge attributes in // some could have gone in earlier, e.g.: class, or other attributes, but will after mount. Is that ok?
                }
                else {
                    if (DOMSinks.has(conf.type)) { // if it's a sink (innerHTML, etc)
                        // TODO: conf.attribute may itself be a promise or an observable, so need to subscribe to it
                        const sink = DOMSinks.get(conf.type);
                        const subscriptionCallback = sink(intermediateInteractiveNode || node, conf.attribute);
                        const sourceStream = conf.attribute?.then || conf.attribute?.subscribe ? conf.attribute : conf.handler;
                        const subscription = sourceStream.then ? sourceStream.then(subscriptionCallback).catch(conf.error || errorSink)
                            : sourceStream.subscribe ? sourceStream.subscribe(subscriptionCallback, conf.error || errorSink, conf.termination || terminationSink)
                                : typeof sourceStream == 'function' ? sourceStream(node)
                                    : () => { };
                        if (sourceStream.subscribe) {
                            const subscriptionList = subscriptions.get(node) || [];
                            subscriptionList.push(subscription);
                            subscriptions.set(node, subscriptionList);
                        }
                    }
                }
            });
            waitingElementHanlders.delete(value);
            if (key == 'onmount') {
                setTimeout(() => node.dispatchEvent(new CustomEvent('mount', { bubbles: true, detail: {} })), 0);
                //node.dispatchEvent(new CustomEvent('mount', {bubbles: true, detail: {}}))
            }
        }
    });
};
export const removeListeners = (node) => {
    [...node.children]
        .forEach(node => removeListeners(node));
    subscriptions.get(node)?.forEach(l => l.unsubscribe?.());
    subscriptions.delete(node);
};
export const mount = (mutationsList, observer) => {
    const childList = mutationsList
        .filter(m => m.type === 'childList');
    // TODO: performance - use document.createTreeWalker
    const addedNodes = childList
        .flatMap(m => ([...m.addedNodes]))
        .filter(elementNodes);
    addedNodes
        .flatMap(node => [node].concat(...node.querySelectorAll('[RESOLVE]')))
        .forEach(transferAttributes);
    // TODO: performance - use document.createTreeWalker
    childList
        .flatMap(m => ([...m.removedNodes]))
        .filter(elementNodes)
        .forEach(removeListeners);
};
