/**
* 
* A lightweight wizard UI component that supports accessibility and HTML5 in JavaScript Vanilla.
*
* @link   https://github.com/AdrianVillamayor/Wizard-JS
* @author Adrii[https://github.com/AdrianVillamayor]
* 
* @class  Wizard
*/

class Wizard1 {

    constructor(args) {

        let opts = {
            "wz_class": "",
            "wz_nav": ".wizard-nav",
            "wz_ori": ".horizontal",
            "wz_nav_style": "dots",
            "wz_content": ".wizard-content",
            "wz_buttons": ".wizard-buttons",
            "wz_button": ".wizard-btn",
            "wz_button_style": ".btn",
            "wz_step": ".wizard-step",
            "wz_form": ".wizard-form",
            "wz_next": ".next",
            "wz_prev": ".prev",
            "wz_finish": ".finish",
            "wz_highlight": ".highlight",

            "nav": true,
            "buttons": true,
            "highlight": true,

            "current_step": 0,
            "steps": 0,
            "highlight_time": 1000,
            "navigation": "all",
            "next": "Next",
            "prev": "Prev",
            "finish": "Submit",

            "highlight_type": { "error": "error", "warning": "warning", "success": "success", "info": "info" },

            "i18n": {
                "empty_wz": "No item has been found with which to generate the Wizard.",
                "empty_nav": "Nav does not exist or is empty.",
                "empty_content": "Content does not exist or is empty.",
                "empty_html": "Undefined or null content cannot be added.",
                "empty_update": "Nothing to update.",
                "no_nav": "Both the nav and the buttons are disabled, there is no navigation system.",
                "form_validation": "One or more of the form fields are invalid.",
                "diff_steps": "Discordance between the steps of nav and content.",
                "random": "There has been a problem, check the configuration and use of the wizard.",
                "already_definded": "This item is already defined",
                "title": "Step"
            }
        };

        this.prefabOpts(opts, args);

        this.wz_class = this.options.wz_class;
        this.wz_active = "active";
        this.wz_ori = this.options.wz_ori;
        this.wz_nav = this.options.wz_nav;
        this.wz_nav_style = this.options.wz_nav_style;
        this.wz_content = this.options.wz_content;
        this.wz_buttons = this.options.wz_buttons;
        this.wz_button = this.options.wz_button;
        this.wz_button_style = this.options.wz_button_style;
        this.wz_step = this.options.wz_step;
        this.wz_form = this.options.wz_form;
        this.wz_next = this.options.wz_next;
        this.wz_prev = this.options.wz_prev;
        this.wz_finish = this.options.wz_finish;
        this.wz_highlight = this.options.wz_highlight;

        this.buttons = this.options.buttons;
        this.nav = this.options.nav;
        this.highlight = this.options.highlight;

        this.highlight_time = this.options.highlight_time;
        this.highlight_type = this.options.highlight_type;

        this.steps = this.options.steps;
        this.current_step = this.options.current_step;
        this.last_step = this.current_step;
        this.navigation = this.options.navigation;
        this.prev = this.options.prev;
        this.next = this.options.next;
        this.finish = this.options.finish;
        this.form = false;
        this.locked = false;
        this.locked_step = null;
    }

    /**
    * Initializes the wizard
    * 
    * @customevent wz.ready     - Indicates that wizard has loaded and is accessible.
    * @property {object} target - wz_class
    * @property {object} elem   - DOM element
    * 
    * @throws empty_wz          - Not found any element to generate the Wizard with.
    * @throws empty_nav         - Nav does not exist or is empty.
    * @throws empty_content     - Content does not exist or is empty.
    * @throws diff_steps        - Discordance between the steps of nav and content.
    * @throws random            - There has been a problem check the configuration and use of the wizard.
    * 
    * @return {void}
    */

    init() {
        try {
            const wz_check = ($_.exists(document.querySelector(this.wz_class))) ? document.querySelector(this.wz_class) : $_.throwException(this.options.i18n.empty_wz);

            if ($_.str2bool(wz_check.getAttribute("data-wizard")) && wz_check.getAttribute("data-wizard") === this.wz_active) {
                console.warn(`${this.wz_class} : ${this.options.i18n.already_definded}`);
                return false;
            }

            $_.cleanEvents(document.querySelector(this.wz_class), true);

            const wz = ($_.exists(document.querySelector(this.wz_class))) ? document.querySelector(this.wz_class) : $_.throwException(this.options.i18n.empty_wz);

            if ($_.str2bool(this.buttons) === false && $_.str2bool(this.nav) === false) {
                console.warn(this.options.i18n.no_nav);
            }

            wz.classList.add((this.wz_ori).replace(".", ""));

            if (wz.tagName === "FORM") {
                this.form = true;
            }

            this.check2Prepare(wz)

            switch (this.navigation) {
                case "all":
                case "nav":
                    this.setNavEvent()
                    this.setBtnEvent()

                    break;
                case "buttons":
                    this.setBtnEvent();
                    break;
            }

            wz.style.display = ($_.hasClass(wz, "vertical")) ? "flex" : "block";

            wz.setAttribute("data-wizard", this.wz_active)

            document.dispatchEvent(new CustomEvent("wz.ready", {
                detail: {
                    "target": this.wz_class,
                    "elem": document.querySelector(this.wz_class)
                }
            }));

        } catch (error) {
            throw error;
        }
    }


    /**
    * Check and update each section of the wizard.
    * 
    * @customevent wz.update    - Indicates that wizard has updaded and is accessible.
    * @property {object} target - wz_class
    * @property {object} elem   - DOM element
    * 
    * @throws empty_wz          - Not found any element to generate the Wizard with.
    * 
    * @return {void}
    */

    update() {
        const wz = ($_.exists(document.querySelector(this.wz_class))) ? document.querySelector(this.wz_class) : $_.throwException(this.options.i18n.empty_wz);

        if (($_.str2bool(wz.getAttribute("data-wizard")) === false) && wz.getAttribute("data-wizard") != this.wz_active) {
            $_.throwException(this.options.i18n.empty_wz);
        }

        this.check2Prepare(wz)

        this.content_update = false;

        document.querySelector(this.wz_class).dispatchEvent(new CustomEvent("wz.update", {
            detail: {
                "target": this.wz_class,
                "elem": document.querySelector(this.wz_class)
            }
        }));

    }

    /**
    * Restart the wizard
    * 
    * @event wz.reset 
    * 
    * @return {void}
    */

    reset() {
        this.setCurrentStep(0);

        const wz = document.querySelector(this.wz_class);
        const nav = wz.querySelector(this.wz_nav);
        const content = wz.querySelector(this.wz_content);

        if ($_.str2bool(this.buttons) !== false) {
            let buttons = wz.querySelector(this.wz_buttons);

            let next = buttons.querySelector(this.wz_button + this.wz_next);
            let prev = buttons.querySelector(this.wz_button + this.wz_prev);
            let finish = buttons.querySelector(this.wz_button + this.wz_finish);

            this.checkButtons(next, prev, finish)
        }

        let $wz_nav = nav.querySelectorAll(this.wz_step);
        $_.removeClassList($wz_nav, "active");

        let $wz_content = content.querySelectorAll(this.wz_step);
        $_.removeClassList($wz_content, "active");

        nav.querySelector(`${this.wz_step}[data-step="${this.getCurrentStep()}"]`).classList.add("active");
        content.querySelector(`${this.wz_step}[data-step="${this.getCurrentStep()}"]`).classList.add("active");

        wz.dispatchEvent(new Event("wz.reset"));
    }

    /**
    * Locks the wizard in the active step
    * 
    * @return {void}
    */

    lock() {
        this.locked = true;
        this.locked_step = this.getCurrentStep();
    }

    /**
    * Unlock wizard
    * 
    * @event wz.unlock 
    * 
    * @return {void}
    */

    unlock() {
        this.locked = false;
        this.locked_step = null;

        document.querySelector(this.wz_class).dispatchEvent(new Event("wz.unlock"));
    }


    /**
    * Generate the steps and define a standard for each step.
    * 
    * @param {object}  $wz_nav           - Nav element
    * @param {object}  $wz_nav_steps     - Steps elements inside Nav
    * @param {object}  $wz_content_steps - Steps elements inside Content
    * 
    * @return {void}
    */

    prefabSteps($wz_content_steps, $wz_nav, $wz_nav_steps) {
        let active_index = this.getCurrentStep();

        for (let i = 0; i < $wz_content_steps.length; i++) {
            let $this = $wz_content_steps[i];

            $this.setAttribute("data-step", i);

            if ($_.str2bool(this.nav) !== false) {
                $wz_nav_steps[i].setAttribute("data-step", i);
            }
        };

        if ($_.str2bool(this.nav) !== false) {
            $_.removeClassList($wz_nav_steps, "active");
            $wz_nav_steps[active_index].classList.add("active");
            $wz_nav.classList.add(this.wz_nav_style);
        }

        $_.removeClassList($wz_content_steps, "active");
        $wz_content_steps[active_index].classList.add("active");

        // if (this.form) this.update2Form();

        this.setButtons();
    }

    /**
    * Adds the form tag and converts the wizard into a <form>
    * 
    * @return {void}
    */

    update2Form() {
        let wz = document.querySelector(this.wz_class);
        let wz_content = wz.querySelector(this.wz_content);

        if (wz_content.tagName !== "FORM") {
            let wz_content_class = wz_content.getAttribute("class");
            let wz_content_content = wz_content.innerHTML

            wz_content.remove();
            const $form = document.createElement("form");

            $form.setAttribute("method", "POST");
            $form.setAttribute("class", wz_content_class + " " + (this.wz_form).replace(".", ""));

            $form.innerHTML = wz_content_content;

            wz.appendChild($form)
        }
    }

    /**
    * Checks and validates each input/select/textarea of the active step.
    * If the step has no inputs, the checks will be ignored.
    * 
    * @throws random    - There has been a problem check the configuration and use of the wizard.
    * 
    * @return {boolean} - If everything is OK, it returns false
    */

    checkForm() {
        let wz = document.querySelector(this.wz_class);
        let wz_content = wz.querySelector(this.wz_content);

        let steps = wz_content.querySelectorAll(this.wz_step);
        let target = steps[this.getCurrentStep()];
        let validation = false;

        let inputs = target.querySelectorAll("input,textarea,select");

        if (inputs.length > 0) {
            validation = this.formValidator(wz_content, inputs);
        }

        return validation;
    }

    /**
    * Generating, styling and shaping the Nav
    * 
    * @param {object} $wz - Wizard element
    * 
    * @return {void}
    */

    setNav($wz) {
        let wz_nav = $wz.querySelector(this.wz_nav);

        if ($_.exists(wz_nav) !== false && $_.str2bool(this.nav) !== false) {
            wz_nav.remove()
            wz_nav = $wz.querySelector(this.wz_nav);
        }

        if ($_.exists(wz_nav) === false && $_.str2bool(this.nav) !== false) {

            let wz_content = $wz.querySelector(this.wz_content);
            let steps = wz_content.querySelectorAll(this.wz_step);

            const nav = document.createElement("ASIDE");
            nav.classList.add((this.wz_nav).replace(".", ""));

            const wz_content_steps = wz_content.querySelectorAll(this.wz_step);
            const steps_length = wz_content_steps.length;

            for (let i = 0; i < steps_length; i++) {
                const nav_step = document.createElement("DIV");
                let title = (steps[i].hasAttribute("data-title")) ? steps[i].getAttribute("data-title") : `${this.options.i18n.title} ${i}`;
                nav_step.classList.add((this.wz_step).replace(".", ""));

                if (this.navigation === "buttons") nav_step.classList.add("nav-buttons");

                const dot = document.createElement("SPAN");
                dot.classList.add('dot');
                nav_step.appendChild(dot);

                const span = document.createElement("SPAN");
                span.innerHTML = title;
                nav_step.appendChild(span);

                nav.appendChild(nav_step);
            }

            $wz.prepend(nav);
        }
    }

    /**
    * Generating, styling and shaping Buttons
    * 
    * @return {void}
    */

    setButtons() {
        let wz = document.querySelector(this.wz_class);
        let wz_btns = wz.querySelector(this.wz_buttons);;


        if ($_.exists(wz_btns) !== false && $_.str2bool(this.buttons) !== false) {
            wz_btns.remove()
            wz_btns = wz.querySelector(this.wz_buttons);;
        }

        if ($_.exists(wz_btns) === false && $_.str2bool(this.buttons) !== false) {
            const buttons = document.createElement("ASIDE");
            buttons.classList.add((this.wz_buttons).replace(".", ""));

            let btn_style = (this.wz_button_style).replaceAll(".", "");
            btn_style = btn_style.split(" ");

            const prev = document.createElement("BUTTON");
            prev.innerHTML = this.prev;
            prev.classList.add((this.wz_button).replace(".", ""));
            prev.classList.add(...btn_style);
            prev.classList.add((this.wz_prev).replace(".", ""));

            if (this.navigation === "nav") prev.style.display = "none";

            buttons.appendChild(prev);

            const next = document.createElement("BUTTON");
            next.innerHTML = this.next;
            next.classList.add((this.wz_button).replace(".", ""));
            next.classList.add(...btn_style);
            next.classList.add((this.wz_next).replace(".", ""));

            if (this.navigation === "nav") next.style.display = "none";

            buttons.appendChild(next);

            const finish = document.createElement("BUTTON");
            finish.innerHTML = this.finish;
            finish.classList.add((this.wz_button).replace(".", ""));
            finish.classList.add(...btn_style);
            finish.classList.add((this.wz_finish).replace(".", ""));
            buttons.appendChild(finish);

            this.checkButtons(next, prev, finish)

            wz.appendChild(buttons);
        }
    }

    /**
    * Generating, styling and shaping Buttons
    * 
    * @param {object} next   - Next button element
    * @param {object} prev   - Prev button element
    * @param {object} finish - Finish button element
    * 
    * @return {void}
    */

    checkButtons(next, prev, finish) {
        let current_step = this.getCurrentStep();
        let n_steps = this.steps - 1;

        if (current_step == 0) {
            prev.setAttribute("disabled", true);
        } else {
            prev.removeAttribute("disabled");
        }

        if (current_step == n_steps) {
            next.setAttribute("disabled", true);
            finish.style.display = "block";
        } else {
            finish.style.display = "none";
            next.removeAttribute("disabled");
        }
    }


    /**
   * Common function for wizard checks and prefab.
   * 
   * @param {object} wz - Wizard element
   * 
   * @return {void}
   */

    check2Prepare(wz) {

        this.setNav(wz);

        const wz_content = ($_.exists(wz.querySelector(this.wz_content))) ? wz.querySelector(this.wz_content) : $_.throwException(this.options.i18n.empty_content);
        const wz_content_steps = wz_content.querySelectorAll(this.wz_step);
        const wz_content_steps_length = (wz_content_steps.length > 0) ? wz_content_steps.length : $_.throwException(this.options.i18n.empty_content);

        let wz_nav = undefined;
        let wz_nav_steps = undefined;

        if ($_.str2bool(this.nav) !== false) {
            wz_nav = ($_.exists(wz.querySelector(this.wz_nav))) ? wz.querySelector(this.wz_nav) : $_.throwException(this.options.i18n.empty_nav);
            wz_nav_steps = wz_nav.querySelectorAll(this.wz_step);
            const wz_nav_steps_length = (wz_nav_steps.length > 0) ? wz_nav_steps.length : $_.throwException(this.options.i18n.empty_nav);

            if (wz_nav_steps_length != wz_content_steps_length) {
                $_.throwException(this.options.i18n.diff_steps);
            }
        }

        this.steps = wz_content_steps_length;

        this.prefabSteps(wz_content_steps, wz_nav, wz_nav_steps);
    }

    /**
    * Click event handler for Buttons and Nav.
    * 
    * @param {object} e         - Event
    * 
    * @event wz.btn.prev        - In case the wizard goes backwards, the wz.btn.prev event will be fired.
    * @event wz.btn.next        - In case the wizard advances, the nextWizard event will be fired.
    * @event wz.nav.forward     - In case of moving forward with the navbar, the forwardNavWizard event will be fired.
    * @event wz.nav.backward    - In case of moving backward with the navbar, the backwardNavWizard event will be fired.
    * @event wz.lock            - In case it is blocked, it will fire the wz.lock event.
    * 
    * @customevent wz.error     - If the form is not correctly filled in, the wz.error event will be fired.
    * @property {string} id     - Eror id
    * @property {string} msg    - Error message
    * @property {object} target - Contains all the elements that have given error
    * 
    * @return {void}
    */

    onClick(e) {
        const $this = e
        const wz = document.querySelector(this.wz_class);

        if (this.locked && this.locked_step === this.getCurrentStep()) {
            wz.dispatchEvent(new Event("wz.lock"));
            return false;
        }

        const parent = $_.getParent($this, this.wz_class);

        const nav = parent.querySelector(this.wz_nav);
        const content = parent.querySelector(this.wz_content);

        const is_btn = ($_.hasClass($this, this.wz_button));
        const is_nav = ($_.hasClass($this, this.wz_step));


        let step = ($_.str2bool($this.getAttribute("data-step")) !== false) ? parseInt($this.getAttribute("data-step")) : this.getCurrentStep();

        if (is_btn) {
            if ($_.hasClass($this, this.wz_prev)) {
                step = step - 1;
                wz.dispatchEvent(new Event("wz.btn.prev"));
            } else if ($_.hasClass($this, this.wz_next)) {
                step = step + 1;
                wz.dispatchEvent(new Event("wz.btn.next"));
            }
        }

        let step_action = step > this.getCurrentStep()

        if (is_nav) {
            if (step_action) {
                wz.dispatchEvent(new Event("wz.nav.forward"));
            } else if (step < this.getCurrentStep()) {
                wz.dispatchEvent(new Event("wz.nav.backward"));
            }
        }

        if (this.form && this.navigation != "buttons") {
            if (step_action) {
                if ((step !== this.getCurrentStep() + 1)) {
                    if (step >= this.last_step) {
                        step = this.last_step;
                    } else {
                        step = this.getCurrentStep() + 1;
                    }
                }
            }
        }

        if (this.form) {
            const check_form = this.checkForm()
            if (check_form.error === true) {

                if (step_action) {
                    wz.dispatchEvent(new CustomEvent("wz.error", {
                        detail: {
                            "id": "form_validaton",
                            "msg": this.options.i18n.form_validation,
                            "target": check_form.target
                        }
                    }));
                }

                this.last_step = this.getCurrentStep();
                if (this.getCurrentStep() < step) {
                    return false;
                }
            }
        }

        if ($_.str2bool(step)) {
            this.setCurrentStep(step)
        }

        if ($_.str2bool(this.buttons) !== false) {
            const buttons = parent.querySelector(this.wz_buttons);
            const next = buttons.querySelector(this.wz_button + this.wz_next);;
            const prev = buttons.querySelector(this.wz_button + this.wz_prev);
            const finish = buttons.querySelector(this.wz_button + this.wz_finish);

            this.checkButtons(next, prev, finish)
        }

        if ($_.str2bool(this.nav) !== false) {
            const $wz_nav = nav.querySelectorAll(this.wz_step);
            $_.removeClassList($wz_nav, "active");
            nav.querySelector(`${this.wz_step}[data-step="${this.getCurrentStep()}"]`).classList.add("active");
        }

        const $wz_content = content.querySelectorAll(this.wz_step);
        $_.removeClassList($wz_content, "active");
        content.querySelector(`${this.wz_step}[data-step="${this.getCurrentStep()}"]`).classList.add("active");
    }


    /**
    * Notifies that the wizard has been completed.
    * 
    * @param {object} e     - Event
    * 
    * @event wz.form.submit - If the wizard is a form it will fire submitWizard
    * @event wz.end         - If the wizard is not a form it will fire endWizard
    * 
    * @return {void}
    */

    onClickFinish(e) {
        if (this.form) {
            const check_form = this.checkForm()
            if (check_form.error !== true) {
                document.querySelector(this.wz_class).dispatchEvent(new Event("wz.form.submit"));
            }
        } else {
            document.querySelector(this.wz_class).dispatchEvent(new Event("wz.end"));
        }
    }

    /**
    * Set the active step 
    * 
    * @param {int} step - The active step 
    * 
    * @return {void}
    */

    setCurrentStep(step) {
        this.current_step = this.setStep(step);
    }

    /**
    * Return the active step 
    * 
    * @return {int} The active step 
    */

    getCurrentStep() {
        return this.current_step;
    }

    /**
    * Check and match the steps of the wizard.
    * 
    * @param {int} step - Step 
    * 
    * @return {int} Step
    */

    setStep(step) {
        let parent = document.querySelector(this.wz_class);
        let content = parent.querySelector(this.wz_content);

        let check_content = content.querySelector(`${this.wz_step}[data-step="${step}"]`);

        if ($_.exists(check_content) === false) {
            let content_length = (content.querySelectorAll(this.wz_step).length) - 1;

            let diff = $_.closetNubmer(content_length, step)

            step = diff;
        }

        this.last_step = (step > this.last_step) ? step : this.last_step;

        return parseInt(step);
    }

    /**
    * Set Nav events
    * 
    * @return {void}
    */

    setNavEvent() {
        let _self = this;
        let el = document.querySelector(this.wz_class);

        $_.delegate(el, "click", `${this.wz_nav} ${this.wz_step}`, function (event) {
            event.preventDefault()
            _self.onClick(this)
        });
    }


    /**
    * Set Button events
    * 
    * @return {void}
    */

    setBtnEvent() {
        let _self = this;
        let el = document.querySelector(this.wz_class);

        $_.delegate(el, "click", `${this.wz_buttons} ${this.wz_button}`, function clickFunction(event) {
            event.preventDefault()

            if ($_.hasClass(event.target, _self.wz_finish)) {
                _self.onClickFinish(this)
            } else {
                _self.onClick(this)

            }
        });
    }

    /**
    * Set options of wizard
    * 
    * @param {object} options - List of options to build the wizard 
    * 
    * @return {void}
    */

    set_options(options) {
        this.options = options;
    }

    /**
    * Check and match the options of the wizard with args definieds
    * 
    * @param {object} options - List of options to build the wizard 
    * @param {object} args    - List of arguments modifying the base options
    *
    * @return {void}
    */

    prefabOpts(options, args) {
        Object.entries(args).forEach(([key, value]) => {
            if (typeof value === 'object') {
                Object.entries(value).forEach(([key_1, value_1]) => {
                    options[key][key_1] = value_1;
                });
            } else {
                options[key] = value;
            }
        });

        this.set_options(options);
    }

    /**
   * Checks the fields of the active step, in case there is an error it generates a highlight. 
   * Returns an array with all the fields that have given error.
   * 
   * @param {object} wz_content  - Active wizard content 
   * @param {object} formData    - All inputs, textarea and select of the active content 
   * 
   * @return {object} 
   * @property {bool} error      - There is an error or not
   * @property {array} target    - Contains all the elements that have given error
   */

    formValidator(wz_content, formData) {

        let error = false;
        let target = [];

        for (let e of formData) {
            if ($_.hasClass(e, "required") || $_.exists(e.getAttribute("required"))) {

                let check = false
                switch (e.tagName) {
                    case "INPUT":
                        check = $_.dispatchInput(wz_content, e);
                        break;
                    case "SELECT":
                    case "TEXTAREA":
                        check = $_.isEmpty(e.value);
                        break;
                }

                if (check === false) {
                    error = true;
                    target.push(e);

                    if ($_.str2bool(this.highlight) === true) {
                        $_.highlight(e, this.wz_highlight, this.highlight_type['error'], this.highlight_time);
                    }
                }
            }
        }

        return {
            "error": error,
            "target": target
        };
    }
};


const $_ = {
    hasClass: function (e, className) {
        className = className.replace(".", "");
        return new RegExp('(\\s|^)' + className + '(\\s|$)').test(e.className);
    },

    getParent: function (elem, selector) {
        let parent = undefined;

        while (elem.parentNode.tagName !== "BODY" && parent === undefined) {
            elem = elem.parentNode;

            if (elem.matches(selector)) {
                parent = elem;
            }
        }

        return parent;
    },

    delegate: function (el, evt, sel, handler) {
        el.addEventListener(evt, function (event) {
            let t = event.target;
            while (t && t !== this) {
                if (t.matches(sel)) {
                    handler.call(t, event);
                }
                t = t.parentNode;
            }
        });
    },

    removeClassList: function (e, className) {
        for (let element of e) {
            element.classList.remove(className);
        };
    },

    objToString: function (obj, delimiter = ";") {
        let str = '';
        for (const p in obj) {
            if (obj.hasOwnProperty(p)) {
                str += p + ':' + obj[p] + delimiter;
            }
        }
        return str;
    },

    isHidden: function (el) {
        const style = window.getComputedStyle(el);
        return (style.display === 'none')
    },

    str2bool: function (value) {
        const str = String(value);
        switch (str.toLowerCase()) {
            case 'false':
            case 'no':
            case 'n':
            case '':
            case 'null':
            case 'undefined':
                return false;
            default:
                return true;
        }
    },

    exists: function (element) {
        return (typeof (element) != 'undefined' && element != null);
    },

    throwException: function (message) {
        let err;
        try {
            throw new Error('wz.error');
        } catch (e) {
            err = e;
        }
        if (!err) return;

        let aux = err.stack.split("\n");
        aux.splice(0, 2); //removing the line that we force to generate the error (var err = new Error();) from the message
        aux = aux.join('\n"');
        throw message + ' \n' + aux;
    },

    closetNubmer: function (length, step) {
        let counts = [];

        for (let index = 0; index <= length; index++) {
            counts.push(index)
        }

        let closet = counts.reduce(function (prev, curr) {
            return (Math.abs(curr - step) < Math.abs(prev - step) ? curr : prev);
        });

        return closet;
    },

    highlight: function (e, highlight_class, highlight_type, highlight_time) {
        let target = highlight_class.replace(".", "");
        let classHigh = `${target}-${highlight_type}`;

        e.classList.add(classHigh)

        setTimeout(function () {
            document.querySelectorAll(`[class*="${target}"]`)
                .forEach((el) => {
                    for (let i = el.classList.length - 1; i >= 0; i--) {
                        let className = el.classList[i];
                        if (className.startsWith(`${target}`)) {
                            el.classList.remove(className);
                        }
                    }
                });
        }, highlight_time);
    },

    dispatchInput: function (wz_content, e) {
        let type = e.getAttribute("type");
        let check = false;

        switch (type) {
            case "email":
                check = $_.isEmail(e.value);
                break;
            case "url":
                check = $_.isValidURL(e.value);
                break;
            case "checkbox":
                let name = e.getAttribute("name");

                if (name.includes("[]")) {
                    checkbox_check = wz_content.querySelectorAll(`input[type="checkbox"][name="${e.getAttribute("name")}"]:checked`);
                    check = (checkbox_check.length > 0);
                } else {
                    check = e.checked;
                }

                break;

            case "radio":
                let radio_check = wz_content.querySelectorAll(`input[type="radio"][name="${e.getAttribute("name")}"]:checked`);
                check = (radio_check.length > 0);
                break;

            default:
                check = $_.isEmpty(e.value);
                break;
        }

        return check
    },

    isEmpty: function (val) {
        val = val.trim();
        return (val != undefined && val != null && val.length > 0);
    },

    isEmail: function (email) {
        const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        return regex.test(email);
    },

    isValidURL: function (str) {
        let url;

        try {
            url = new URL(str);
        } catch (_) {
            return false;
        }

        return url.protocol === "http:" || url.protocol === "https:";
    },

    cleanEvents: function (el, withChildren = false) {
        if ($_.exists(el)) {
            if (withChildren) {
                el.parentNode.replaceChild(el.cloneNode(true), el);
            } else {
                const newEl = el.cloneNode(false);
                while (el.hasChildNodes()) newEl.appendChild(el.firstChild);
                el.parentNode.replaceChild(newEl, el);
            }
        }
    }
}

