if(self && typeof self.ServiceWorkerGlobalScope !== 'undefined'){

    var isServiceWorker = true;

}
if(self && typeof self.WorkerGlobalScope !== 'undefined'){

    var isWorker = true;

}

var Helpers = {

    dataToExcelXlsx(data, requiredColumns, title){

        let xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
        <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
            xmlns:o="urn:schemas-microsoft-com:office:office"
            xmlns:x="urn:schemas-microsoft-com:office:excel"
            xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
            xmlns:html="http://www.w3.org/TR/REC-html40">
            <DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
                <Title>${title}</Title>
            </DocumentProperties>
            <Styles>
                <Style ss:ID="RedBackground">
                    <Interior ss:Color="#FF0000" ss:Pattern="Solid"/>
                </Style>
            </Styles>
            <Worksheet ss:Name="${title}">
                <Table>`;

        // Apply styling to required columns in the header
        xml += `<Row>`;

        data[0].forEach((column, index) => {
            if (requiredColumns.includes(column)) {
                xml += `<Cell ss:StyleID="RedBackground"><Data ss:Type="String">${column}</Data></Cell>`;
            } else {
                xml += `<Cell><Data ss:Type="String">${column}</Data></Cell>`;
            }
        });

        xml += `</Row>`;

        // Add data rows
        for (let i = 1; i < data.length; i++) {

            xml += `<Row>`;

            data[i].forEach(value => {
                xml += `<Cell><Data ss:Type="String">${value}</Data></Cell>`;
            });

            xml += `</Row>`;

        }

        // Close XML
        xml += `</Table></Worksheet></Workbook>`;

        return xml;

    },

    dataToXlsx(data, requiredColumns, title){

        let xmlContent = Helpers.dataToExcelXlsx(data, requiredColumns, title);

        // Convert the XML content to Base64
        let base64Content = btoa(unescape(encodeURIComponent(xmlContent)));
        
        // Create a data URI for the Base64 content
        let dataUri = `data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,${base64Content}`;
        
        // Create a link to download the XML file
        let link = document.createElement('a');
        link.href = dataUri;
        link.download = `${title}.xlsx`;

        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);

    },

    isElementInViewport: function (el) {

        if(!el.getBoundingClientRect && el.get) el = el.get(0);

        var rect = el.getBoundingClientRect();

        return (
            rect.top >= 0 &&
            rect.left >= 0 &&
            rect.bottom <= (window.innerHeight || document. documentElement.clientHeight) &&
            rect.right <= (window.innerWidth || document. documentElement.clientWidth)
        );

    },

    actionWhenVisibleOnScroll: function(elm, f, out){
        
        var timeout = null;

        $(window).scroll(function(){

            clearTimeout(timeout);

            timeout = setTimeout(function(){

                if(Helpers.isElementInViewport(elm)){

                    f(elm);

                } else{

                    if(out) out(elm);

                }

            }, 50);

        });

    },

    snapTimeout: 0,

    typeFocus: function(elm, text, focusElm){

        return Helpers.type(elm, text).then(function(){

            $(focusElm).focus();

        });

    },

    // Função que digita caractere por caractere, mas antes de chegar no que vai aparecer, ele da uma trocada
    // para um caractere aleatório
    type: function(elm, text, opts){

        if(typeof opts === 'undefined') opts = {};

        if(opts.speed) opts.delay = opts.speed;

        var delay = opts.delay || 10;
        var variation = opts.variation || 0;

        let resolve = null;

        let promise = new Promise(function(res, rej){

            resolve = res;

        });

        // Umas 3 letras pra frente vão sendo puxadas
        var randomText = function(){

            var ret = opts.startChar || '';

            for(var i = 0; i < (opts.randomCharsNumber || 1); i++){

                ret += '';

            }

            return ret;

        }

        let f = 'html';

        if(elm.is('input')) f = 'val';

        var type = function(index){

            // Varies due originial delay and variation
            delay = opts.delay + (Math.random() * variation);

            // Se chegou no final finaliza com o texto correto
            if(index >= text.length){

                resolve();
                
                return elm[f](text);
            }

            var newText = text.substring(0, index) + randomText();

            elm[f](newText);

            setTimeout(function(){

                type(index + 1);

            }, delay);

        }

        type(0);

        return promise;


    },

    typeOnAttribute: function(elm, text, attribute, opts){

        if(typeof opts === 'undefined') opts = {};

        if(opts.speed) opts.delay = opts.speed;

        var delay = opts.delay || 10;

        let resolve = null;

        let promise = new Promise(function(res, rej){

            resolve = res;

        });

        var type = function(index){

            // Se chegou no final finaliza com o texto correto
            if(index >= text.length){

                resolve();

                return elm.attr(attribute, text);

            }

            var newText = text.substring(0, index);

            elm.attr(attribute, newText);

            setTimeout(function(){

                type(index + 1);

            }, delay);

        }

        type(0);

        return promise;

    },

    // Função que digita caractere por caractere, mas antes de chegar no que vai aparecer, ele da uma trocada
    // para um caractere aleatório
    type: function(elm, text, opts){

        if(typeof opts === 'undefined') opts = {};

        if(opts.speed) opts.delay = opts.speed;

        var delay = opts.delay || 10;
        var variation = opts.variation || 0;

        let resolve = null;

        let promise = new Promise(function(res, rej){

            resolve = res;

        });

        // Umas 3 letras pra frente vão sendo puxadas
        var randomText = function(){

            var ret = opts.startChar || '';

            for(var i = 0; i < (opts.randomCharsNumber || 1); i++){

                ret += '';

            }

            return ret;

        }

        let f = 'html';

        if(elm.is('input')) f = 'val';

        var type = function(index){

            // Varies due originial delay and variation
            delay = opts.delay + (Math.random() * variation);

            // Se chegou no final finaliza com o texto correto
            if(index >= text.length){

                resolve();
                
                return elm[f](text);
            }

            var newText = text.substring(0, index) + randomText();

            elm[f](newText);

            setTimeout(function(){

                type(index + 1);

            }, delay);

        }

        type(0);

        return promise;


    },

    validateEmail: function(email){

        var re = /\S+@\S+\.\S+/;

        return re.test(email);

    },

    isMobile: function(){

        return $('body').width() <= 800;

    },

    snapToElm: function(container, divisor, isMobileFunction){

        if(isMobileFunction) return Helpers.snapScrollSide(container, divisor, isMobileFunction);

		clearInterval(Helpers.snapTimeout);

		Helpers.snapTimeout = setTimeout(() => {
			let divisorHeight = divisor.outerHeight(true);
			let ratio = Math.round(container[0].scrollTop / divisorHeight);
			let scrollTop = ratio * divisorHeight;

			container.animate({
				scrollTop: scrollTop,
			}, 150);

		}, 400);
    },

    // Uma versão alternativa ao snapScrollLeft
    snapScrollSide: function(elm, divisors, f){

        if(!f) f = function(){};

        var divWidth = divisors.outerWidth(true);

        var timeout = null;

        elm.off('scroll');
        elm.on('scroll', function(event){

            // Impede um scroll vindo do .animate
            if($(this).is(':animated')) return;

            clearTimeout(timeout);

            timeout = setTimeout(function(){

                var index = Math.round(elm.get(0).scrollLeft / divWidth);

                elm.animate({
                    scrollLeft: index * divWidth
                }, 100);

                f(index);

            }, 100);

        });

    },

    snapScrollTop: function(elm, divisors, f){

        var timeout = null;

        elm.off('scroll');
        elm.on('scroll', function(event){

            // Impede um scroll vindo do .animate
            if($(this).is(':animated')) return;

            clearTimeout(timeout);

            timeout = setTimeout(function(){

                var divHeight = divisors.outerHeight(true);
                var ratio = Math.round(elm.get(0).scrollTop / divHeight);

                elm.animate({
                    scrollTop: ratio * divHeight
                }, 200);

                f(ratio);

            }, 400);

        });

    },

    forceScroll: function (elm, divisors, next) {

        return new Promise(function (resolve) {

            var divSize = divisors.outerWidth(true);
            var nextPos = Math.round(elm[0].scrollLeft / divSize) * divSize + (next ? divSize : divSize * -1);

            elm.css('overflow', 'hidden');

            setTimeout(function () { elm.css('overflow', 'auto'); resolve(); }, 200);

            elm.animate({ 'scrollLeft': nextPos }, 200);

        });

    },

    instantSnap: function (elm, divisors, orientation) {

        var animateDelay = 200;

        if (typeof orientation === 'undefined') orientation = 'height';

        var scrollOrientation = 'scrollLeft';
        var measureFunction = 'outerWidth';

        if (orientation === 'height') {

            scrollOrientation = 'scrollTop';
            measureFunction = 'outerHeight';

        }

        var divSize = divisors[measureFunction](true);
        var ratio = Math.round(elm.get(0)[scrollOrientation] / divSize);

        elm.css('overflow', 'hidden');

        setTimeout(function () { elm.css('overflow', 'auto'); }, animateDelay);

        var objAnimate = {};

        objAnimate[scrollOrientation] = ratio * divSize;

        elm.animate(objAnimate, animateDelay);

    },

    snapScrollLeft: function(elm, divisors, animate, delay){

        var timeout = null;

        if(typeof animate === 'undefined') animate = 200;
        if(typeof delay   === 'undefined') delay   = 400;

        elm.off('scroll');

        elm.on('scroll', function(){

            clearTimeout(timeout);

            timeout = setTimeout(function(){

                Helpers.instantSnap(elm, divisors, 'width');

            }, delay);

        });

    },

    getSimpleUniqueId: function(prefix){

        if(typeof prefix == 'undefined') prefix = '';

        return prefix + Math.random() + '_' + new Date().getTime();

    },

    workers: {

        callbacks: {},

        register: function(worker){

            worker.onmessage = function(msg){
                
                var data = msg.data;

                if(data[0] == 'answer'){

                    Helpers.workers.callbacks[data[1]](data[2]);

                }

            }

        },

        postMessage: function(worker, label, data){

            return new Promise((resolve, reject) => {

                var id = Helpers.getSimpleUniqueId();

                Default.workers.progressiveImport.postMessage({
                    label: label,
                    data: data,
                    id: id
                });

                Helpers.workers.callbacks[id] = resolve;

                // resolve()

            });

        },

        receive: function(msg){

            if(!isWorker) return console.error("This function should only be used inside workers");

            if(!msg.data) return;

            if(!msg.data.label) return;

            // Se a função existir no escopo
            if(self[msg.data.label] && typeof self[msg.data.label] == 'function'){

                self[msg.data.label](msg).then(ret => {

                    self.postMessage(['answer', msg.data.id, ret]);

                });

            }

        }

    },

    isValidJwt: function(jwt){

        if(!jwt) return false;

        var jwtSplit = jwt.toString().split('.');

        if(jwtSplit.length != 3) return false;

        try{

            var parsed = JSON.parse(atob(jwtSplit[1]));

            if(parsed.jti) return true;

        } catch(e){
            return false;
        }

        return true;

    },

    // @todo Colocar alertas
    timingDelay: function(){

        return new Promise((resolve, reject) => {

            setTimeout(resolve, 10);

        });

    },

    populateWithLoadingElms: function(elm, howMuch, delay){

        if(typeof howMuch == 'undefined') howMuch = 4;
        if(typeof delay   == 'undefined') delay   = 300;

        for(var i = 0; i < howMuch; i++){

            var fakeElm = $('<div class="fake"></div>');

            fakeElm.css({
                animationDelay: delay * i + 'ms'
            });

            elm.append(fakeElm);

        }

    },

    _assets: {
        animationSVG: '<svg width="20" height="18" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><circle cx="50" cy="50" fill="none" stroke="#ec538b" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138" transform="rotate(338.201 50 50)">  <animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;360 50 50" keyTimes="0;1"></animateTransform></circle></svg>',
        animationSVGWidth30: '<svg width="30" height="30" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><circle cx="50" cy="50" fill="none" stroke="#ec538b" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138" transform="rotate(338.201 50 50)">  <animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;360 50 50" keyTimes="0;1"></animateTransform></circle></svg>',
        bigAnimationSVG: '<svg class="loading" width="300" height="300" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><circle cx="50" cy="50" fill="none" stroke="#6e8a9b" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138" transform="rotate(338.201 50 50)">  <animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;360 50 50" keyTimes="0;1"></animateTransform></circle></svg>'
    },

    isLoading: function(btn){

        return !!$(btn).find('.loading-fill').length;

    },

    // @description O mesmo que loadingBtn porém desativa o uso do botão até a função
    // reset ser chamada novamente
    loading: function(btn){

        $(btn).prop('disabled', true);

        $(btn).find('.loading-fill').remove();

        $(btn).prepend('<span class="loading-fill">' + Helpers._assets.animationSVG + '</span>');

        $(btn).addClass('loading-state-pending');

        let obj = {

            reset: function(){

                $(btn).prop('disabled', false);
                $(btn).find('.loading-fill').remove();

                $(btn).removeClass('loading-state-pending');

            }

        };

        // Turn reset as a function of the button
        $(btn).data('reset', obj.reset);

        return obj; 

    },

    // @deprecated
    loadingBtn: function(btn){

        return Helpers.loading(btn);

    },

    loadingCenter: function(elm, promise){

        let loading = $('<div class="loading-center">' + Helpers._assets.animationSVGWidth30 + '</div>');

        $(elm).addClass('totally-disabled');

        $(elm).find('.loading-fill').remove();

        elm.append(loading);

        let states = {

            reset: function(){

                $(elm).removeClass('totally-disabled');
                $(elm).find('.loading-fill').remove();
                loading.remove();

            }

        }

        if(promise && promise.then) promise.then(states.reset);

        return states;

    },

    loadingState: function(btn, promise){

        let originalContent = btn.css('opacity', 0);

        let loadingBtn = $('<span class="loading-state-fill">' + Helpers._assets.animationSVGWidth30 + '</span>');

        $(btn).before(loadingBtn);
        $(btn).addClass('loading-state-pending');

        loadingBtn.css({
            top: btn.offset().top + ($(btn).height() / 4),
            left: btn.offset().left + ($(btn).width() / 4)
        });

        let loading = {

            reset: function(){

                loadingBtn.remove();

            }

        }

        promise.then(function(){

            loading.reset();
            btn.css('opacity', 1);
            btn.removeClass('loading-state-pending');

        });

    },

    // @todo Fazer mais comentários
    calcMediaQuery: function(w, o){

        var final = 0;

        Object.keys(o).sort(function(a, b){

            return Number(a) - Number(b);

        }).reverse().forEach(function(v, k){

            if(k === 0){
                final = o[v];
            }

            if(v >= w) final = o[v];

        });

        return final;

    },

    gridAppend: function(parent, child, opts){

        if(typeof child == 'undefined') return console.error('Child arg required');

        if(typeof opts === 'undefined'){

            opts = {
                640:     1,
                1000:    2,
                9999999999999999: 3
            }

        }

        if(!opts.default) opts.default = 3;

        var width = $(document).width();

        var columns     = [];
        var gridK       = 0;

        // Checa se possui grid
        var gridColumns = $(parent).find('.grid-columns');

        var columnCount = opts.default;

        // Se não tem esse grid, vamos criar
        if(!gridColumns.length){

            columnCount = Helpers.calcMediaQuery(width, opts);

            for(var i = 0; i < columnCount; i++){

                columns.push($('<div class="grid-columns"></div>'));

                var gridWidth = (100 - columnCount) / columnCount;

                columns[i].css('width', gridWidth + '%');
                columns[i].css('margin-right', '1%');

                $(parent).append(columns[i]);

            }

        } else{

            for(var i = 0; i < gridColumns.length; i++){

                columns.push($(gridColumns[i]));

                gridK += columns[i].children().length;

            }

        }

        child.attr('data-tabindex', gridK);

        if(columnCount == 1){

            $(parent).append(child);

        } else{

            columns[gridK % columns.length].append(child);

        }

    },

    // Separa um array em dias(Helpers.getBeautifulDate)
    // @sintaticalSugar
    separateByDay: function(items, prop, inOrder){

        // Usa uma função alternativa
        return Helpers.separateBy(items, prop, Helpers.getBeautifulDate);

    },

    // Formata um texto tipo "2024-08-08" para um texto "08 de Agosto de 2024"
    getDateByString(dateString) {

        const [year, month, day] = dateString.split('-');
        const dateObj = new Date(year, month - 1, day); 
        
        const options = { 
            year: 'numeric', 
            month: 'long', 
            day: 'numeric' 
        };
        
        return dateObj.toLocaleDateString('pt-BR', options);

    },

    // Separa um array em dias(Helpers.getBeautifulDate)
    separateBy: function(items, prop, f){

        // @todo Verificar se essa função fica melhor nesse estilo
        // pois dessa maneira, qualquer função pode ser usada para 
        // separação de arrays
        if(typeof f === 'undefined') f = Helpers.getBeautifulDate;

        // Guarda os agrupamentos de itens, nesta função nativa para dias
        var days = {};

        // Itera entre os itens
        items.forEach(function(item){

            // Pega a propriedade principal do item
            var value    = item[prop];

            // Pega o texto que determina o dia atual
            var dayValue = f(value);

            // Casonão exista essa propriedade dentro do objeto, vamos cria-la
            if(typeof days[dayValue] === 'undefined') days[dayValue] = [];

            // Adicionamos o item atual a essa chave dayValue
            days[dayValue].push(item);

        });

        // Retorno do resultado
        return days;

    },

    subtract: function(a, b){

        var ret = "";

        var theKeys = {};

        a.split("\n").forEach(line => {

            var lineColumns = line.split("\t");

            var key = lineColumns[0].trim().toUpperCase() + '-' + lineColumns[1].trim().toUpperCase() + '-' + lineColumns[3].trim();

            theKeys[key] = lineColumns[4];

        });

        b.split("\n").forEach(line => {

            var lineColumns = line.split("\t");

            var key = lineColumns[0].trim().toUpperCase() + '-' + lineColumns[1].trim().toUpperCase() + '-' + lineColumns[3].trim();

            if(theKeys[key]){
            
                theKeys[key] -= lineColumns[4]

            } else{

                theKeys[key] = lineColumns[4];

            }

        });

        Object.keys(theKeys).forEach(function(key){

            var keyVal = theKeys[key];

            if(keyVal !== 0){

                ret += key.replace(/\-/g, "\t") + "\t\t" + keyVal + "\n";

            }

        });

        return ret;

    },

    sum: function(a, b){

        var ret = "";

        var theKeys = {};

        a.split("\n").forEach(line => {

            var lineColumns = line.split("\t");

            var key = lineColumns[0].trim().toUpperCase() + '-' + lineColumns[1].trim().toUpperCase() + '-' + lineColumns[3].trim();

            theKeys[key] = lineColumns[4];

        });

        b.split("\n").forEach(line => {

            var lineColumns = line.split("\t");

            var key = lineColumns[0].trim().toUpperCase() + '-' + lineColumns[1].trim().toUpperCase() + '-' + lineColumns[3].trim();

            if(theKeys[key]){
            
                theKeys[key] += lineColumns[4]

            } else{

                theKeys[key] = lineColumns[4];

            }

        });

        Object.keys(theKeys).forEach(function(key){

            var keyVal = theKeys[key];

            if(keyVal !== 0){

                ret += key.replace(/\-/g, "\t") + "\t\t" + keyVal + "\n";

            }

        });

        return ret;

    },

    // @template
    // Helpers.forEachPromise(items, function(item){
    // 
    //     return Orders.toPicking(item);
    // 
    // });
    forEachPromise: function(arr, callback){

        if(!arr || arr.length == 0) return Promise.resolve();

        return new Promise(function(resolve, reject){

            var index = 0;

            var tick = function(){

                if(typeof arr[index] === 'undefined'){

                    return resolve();

                }

                callback(arr[index]).then(function(){

                    index++;

                    tick();

                });

            }

            tick();

        });

    },
    
    brightness(color){
        const hex = color.replace('#', '');
        const c_r = parseInt(hex.substring(0, 0 + 2), 16);
        const c_g = parseInt(hex.substring(2, 2 + 2), 16);
        const c_b = parseInt(hex.substring(4, 4 + 2), 16);
        const brightness = ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
        return brightness;
    },

    isBright(color){
        const hex = color.replace('#', '');
        const c_r = parseInt(hex.substring(0, 0 + 2), 16);
        const c_g = parseInt(hex.substring(2, 2 + 2), 16);
        const c_b = parseInt(hex.substring(4, 4 + 2), 16);
        const brightness = ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
        return brightness > 155;
    },

    searchArr: function(arr, opts){

        return arr.filter(function(item){

            var ret = false;

            if(!opts.search) return true;

            opts.columns.forEach(function(column){

                // Caso exista algum termo encontrado
                if(item[column] && ~item[column].toLowerCase().indexOf(opts.search)){

                    ret = true;

                }

            });

            return ret;

        });

    },

    // @version 2.0
    isToday: function(date){

        date = new Date(date);

        return date.toDateString() === new Date().toDateString();

    },

    cloneObject: function(obj){

        return JSON.parse(JSON.stringify(obj));

    },

    component: function(qs, obj){

        var elm = $('.components').find(qs).clone();

        elm.find('[data-value]').each(function(){

            var propName = $(this).attr('data-value');

            var val = obj[propName];

            // @todo Verificar o tipo do elemento, se for do tipo input, devemos adicionar
            // o valor por val ao invés de html

            if(val) $(this).html(val);

        });

        return elm;

    },

    vars: {
    
        months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
        monthsShort: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],

    },

    // Retorna a quantidade de segundos, minutos ou horas que se passaram
    timePassed(time){

        var now = new Date(time || new Date().getTime());

        var diff = new Date().getTime() - now.getTime();

        var seconds = Math.floor(diff / 1000);

        var minutes = Math.floor(seconds / 60);

        var hours = Math.floor(minutes / 60);

        if(seconds < 60) return seconds + ' segundo' + (seconds > 1 ? 's' : '');

        if(minutes < 60) return minutes + ' minuto' + (minutes > 1 ? 's' : '');

        return hours + ' hora' + (hours > 1 ? 's' : '');

    },

    getBeautifulDate: function(unixtime){

        if(typeof unixtime === 'undefined') unixtime = new Date().getTime();

        var date = new Date(unixtime);

        return date.getDate() + ' de ' + Helpers.vars.months[date.getMonth()];

    },

    similarity: function(s1, s2){

        var longer  = s1;
        var shorter = s2;

        if(s1.length < s2.length){
            longer  = s2;
            shorter = s1;
        }

        var longerLength = longer.length;

        if(longerLength == 0){
            return 1.0;
        }

        return (longerLength - Helpers.levenshteinDistance(longer, shorter)) / parseFloat(longerLength);

    },

    // @deprecated
    levenshteinDistance: function(s1, s2){

        s1 = s1.toString().toLowerCase();
        s2 = s2.toString().toLowerCase();

        var costs = new Array();

        for(var i = 0; i <= s1.length; i++){

            var lastValue = i;
            
            for(var j = 0; j <= s2.length; j++){

                if (i == 0) costs[j] = j;
                else {

                    if (j > 0) {

                        var newValue = costs[j - 1];

                        if (s1.charAt(i - 1) != s2.charAt(j - 1)) newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1;
                        
                        costs[j - 1] = lastValue;
                        lastValue = newValue;

                    }

                }

            }

            if (i > 0) costs[s2.length] = lastValue;

        }

        return costs[s2.length];

    },

    decodeUtf: function(string){

        try{

            return decodeURIComponent(escape(string))

        } catch(e){

            return string;

        }

    },

    csvColumns: function(choseColumns){

        return Helpers.choseColumnsExcelImport(choseColumns).then(function(ret){

            var columns = Object.keys(ret.columns);
            var aux     = [];

            ret.parsed.forEach(function(item){

                var obj = {};

                columns.forEach(function(column){

                    obj[column] = item[ret.columns[column]];

                });

                aux.push(obj);

            });

            return {
                info: ret.fileInfo,
                columns: ret.columns,
                parsed: aux
            };

        });

    },

    csvValues: function(choseColumns){

        return Helpers.choseColumnsExcelImport(choseColumns).then(function(ret){

            var columns = Object.keys(ret.columns);
            var aux     = [];

            ret.parsed.forEach(function(item){

                var line = [];

                columns.forEach(function(column){

                    line.push(item[ret.columns[column]]);

                });

                aux.push(line);

            });

            return {
                info: ret.fileInfo,
                columns: ret.columns,
                parsed: aux
            };

        });

    },

    choseColumnsExcelImport: function(choseColumns){

        return new Promise(function(resolve, reject){

            Helpers.importExcel().then(function(imported){

                var parsed = imported.data;

                var finalList = {};

                var columns = imported.columns;

                var toChoseColumns = choseColumns;

                // Adiciona o ignorar na frente
                toChoseColumns.unshift('Ignorar');

                Alerts.open('.chose-columns', {

                    ready: function(modal){

                        var example = modal.find('.example');

                        var firstLine = $('<p></p>');

                        columns.forEach(function(column){

                            firstLine.append('<span>' + column + '</span>');

                        });

                        example.append(firstLine);

                        for(var i = 0; i < 4; i++){

                            var exampleLine = $('<p></p>');

                            if(!parsed[i]) continue;

                            columns.forEach(function(columnName){

                                var column = parsed[i][columnName];

                                var columnElm = $('<span>' + column + '</span>');

                                exampleLine.append(columnElm);

                            });

                            example.append(exampleLine);

                        }

                        // Ao clicar em importar
                        modal.find('.save').on('click', function(){

                            // Guarda a lista das colunas selecionadas
                            var selectedColumns = {};

                            // Vamos passar por cada select
                            modal.find('.list select').each(function(){

                                // E ignorar, caso esteja selecionado para ignorar a coluna
                                if($(this).val() == 'Ignorar') return;

                                selectedColumns[$(this).val()] = $(this).attr('data-label');

                            });

                            resolve({
                                columns: selectedColumns,
                                parsed: parsed,
                                fileInfo: imported.fileInfo
                            });

                            Alerts.close(modal);

                        });

                        modal.find('.how-many').text(parsed.length);

                        columns.forEach(function(column){

                            var options = '';

                            toChoseColumns.forEach(function(toChose){

                                var attrs = '';

                                // @test, @tested 2:51, 5-fev-2020
                                // @bug
                                if(Helpers.similarity(column, toChose) >= .9){

                                    attrs += 'selected';

                                }

                                options += '<option ' + attrs + '>' + toChose + '</option>';

                            });

                            modal.find('.list').append('\
                                <p>\
                                    <label>A coluna ' + column + ' equivale a </label>\
                                    <select data-label="' + column + '">\
                                    ' + options + '\
                                    </select>\
                                </p>\
                                ')

                        });

                        Alerts.centralize(modal);

                    }

                });

            });

        });

    },

    readFile: function(opts){

        return Helpers.importFile(opts).then(function(file){

            return Helpers.getContent(file);

        });

    },

    importFile: function(opts){

        if(typeof opts == 'undefined'){

            opts = {};

        }

        if(typeof opts.identifier == 'undefined'){

            opts.identifier = '';

        }

        if(!opts.multiple){

            opts.multiple = false;
            
        }

        if(opts.identifier){

            $('<input data-identifier="' + opts.identifier + '" type="file">').remove();

        }

        return new Promise(function(resolve, reject){

            let attrs = '';

            if(opts.multiple) attrs += 'multiple';

            // Vamos criar esse elemento, com o determinado id, para que possamos
            // identifica-lo na próxima iteração
            var fileElm = $('<input data-identifier="' + opts.identifier + '" ' + attrs + ' type="file">');

            // Quando houver uma mudança, como a adiçao de um arquivo
            fileElm.on('change', function(){
                
                var files = fileElm.get(0).files;

                var ret = files;

                if(!opts.multiple) ret = files[0];

                resolve(ret);

                fileElm.remove();

            });

            fileElm.click();

        });

    },

    importExcel: function(fileId, callback){

        if(typeof callback === 'undefined') callback = function(){};

        return new Promise(function(resolve, reject){

            // Caso o fileId não esteja definido
            if(typeof fileId === 'undefined'){

                // Vamos criar um id para ele
                fileId = uuid();

                // Remove este elemento, caso já exista
                $('input[data-file-id="' + fileId + '"]').remove();

                // Vamos criar esse elemento, com o determinado id, para que possamos
                // identifica-lo na próxima iteração
                var fileElm = $('<input data-file-id="' + fileId + '" type="file">');

                fileElm.css({
                    display: 'none'
                })

                // Quando houver uma mudança, como a adiçao de um arquivo
                fileElm.on('change', function(){

                    Helpers.importExcel(fileId, function(imported){

                        var columns = imported.columns;
                        var data    = imported.data;

                        var file = fileElm.get(0).files[0];

                        var fileInfo = {
                            name:         file.name,
                            size:         file.size,
                            lastModified: file.lastModified
                        }

                        data.fileInfo = fileInfo;

                        resolve({
                            data: data,
                            columns: columns,
                            fileInfo: fileInfo
                        });

                        fileElm.remove();

                    });

                });

                $('body').append(fileElm);

                fileElm.click();

            } else{

                var file = $('input[data-file-id="' + fileId + '"]').get(0);

                Helpers.parseExcelRows(file.files[0]).then(callback);

            }

        });

    },

    htmlToMarkdown: function(html){

        var converter = new showdown.Converter();

        return converter.makeMarkdown(html);

    },

    format(description, type){

        if(type == 'markdown' || type == 'markdowncopy'){

            description = description.replace(/\n/g, '<br>');

            let formatted = Helpers.htmlToMarkdown(description);

            // Formata corretamente os links que vierem do converter.makeMarkdown
            formatted = formatted.replace(/\[(.*?)\]\(\<(.*?)\>\)/g, '[$1]($2)');

            formatted = formatted.replace(/\<\/(.*?)\>/g, '&lt;$1&gt;');

            formatted = formatted.replace(/\n/g, '<br>');

            // Impede que quebras de linha se repitam

            formatted = formatted.replace(/\n/g, '<br>');

            // Se tiver um <br> ou inumeros
            formatted = formatted.replace(/(<br>){2,}/g, '<br><br>');

            return formatted;

        }

        if(type == 'html'){

            formatted = description.replace(/<(.*?)>/g, '&lt;$1&gt;');

            return formatted;

        }

        if(type == 'htmlcopy'){

            return description;

        }

        if(type == 'text'){

            formatted = description;

            return formatted;

        }

        if(type == 'textcopy'){

            formatted = description.replace(/<(.*?)>/g, '');

            return formatted;

        }

        return description;

    },

    copyToClipboard: function(text){

        var aux = document.createElement("textarea");

        aux.value = text;

        document.body.appendChild(aux);

        aux.select();

        document.execCommand("copy");

        document.body.removeChild(aux);

    },

    // @todo Entender essa função, que foi basicamente copiar e colar
    parseXml: function(xml, arrayTags){

        var dom = null;
        if (window.DOMParser)
        {
            dom = (new DOMParser()).parseFromString(xml, "text/xml");
        }
        else if (window.ActiveXObject)
        {
            dom = new ActiveXObject('Microsoft.XMLDOM');
            dom.async = false;
            if (!dom.loadXML(xml))
            {
                throw dom.parseError.reason + " " + dom.parseError.srcText;
            }
        }
        else
        {
            throw "cannot parse xml string!";
        }

        function isArray(o)
        {
            return Object.prototype.toString.apply(o) === '[object Array]';
        }

        function parseNode(xmlNode, result)
        {
            if (xmlNode.nodeName == "#text") {
                var v = xmlNode.nodeValue;
                if (v.trim()) {
                   result['#text'] = v;
                }
                return;
            }

            var jsonNode = {};
            var existing = result[xmlNode.nodeName];
            if(existing)
            {
                if(!isArray(existing))
                {
                    result[xmlNode.nodeName] = [existing, jsonNode];
                }
                else
                {
                    result[xmlNode.nodeName].push(jsonNode);
                }
            }
            else
            {
                if(arrayTags && arrayTags.indexOf(xmlNode.nodeName) != -1)
                {
                    result[xmlNode.nodeName] = [jsonNode];
                }
                else
                {
                    result[xmlNode.nodeName] = jsonNode;
                }
            }

            if(xmlNode.attributes)
            {
                var length = xmlNode.attributes.length;
                for(var i = 0; i < length; i++)
                {
                    var attribute = xmlNode.attributes[i];
                    jsonNode[attribute.nodeName] = attribute.nodeValue;
                }
            }

            var length = xmlNode.childNodes.length;
            for(var i = 0; i < length; i++)
            {
                parseNode(xmlNode.childNodes[i], jsonNode);
            }
        }

        var result = {};
        for (var i = 0; i < dom.childNodes.length; i++)
        {
            parseNode(dom.childNodes[i], result);
        }

        return result;

    },

    jsonToRelacional: function(json){

        var columns = Object.keys(json[0]);

        var relacional = [];

        json.forEach(function(item){

            var line = []

            columns.forEach(function(column){

                line.push(item[column]);

            });

            relacional.push(line);

        });

        return relacional;

    },

    // @deprecated Usar makeExcel
    exportJsonToCsv: function(json, name){

        var toExport = Helpers.jsonToRelacional(json);

        var csvContent = "data:text/csv;charset=utf-8,";

        if(typeof name === 'undefined'){

            name = 'Sem titulo.csv';

        }

        var n = 0;

        toExport.forEach(function(rowArray){

            n++;

            var row = rowArray.join(";");
            csvContent +=row + "\r\n";

        });

        var encodedUri = encodeURI(csvContent);

        var link = document.createElement("a");

        link.setAttribute("href", encodedUri);
        link.setAttribute("download", name + '.csv');

        document.body.appendChild(link);

        link.click();

    },

    download(url, name){

        var link = document.createElement("a");

        link.setAttribute("href", url);
        link.setAttribute("download", name);

        document.body.appendChild(link);

        link.click();

    },

    sanitize: (str) => {

        // Se acharmos o texto Funcionalidades:, devemos quebrar a linha e torna-lo h2

        // str = str.replaceAll('Funcionalidades:', '<h3>Funcionalidades:</h3>');

        // str.split('\n').forEach((line, i) => {

        //     if(line.toLowerCase().indexOf('Functionalidades:') != -1){

        //         str = str.replace(line, '<h2>' + line + '</h2>');

        //     }

        // });

        // str = str.replaceAll('<p></p>', '');

        // str = str.replaceAll('<p></p>', '<p>&nbsp;</p>');



        // console.log(str);

        return str;


    },

    ext: function(filename){

        if(!~filename.indexOf('.')) return '';

        return filename.split('.').pop();

    },

    guessApi(obj){

        var api = '';

        // @todo Verificar, pois essa linha aqui deprecia tudo abaixo
        if(obj._api) return obj._api;

        if(obj.Id && typeof obj.BrandId != 'undefined' && obj.Name){

            api = 'vtex';

        } else if(typeof obj.cest != 'undefined' && obj.condicao && obj.descricao && typeof obj.codigo != 'undefined'){

            api = 'bling';

        } else if(typeof obj.requires_shipping != 'undefined' && typeof obj.handle != 'undefined' && obj.canonical_url && obj.variants){
            
            api = 'nuvemshop';
            
        } else if(typeof obj.category_default != 'undefined' && typeof obj.category_default_id != 'undefined' && typeof obj.slug != 'undefined' && typeof obj.max_quantity != 'undefned'){

            api = 'bagy';

        } else if(typeof obj.Product != 'undefined' && typeof obj.Product.all_categories != 'undefined'){
            
            api = 'tray';
            
        } else if(typeof obj.preco_promocional != 'undefined' && typeof obj.unidade_por_caixa != 'undefined' && typeof obj.preco_custo_medio != 'undefined' && typeof obj.anexos != 'undefined'){

            api = 'tiny';

        } else if(typeof obj.apelido != 'undefined' && typeof obj.categorias != 'undefined' && typeof obj.resource_uri != 'undefined' && typeof obj.grades != 'undefined'){
            
            api = 'lojaintegrada';
            
        } else if(typeof obj.fotos != 'undefined' && typeof obj.outras_descricoes != 'undefined' && typeof obj.categoria_metaabstract != 'undefined' && typeof obj.categoria_produto != 'undefined'){

            api = 'openk';

        } else {

            console.error('Api não identificada');
        }

        return api;
    },

    wait(ms){
            
        return new Promise(resolve => setTimeout(resolve, ms));

    },

    toExcel: function(json, name){

        let table = [Object.keys(json[0])];

        table = table.concat(Helpers.jsonToRelacional(json));

        Helpers.makeExcel(table, name);

    },

    showTable(table, name){

        let modal = Alerts.ok(name);

        let columns = Object.keys(table[0]);

        let tableElm = $('<table></table>');

        let thead = $('<thead></thead>');

        let tbody = $('<tbody></tbody>');

        tableElm.append(thead);

        tableElm.append(tbody);

        $(modal).find('.content').append(tableElm);

        columns.forEach((column) => {

            thead.append('<th>' + column + '</th>');

        });

        table.forEach((row) => {

            let tr = $('<tr></tr>');

            columns.forEach((column) => {

                tr.append('<td>' + row[column] + '</td>');

            });

            tbody.append(tr);

        });

        $(modal).find('.dialog-body').append(tableElm);

        console.log(table);
        
    },

    downloadTable(table, name){

        let columns = [];
        let rows    = [];

        for(let i = 0; i < table.length; i++){

            let row = table[i];

            let itemColumns = Object.keys(row);

            let itemRow = [];

            itemColumns.forEach((column) => {

                let columnName = I18n.get(column);

                if(columns.indexOf(columnName) == -1) columns.push(columnName);

                // Se for array, vamos transformar em json
                if(typeof row[column] == 'object') row[column] = JSON.stringify(row[column]);

                itemRow.push(row[column]);

            });

            rows.push(itemRow);

        }

        let finalTable = [columns].concat(rows);

        Helpers.makeExcel(finalTable, name);

    },

    makeExcel: function(json, name, sheetName){

        var table = Helpers.jsonToRelacional(json);

        var ws_name = sheetName || "cadastra.ai";

        var wb = XLSX.utils.book_new();
        var ws = XLSX.utils.aoa_to_sheet(table);

        XLSX.utils.book_append_sheet(wb, ws, ws_name);

        XLSX.writeFile(wb, name);

    },

    debounceTimeout: null,

    debounce: function(delay){

        return new Promise((resolve) => {

            if (Helpers.debounceTimeout) {
                clearTimeout(Helpers.debounceTimeout);
            }

            Helpers.debounceTimeout = setTimeout(() => resolve(), delay);

        });

    },

    readExcel: function(file) {
        return new Promise(function(resolve, reject) {
            var reader = new FileReader();
    
            reader.onload = function(e) {
                var data = e.target.result;
    
                var workbook = XLSX.read(data, {
                    type: 'binary'
                });
    
                var sheet_name_list = workbook.SheetNames;
    
                // Converte a planilha para um array de arrays, preservando todas as colunas
                var raw_data = XLSX.utils.sheet_to_json(workbook.Sheets[sheet_name_list[0]], {
                    header: 1 // Retorna as linhas como arrays
                });
    
                // Extrai os cabeçalhos (primeira linha)
                var headers = raw_data[0];

                raw_data = raw_data.filter(row => row.length > 0); // Elimina as linhas totalmente vazias

                // Processa as linhas para garantir que todas as colunas sejam mantidas
                var json = raw_data.slice(1).map(function(row) {
                    var obj = {};
                    headers.forEach(function(header, i) {
                        obj[header || `Column${i + 1}`] = row[i] !== undefined ? row[i] : ''; // Adiciona a célula ou uma string vazia
                    });
                    return obj;
                });
    
                resolve(json);
            };
    
            reader.onerror = reject;
    
            reader.readAsBinaryString(file);
        });
    },

    // @dependency https://github.com/SheetJS/sheetjs
    parseExcel: function(file){

        // Pega o binário do arquivo
        return Helpers.getBinary(file).then(function(data){

            // Lê o arquivo pelo script
            var workbook = XLS.read(data, {
                type: 'binary',
                raw: true
            });

            var result = {};

            // Passa por cada aba
            workbook.SheetNames.forEach(function(sheetName){

                // Aqui, o arquivo é convertido para json
                var roa = XLS.utils.sheet_to_json(workbook.Sheets[sheetName], {
                    header: 1
                });

                // Caso haja resultado, vamos salvar
                if(roa.length){
                    result[sheetName] = roa;
                }

            });

            // Retorna o json
            return result;

        });

    },

    pagedArray(page, perPage, array){

        var pageItems = [];

        var total = Math.ceil(array.length / perPage);
        var start = (page-1) * perPage;
        var end   = page * perPage;

        array.forEach(function(item, k){

            if(k >= start && k < end) pageItems.push(item)

        });

        if(!pageItems.length){

            return false;

        }
        
        return pageItems;

    },

    parseExcelRows: function(file, sw){

        // if(!opts) opts = {}

        // if(typeof opts.sw === 'undefined'){
        //     opts.sw = true;
        // }

        // // @todo Permite 
        // if(typeof opts.multiple === 'undefined'){
        //     opts.multiple = true;
        // }

        if(!isServiceWorker && Default.bc && sw){

            return Default.toSw('Helpers.parseExcelRows', [file]);

        }

        return Helpers.parseExcel(file).then(function(parsed){

            var rows    = parsed[Object.keys(parsed)[0]];
            var columns = [];
            var data    = [];

            rows.forEach(function(row, k){

                // Caso seja a linha 1, vamos interpretar como os nomes das colunas
                if(k === 0){

                    for(var i = 0; i < row.length; i++){

                        var val = row[i];

                        if(!val){

                            val = '(sem valor)';

                        }

                        columns.push(val);

                    }

                    return;

                }

                var toPush = {};

                row.forEach(function(col, k){

                    toPush[columns[k]] = Helpers.decodeUtf(col);

                });

                data.push(toPush);

            });

            return {
                columns: columns,
                data:    data
            };

        });

    },

    parseStockExcelRows: function(file, opts){

        if(!opts) opts = {}

        if(typeof opts.sw === 'undefined'){
            opts.sw = true;
        }

        // @todo Permite 
        if(typeof opts.multiple === 'undefined'){
            opts.multiple = true;
        }

        if(!isServiceWorker && Default.bc && opts.sw){

            return Default.toSw('Helpers.parseExcelRows', [file]);

        }

        return Helpers.parseExcel(file).then(function(parsed){

            var rows    = parsed[Object.keys(parsed)[0]];
            var columns = [];
            var data    = [];

            rows.forEach(function(row, k){

                // Caso seja a linha 1, vamos interpretar como os nomes das colunas
                if(k === 0){

                    // Caso tenha 5 colunas e seja 
                    if(row.length == 5 && !isNaN(row[row.length-1])){

                        columns.push('Endereço');
                        columns.push('Ean');
                        columns.push('Sku');
                        columns.push('Nome');
                        columns.push('Quantidade');

                    } else{

                        for(var i = 0; i < row.length; i++){

                            var val = row[i];

                            if(!val){

                                val = '(sem valor ' + i + ')';

                            }

                            columns.push(val);

                        }

                        return;

                    }

                }

                var toPush = {};

                row.forEach(function(col, k){

                    toPush[columns[k]] = Helpers.decodeUtf(col);

                });

                data.push(toPush);

            });

            return {
                columns: columns,
                data:    data
            };

        });

    },

    getBase64: function(file){

        return new Promise(function(resolve, reject){

            var reader = new FileReader();
            
            reader.readAsDataURL(file);
            
            reader.onload = function () {
                resolve(reader.result);
            };
            
            reader.onerror = reject;

        });

    },

    getBinary: function(file){

        return new Promise(function(resolve, reject){

            var reader = new FileReader();
            
            reader.readAsBinaryString(file);
            
            reader.onload = function () {
                resolve(reader.result);
            };
            
            reader.onerror = reject;

        });

    },

    getContent: function(file){

        return new Promise(function(resolve, reject){

            var reader = new FileReader();
            
            reader.readAsText(file);
            
            reader.onload = function () {
                resolve(reader.result);
            };
            
            reader.onerror = reject;

        });

    },

    removeItem: function(arr, item){

        for( var i = 0; i < arr.length; i++){ 
           if ( arr[i] === item) {
             arr.splice(i, 1); 
           }
        }

    },

    arrRemove: function(arr, item){

        for( var i = 0; i < arr.length; i++){ 
           if ( arr[i] === item) {
             arr.splice(i, 1); 
           }
        }

    },

    /**
     * Quando determinado elemento estiver no html atual
     * @param  {[type]} querySelector Query selector do elemento a ser testado
     * @param  {[type]} unlimited     Caso unlimited seja true, não tem limite de tentativas
     * @return {[type]}               Promise que resolve quando o elemento for encontrado
     */
    whenElm: function (querySelector, unlimited) {

        if (unlimited === "undefined") unlimited = false;

        return new Promise(function (resolve, reject) {

            var maxLoop = 0;

            var timer = setInterval(function () {

                if (!unlimited && maxLoop > 150){
                    clearInterval(timer);
                    console.log('desativad');
                }

                if ($(querySelector).length) {
                    resolve($(querySelector));
                    clearInterval(timer);
                } else {
                    maxLoop++;
                }
            }, 100);

        });
    },

    pendingWhenVar: {},

    /**
     * Quando determinada variavel estiver disponível
     * @param  {String} query A variavel a ser testada
     * @param  {Object} pre   O objeto que guarda a query
     * @return {Promise}      Promise que é resolvida quando a variavel é diferente de undefined
     */
    whenVar: function(query, pre, maxLoop){

        this.pendingWhenVar[query] = {
            pre: pre,
            maxLoop: maxLoop
        };

        if(typeof maxLoop == 'undefined') maxLoop = 150;

        pre = pre || window;

        return new Promise(function(resolve, reject){

            var actualLoop = 0;

            var timer = setInterval(function(){

                if(actualLoop > maxLoop) clearInterval(timer);

                if(typeof pre[query] !== 'undefined'){
                    resolve(pre[query]);
                    clearInterval(timer);
                    delete Helpers.pendingWhenVar[query];
                } else{
                    actualLoop++;
                }
            }, 100);

        });
    },

    capitalize: function(string){
        return string[0].toUpperCase() + string.substr(1);
    },

    standardCatch: function(e){

        Alerts.notify(e);

    },

    getSimpleHour: function(unixtime, outputSeconds){

        if(typeof unixtime === 'undefined') unixtime = new Date().getTime();

        if(typeof outputSeconds === 'undefined') outputSeconds = true;

        var date = new Date(unixtime);

        var hour    = date.getHours();
        var minutes = date.getMinutes();
        var seconds = date.getSeconds();

        if(hour < 10){
            hour = '0' + hour;
        }

        if(minutes < 10){
            minutes = '0' + minutes;
        }

        if(seconds < 10){
            seconds = '0' + seconds;
        }

        if(outputSeconds){
            outputSeconds = ':' + seconds;
        } else{
            outputSeconds = '';
        }

        return hour + ':' + minutes + outputSeconds;

    },

    getDbSimpleDate: function(unixtime, db){

        if(typeof unixtime === 'undefined'){
            unixtime = new Date().getTime();
        }

        var date = new Date(unixtime);

        var year  = date.getFullYear();
        var month = date.getMonth() + 1;
        var day   = date.getDate();

        if(month < 10){
            month = '0' + month;
        }

        if(day < 10){
            day = '0' + day;
        }

        var offset = date.getTimezoneOffset() / 60;

        if(offset < 10){
            offset = '0' + offset;
        }

        return year + '-' + month + '-' + day + 'T' + Helpers.getSimpleHour(unixtime) + '-' + offset + ':00';

    },

    getLightDate: function(unixtime, delimiter){

        if(typeof unixtime === 'undefined'){
            unixtime = new Date().getTime();
        }

        if(typeof delimiter === 'undefined'){
            delimiter = '-';
        }

        var date = new Date(unixtime);

        var year  = date.getFullYear();
        var month = date.getMonth() + 1;
        var day   = date.getDate();

        if(month < 10){
            month = '0' + month;
        }

        if(day < 10){
            day = '0' + day;
        }

        var offset = date.getTimezoneOffset() / 60;

        if(offset < 10){
            offset = '0' + offset;
        }

        return day + delimiter + month + delimiter + year;

    },

    downloadBuffer: function(buffer, filename){

        var blob = new Blob([new Uint8Array(buffer.data, 0, buffer.data.length)]);

        var link  = document.createElement('a');
        
        link.href = window.URL.createObjectURL(blob);

        link.download = filename;

        link.click();

    },

    getSimpleDate: function(unixtime, db){

        if(typeof unixtime === 'undefined'){
            unixtime = new Date().getTime();
        }

        if(typeof db === 'undefined'){

            db = false;

        }

        var date = new Date(unixtime);

        var year  = date.getFullYear();
        var month = date.getMonth() + 1;
        var day   = date.getDate();

        if(month < 10){
            month = '0' + month;
        }

        if(day < 10){
            day = '0' + day;
        }

        var offset = date.getTimezoneOffset() / 60;

        if(offset < 10){
            offset = '0' + offset;
        }

        var middleOne = db?'T':' ';

        return year + '-' + month + '-' + day + middleOne + Helpers.getSimpleHour(unixtime) + '-' + offset + ':00';

    },

    validateDocument: function(doc) {
        doc = doc.replace(/\D/g, ''); // Regex para pegarmos apenas números

        if (doc.length == 14) return Helpers.validateCNPJ(doc);
        else if (doc.length == 11) return Helpers.validateCPF(doc);
    },

    maskDocument: function(doc){

        if (doc.length == 14) doc = Helpers.toCnpj(doc);
        else if (doc.length == 11) doc = Helpers.toCpf(doc);

        return doc;

    },

    // @alias de this.maskDocument
    toDoc: function(doc){

        if (doc.length == 14) doc = Helpers.toCnpj(doc);
        else if (doc.length == 11) doc = Helpers.toCpf(doc);

        return doc;

    },

    /**
     * Mascara CNPJ
     * @param { jQuery element | DOM element } elm input onde sera aplicada a marcara
     */
    maskCnpj: function (elm) { // @todo renomear para maskCnpjInput

        elm = $(elm);

        var c = elm.val().substr(0, 18);

        c = Helpers.toCnpj(c);

        elm.val(c);

    },

    toCnpj(str) { // @todo renomear para maskCnpj

        let formated = ''

        if(str === null || str === 'null') return null;
        
        formated = str.replace(/\D/g, "");
        formated = formated.replace(/^(\d{2})(\d)/, "$1.$2");
        formated = formated.replace(/^(\d{2})\.(\d{3})(\d)/, "$1.$2.$3");
        formated = formated.replace(/\.(\d{3})(\d{4})(\d)/, ".$1/$2-$3");
    
        return formated;
    

    },

    /**
     * Mascara CPF
     * @param { jQuery element | DOM element } elm input onde sera aplicada a marcara
     */
    maskCpf: function (elm) {

        elm = $(elm);

        var c = elm.val().substr(0, 14);

        c = Helpers.toCpf(c);

        elm.val(c);

    },

    toCpf(str) {

        str = str.replace(/(\D)/g, "");
        str = str.replace(/^(\d{3})(\d)/, "$1.$2");
        str = str.replace(/(\d{3})(\d)/, "$1.$2");
        str = str.replace(/(\d{3})(\d{2})$/, "$1-$2");

        return str;

    },

    toTel(tel){

        if(tel.length == 8){

            return tel.replace(/^(\d{4})(\d)/, "$1-$2");

        }

        return tel;

    },

    /**
     * Mascara RG
     * @param { jQuery element | DOM element } elm input onde sera aplicada a marcara
     */
    maskRg: function (elm) {

        elm = $(elm);

        var c = elm.val();

        if (c.split('').length >= 8) c = c.substr(0, 18);

        c = c.replace(/[^\dXx]/g, "");
        c = c.replace(/^(\d{2})(\d)/, "$1.$2");
        c = c.replace(/(\d{3})(\d)/, "$1.$2");
        c = c.replace(/(\d{3})([\dXx]{1,2})$/, "$1-$2");

        elm.val(c);

    },

    maskPhone(str) {
        let formatted = str.replace(/\D/g, '');

        // Aplica máscara de celular (11 dígitos) no formato (xx) xxxxx-xxxx
        if (formatted.length === 11) {
            formatted = formatted.replace(/^(\d{2})(\d{5})(\d{4})$/, "($1) $2-$3");
        }
        // Aplica máscara de telefone fixo (10 dígitos) no formato (xx) xxxx-xxxx
        else if (formatted.length === 10) {
            formatted = formatted.replace(/^(\d{2})(\d{4})(\d{4})$/, "($1) $2-$3");
        }
    
        return formatted;
    },

    animateTextCounter(element, totalValue) {
        
        $({ currentValue: element.text() })
        .animate({
            currentValue: totalValue
        }, {
            duration: 1000,
            easing: 'linear',
            step: function() {
                element.text(Math.floor(this.currentValue));
            },
            complete: function() {
                element.text(this.currentValue);
        }});
    
    },

    /**
     * Mascara telefone
     * @param { jQuery element | DOM element } elm input onde sera aplicada a marcara
     */
    maskTel: function (elm) {

        elm = $(elm);

        var v = elm.val();

        if (v.split('').length > 15) v = v.substr(0, 15);

        v = v.replace(/\D/g, "");
        v = v.replace(/^(\d{2})(\d)/g, "($1) $2");
        v = v.replace(/(\d)(\d{4})$/, "$1-$2");

        elm.val(v);

    },

    mask: {

        doc: function(doc){

            if (doc.length == 14)
                doc = Helpers.toCnpj(doc);
            else if (doc.length == 11)
                doc = Helpers.toCpf(doc);

            return doc;

        },

        tel: function (txt) {

            if(!txt) return txt;

            if (txt.split('').length > 15) txt = txt.substr(0, 15);

            txt = txt.replace(/\D/g, "");
            txt = txt.replace(/^(\d{2})(\d)/g, "($1) $2");
            txt = txt.replace(/(\d)(\d{4})$/, "$1-$2");

            return txt;

        },

    },

    /**
     * Mascara CEP
     * @param { jQuery element || DOM element } elm input onde sera aplicada a marcara
     */
    maskCep: function (elm) {

        elm = $(elm);

        var v = elm.val();

        if (v.split('').length > 9) v = v.substr(0, 9);

        v = v.replace(/\D/g, "");
        v = v.replace(/^(\d{5})(\d{3})$/g, "$1-$2");

        elm.val(v);

    },

    /**
     * Remove todos os caracteres não numericos de uma máscara
     * @param { String } value String com máscara
     * @returns { String } String sem máscara
     */
    unMask: function (value) {

        return value.replace(/([^0-9])/g, '');

    },

    /**
     * Valida CNPJ
     * @param { String } cnpj CNPJ a ser validado
     * @returns { Boolean } True se CNPJ for valido, false se não
     */
    validateCNPJ: function (cnpj) {

        cnpj = cnpj.replace(/[^\d]+/g, '');

        if (cnpj == '') return false;

        if (cnpj.length != 14) return false;

        // Verifica se todos os dígitos são iguais
        if (/^(\d)\1+$/.test(cnpj)) return false;

        // Valida DVs
        var length = cnpj.length - 2,
            numbers = cnpj.substring(0, length),
            digits = cnpj.substring(length),
            sum = 0,
            pos = length - 7;

        for (i = length; i >= 1; i--) {
        
            sum += numbers.charAt(length - i) * pos--;

            if (pos < 2) pos = 9;

        }

        var result = sum % 11 < 2 ? 0 : 11 - sum % 11;

        if (result != digits.charAt(0)) return false;

        length = length + 1;
        numbers = cnpj.substring(0, length);
        sum = 0;
        pos = length - 7;

        for (i = length; i >= 1; i--) {

            sum += numbers.charAt(length - i) * pos--;

            if (pos < 2) pos = 9;

        }

        result = sum % 11 < 2 ? 0 : 11 - sum % 11;

        if (result != digits.charAt(1)) return false;

        return true;

    },
   
    /**
     * Valida CPF
     * @param { String } cpf CPF a ser validado
     * @returns { Boolean } True se CPF for valido, false se não
     */
    validateCPF(cpf) {
        // Remove caracteres não numéricos
        cpf = cpf.replace(/[^\d]/g, "");
    
        // Verifica se o CPF
        if (cpf.length !== 11) return false;
    
        // Verifica se todos os dígitos são iguais, o que invalida o CPF
        if (/^(\d)\1+$/.test(cpf)) return false;
    
        // Validação do primeiro dígito verificador
        let sum = 0;
        for (let i = 0; i < 9; i++) {
            sum += parseInt(cpf.charAt(i)) * (10 - i);
        }
        let remainder = (sum * 10) % 11;
        if (remainder === 10 || remainder === 11) remainder = 0;
        if (remainder !== parseInt(cpf.charAt(9))) return false;
    
        // Validação do segundo dígito verificador
        sum = 0;
        for (let i = 0; i < 10; i++) {
            sum += parseInt(cpf.charAt(i)) * (11 - i);
        }
        remainder = (sum * 10) % 11;
        if (remainder === 10 || remainder === 11) remainder = 0;
        if (remainder !== parseInt(cpf.charAt(10))) return false;
    
        return true;
    },

    /**
     * Mostra o loader no centro do elemento
     * @param { jQuery element | DOM element } elm Elemento onde o loader será colocado
     * @param { String | Number } size Tamanho opcional do loader
     * @returns { jQuery element } Elemento do loader
     */
    showLoader: function (elm, size) {

        elm = $(elm);
        
        var currentLoader = Helpers.getLoader(elm);

        if (currentLoader.length) return currentLoader;

        var elmPosition = elm.css('position');

        if (elmPosition == 'static')
            elm.css('position', 'relative');
        else if (elmPosition != 'sticky' && elmPosition != 'relative')
            return;

        size = size || 30;

        var top = (elm.outerHeight() / 2) - (size / 2),
            left = (elm.outerWidth() / 2) - (size / 2);

        var loaderSpinner = $('<img src="img/logo-animated.svg" class="loader-spinner">');

        loaderSpinner.css({ 'width': size, 'height': size, 'top': top, 'left': left });

        elm.append(loaderSpinner);

        return loaderSpinner;

    },

    /**
     * Pega o loader no elemento
     * @param { jQuery element | DOM element } elm Elemento contendo o loader
     * @returns { jQuery element } Loader no elemento
     */
    getLoader: function (elm) {

        return $(elm).find('.loader-spinner');

    },

    /**
     * Remove o loader da tela
     * @param { jQuery element | DOM element } loaderElm Loader específico para ser removido
     */
    removeLoader: function (loaderElm) {

        loaderElm = $(loaderElm);

        if (loaderElm.length) {

            if (loaderElm.hasClass('loader-spinner')) loaderElm.remove();

        } else $('.loader-spinner').remove();

    },

    /**
     * Formata um número como uma string no formato de moeda brasileira (R$).
     *
     * @param {number|string} price - O número a ser formatado.
     * @returns {string} A string formatada como moeda brasileira.
     * @throws {Error} Se o valor passado não for um número válido.
     */
    formatPrice: function (price) {

        const priceNumber = Number(price);

        // @todo Indentação
        if (isNaN(priceNumber)) {
        throw new Error(`Cannot format '${price}', not a number`);
        }
    
        return priceNumber.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });

    },

    formatTimestamp: function (timestamp) {
        const date = new Date(timestamp);
        
        const dateOptions = { year: 'numeric', month: '2-digit', day: '2-digit' };
        const timeOptions = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
        
        const formatDate = new Intl.DateTimeFormat('pt-BR', dateOptions);
        const formatTime = new Intl.DateTimeFormat('pt-BR', timeOptions);
        
        const formattedDate = formatDate.format(date);
        const formattedTime = formatTime.format(date);
        
        return { formattedDate, formattedTime };
    },
  

    months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],

    /**
     * Retorna o nome do mês pelo seu número
     * @param { Number } monthNumber Número do mês
     * @returns { String } Nome do mês
     */
    getMonthName(monthNumber) {

        return Helpers.months[monthNumber];

    },

    /**
     * Retorna o número do mês pelo seu nome
     * @param { Number } monthName Nome do mês
     * @returns { String } Número do mês
     */
    getMonthNumber(monthName) {

        return Helpers.months.findIndex(name => name.toLowerCase() == monthName.toLowerCase()) + 1;

    },

    /**
     * Converte RGB para hexadecimal
     * @param { Array } rgbArr Array de RGB
     * @returns { String } Hexadecimal
     */
    convertRgbToHex(rgbArr) {

        return rgbArr.splice(0, 3).reduce((result, color) => {

            color = Number(color).toString(16);
            color = color.length == 1 ? '0' + color : color;
    
            return result + color;
    
        }, '');

    },

    getHigherNumber(number_array) {
        return Math.max(...number_array);
    },

    // Verifica a casa decimal de um número positivo e retorna o resultado em string
    checkDecimalPlace(number) {

        // @todo add validacao da variavel num 
        if (number >= 1000) { 
            return "hundred";
        } else if (number >= 100) {
            return "thousand";
        } else if (number >= 10) {
            return "ten";
        } else if (number <= 10) {
            return "ten";
        }

    },

    // Obtém o valor decimal com base na string da casa decimal
    getDecimalValue(decimalPlace) {
        switch (decimalPlace) {
            case 'hundred':
                return 1000;
            case 'thousand':
                return 100;
            case 'ten':
                return 10;
            default:
                return 0;
        }
    },

    getMonthName(monthNumber) {
        const date = new Date();
        date.setMonth(monthNumber - 1);
      
        return Helpers.capitalize(date.toLocaleString('default', { month: 'long' }));
    },

}

if(isServiceWorker){

    Bc.register('Helpers.parseExcelRows', Helpers.parseExcelRows);

}

// Warn if overriding existing method
if(!Array.prototype.equals){
        
    Array.prototype.equals = function (array) {
        // if the other array is a falsy value, return
        if (!array)
            return false;

        // compare lengths - can save a lot of time 
        if (this.length != array.length)
            return false;

        for (var i = 0, l=this.length; i < l; i++) {
            // Check if we have nested arrays
            if (this[i] instanceof Array && array[i] instanceof Array) {
                // recurse into the nested arrays
                if (!this[i].equals(array[i]))
                    return false;       
            }           
            else if (this[i] != array[i]) { 
                // Warning - two different object instances will never be equal: {x:20} != {x:20}
                return false;   
            }           
        }       
        return true;
    }
    // Hide method from for-in loops
    Object.defineProperty(Array.prototype, "equals", {enumerable: false});
    
}

!function(n){"use strict";function e(){var e=n.crypto||n.msCrypto;if(!f&&e&&e.getRandomValues)try{var r=new Uint8Array(16);s=f=function(){return e.getRandomValues(r),r},f()}catch(n){}if(!f){var o=new Array(16);i=f=function(){for(var n,e=0;e<16;e++)0===(3&e)&&(n=4294967296*Math.random()),o[e]=n>>>((3&e)<<3)&255;return o},"undefined"!=typeof console&&console.warn&&console.warn("[SECURITY] node-uuid: crypto not usable, falling back to insecure Math.random()")}}function r(){if("function"==typeof require)try{var n=require("crypto").randomBytes;c=f=n&&function(){return n(16)},f()}catch(n){}}function o(n,e,r){var o=e&&r||0,t=0;for(e=e||[],n.toLowerCase().replace(/[0-9a-f]{2}/g,function(n){t<16&&(e[o+t++]=y[n])});t<16;)e[o+t++]=0;return e}function t(n,e){var r=e||0,o=v;return o[n[r++]]+o[n[r++]]+o[n[r++]]+o[n[r++]]+"-"+o[n[r++]]+o[n[r++]]+"-"+o[n[r++]]+o[n[r++]]+"-"+o[n[r++]]+o[n[r++]]+"-"+o[n[r++]]+o[n[r++]]+o[n[r++]]+o[n[r++]]+o[n[r++]]+o[n[r++]]}function u(n,e,r){var o=e&&r||0,u=e||[];n=n||{};var a=null!=n.clockseq?n.clockseq:g,f=null!=n.msecs?n.msecs:(new Date).getTime(),i=null!=n.nsecs?n.nsecs:C+1,c=f-h+(i-C)/1e4;if(c<0&&null==n.clockseq&&(a=a+1&16383),(c<0||f>h)&&null==n.nsecs&&(i=0),i>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");h=f,C=i,g=a,f+=122192928e5;var s=(1e4*(268435455&f)+i)%4294967296;u[o++]=s>>>24&255,u[o++]=s>>>16&255,u[o++]=s>>>8&255,u[o++]=255&s;var l=f/4294967296*1e4&268435455;u[o++]=l>>>8&255,u[o++]=255&l,u[o++]=l>>>24&15|16,u[o++]=l>>>16&255,u[o++]=a>>>8|128,u[o++]=255&a;for(var d=n.node||w,v=0;v<6;v++)u[o+v]=d[v];return e?e:t(u)}function a(n,e,r){var o=e&&r||0;"string"==typeof n&&(e="binary"===n?new d(16):null,n=null),n=n||{};var u=n.random||(n.rng||f)();if(u[6]=15&u[6]|64,u[8]=63&u[8]|128,e)for(var a=0;a<16;a++)e[o+a]=u[a];return e||t(u)}var f,i,c,s,l;n?e():r();for(var d="function"==typeof Buffer?Buffer:Array,v=[],y={},m=0;m<256;m++)v[m]=(m+256).toString(16).substr(1),y[v[m]]=m;var p=f(),w=[1|p[0],p[1],p[2],p[3],p[4],p[5]],g=16383&(p[6]<<8|p[7]),h=0,C=0,R=a;R.v1=u,R.v4=a,R.parse=o,R.unparse=t,R.BufferClass=d,R._rng=f,R._mathRNG=i,R._nodeRNG=c,R._whatwgRNG=s,"undefined"!=typeof module&&module.exports?module.exports=R:"function"==typeof define&&define.amd?define(function(){return R}):(l=n.uuid,R.noConflict=function(){return n.uuid=l,R},n.uuid=R)}("undefined"!=typeof window?window:null);
//# sourceMappingURL=uuid.min.js.map


function getStackTrace() {
    var err = new Error();
    return err.stack;
}