/**
 * @author Isabelly & Luiz Felype
 * @license MIT
 * @version 1.0.0
 */

function isSelector(str) {
    if (typeof str !== 'string') return false;
    return /[.#\s\[\]>]/.test(str);
}

function isElement(obj) {
    return obj !== null && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string';
}

function isHtmlString(str) {
    if (typeof str !== 'string') return false;
    const trimmed = str.trim();
    return trimmed.startsWith('<') && trimmed.endsWith('>');
}

function frag (htmlString, doc = document) {
    const template = doc.createElement('template');
    template.innerHTML = htmlString;
    return template.content;
}

function text(content, doc = document) {
    return doc.createTextNode(content);
}

function tag (selectorOrTagOrHtml = 'div', doc = document) {
    let element;
    const attributeHandlers = {};

    const triggerHandlers = (attrName, newValue, oldValue) => {
        if (attributeHandlers[attrName] && newValue !== oldValue) {
            attributeHandlers[attrName].forEach(callback => {
                callback(newValue, oldValue, element);
            });
        }
    };

    if (isElement(selectorOrTagOrHtml)) {
        element = selectorOrTagOrHtml;
    } else if (isHtmlString(selectorOrTagOrHtml)) {
        element = frag(selectorOrTagOrHtml.trim(), doc).firstElementChild;
        if (!element) {
            throw new Error(`String HTML não resultou em um elemento válido: "${selectorOrTagOrHtml}"`);
        }
    } else if (isSelector(selectorOrTagOrHtml)) {
        element = doc.querySelector(selectorOrTagOrHtml);
        if (!element) throw new Error(`Elemento não encontrado: "${selectorOrTagOrHtml}"`);
    } else {
        element = doc.createElement(String(selectorOrTagOrHtml));
    }

    const targetFn = function() {};

    return new Proxy(targetFn, {
        get(_, prop, receiver) {
            if (prop === 'valueOf' || prop === Symbol.toPrimitive) return () => element;
            if (prop === 'toString') return () => element.outerHTML;
            if (prop === 'element') return element;

            if (prop === 'append') {
                return (...children) => {
                    children.flat().forEach(child => {
                        const childElement = typeof child === 'function' ? child() : child;
                        if (childElement) element.appendChild(childElement);
                    });
                    return receiver;
                };
            }

            if (prop === 'addEventListener') {
                return (event, handler, opts) => {
                  element.addEventListener(event, handler, opts);
                  return receiver;
                };
            }

            if (prop === 'onAttrChange') {
                return (keyOrObject, callback) => {
                    const unsubscribeFns = [];
                    const register = (key, cb) => {
                        if (!attributeHandlers[key]) attributeHandlers[key] = [];
                        attributeHandlers[key].push(cb);
                        return () => {
                            const index = attributeHandlers[key].indexOf(cb);
                            if (index > -1) attributeHandlers[key].splice(index, 1);
                        };
                    };

                    if (typeof keyOrObject === 'object' && keyOrObject !== null) {
                        for (const key in keyOrObject) {
                            if (Object.hasOwn(keyOrObject, key)) {
                                unsubscribeFns.push(register(key, keyOrObject[key]));
                            }
                        }
                        return () => unsubscribeFns.forEach(fn => fn());
                    } else if (typeof keyOrObject === 'string') {
                        return register(keyOrObject, callback);
                    }
                    return receiver;
                };
            }

            if (prop === 'data') {
                return (keyOrObject, value) => {
                    if (typeof keyOrObject === 'object' && keyOrObject !== null) {
                        for (const key in keyOrObject) {
                            if (Object.hasOwn(keyOrObject, key)) {
                                const oldValue = element.dataset[key];
                                element.dataset[key] = keyOrObject[key];
                                triggerHandlers(key, keyOrObject[key], oldValue);
                            }
                        }
                    } else if (typeof keyOrObject === 'string') {
                        const oldValue = element.dataset[keyOrObject];
                        element.dataset[keyOrObject] = value;
                        triggerHandlers(keyOrObject, value, oldValue);
                    }
                    return receiver;
                };
            }

            if (prop === 'variable') {
                return (keyOrObject, value) => {
                    if (typeof keyOrObject === 'object' && keyOrObject !== null) {
                        for (const key in keyOrObject) {
                            if (Object.hasOwn(keyOrObject, key)) {
                                element.style.setProperty(key, keyOrObject[key]);
                            }
                        }
                    } else if (typeof keyOrObject === 'string') {
                        element.style.setProperty(keyOrObject, value);
                    }
                    return receiver;
                };
            }

            if (prop === 'attr') {
                return (keyOrObject, value) => {
                    if (typeof keyOrObject === 'object' && keyOrObject !== null) {
                        for (const key in keyOrObject) {
                            if (Object.hasOwn(keyOrObject, key)) {
                                const oldValue = element.getAttribute(key);
                                element.setAttribute(key, keyOrObject[key]);
                                triggerHandlers(key, keyOrObject[key], oldValue);
                            }
                        }
                    } else if (typeof keyOrObject === 'string') {
                        const oldValue = element.getAttribute(keyOrObject);
                        element.setAttribute(keyOrObject, value);
                        triggerHandlers(keyOrObject, value, oldValue);
                    }
                    return receiver;
                };
            }

            return (value) => {
                let oldValue;
                if (prop in element.style) {
                    oldValue = element.style[prop];
                    element.style[prop] = value;
                } else if (prop in element) {
                    oldValue = element[prop];
                    element[prop] = value;
                } else {
                    oldValue = element.getAttribute(prop);
                    element.setAttribute(prop, value);
                }
                triggerHandlers(prop, value, oldValue);
                return receiver;
            };
        },
        apply() {
            return element;
        }
    });
}

window.tag = tag;
window.Tag = tag;