import { GOVBRMessages } from '../../mensagens/1.1.1/GOVBRMessages.mjs';
import { GOVBRPlataformaColorUtils } from './GOVBRPlataformaColorUtils.mjs';
import { GOVBRPlataformaComponentUtils } from './GOVBRPlataformaComponentUtils.mjs';
import { GOVBRPlataformaDataGrid } from './GOVBRPlataformaDataGrid.mjs';
import { GOVBRPlataformaLogs } from './GOVBRPlataformaLogs.mjs';

const VERSOES_ARQUIVOS = (async () => {
    const arquivoVersoes = await fetch(new URL('../../versoes.json', import.meta.url), { cache: 'no-cache' });
    return arquivoVersoes.json();
})();
const VERSAO_PLATAFORMA = (async () => {
    return (await VERSOES_ARQUIVOS)['GOVBRPlataforma'][(await VERSOES_ARQUIVOS)['GOVBRPlataforma'].length - 1];
})();

/*
 *********************
 * NAMESPACE GLOBAL
 *********************
 */
class GOVBRPlataformaJSImpl {
    /**
     * @param {any} val
     */
    static __resolvePromiseInit = (val) => {};
    static promiseInit = new Promise((resolve) => {
        GOVBRPlataformaJSImpl.__resolvePromiseInit = resolve;
    });

    /**
     * @type {Record<string, string[]>}
     */
    static versoes = null;
    /**
     * @type string
     */
    static versao = null;

    /**
     * Inicia o GOVBRPlataforma, necessário para utilizar os controles de importarScripts/Styles.
     * @param {string} urlCDN
     */
    static init(urlCDN, carregaMensagens = true) {
        GOVBRPlataformaJSImpl.setUrlCDNPadrao(urlCDN);
        GOVBRPlataformaJSImpl.__resolvePromiseInit();
        if (carregaMensagens) {
            GOVBRPlataformaJSImpl.#initMessages();
        }
        VERSAO_PLATAFORMA.then((versao) => {
            GOVBRPlataformaJSImpl.versao = versao;
            GOVBRPlataformaJSImpl.logs.info('GOVBRPlataformaJS versão ' + versao);
        });
        VERSOES_ARQUIVOS.then((versoes) => {
            GOVBRPlataformaJSImpl.versoes = versoes;
            GOVBRPlataformaJSImpl.logs.debug(
                '\n' +
                    Object.entries(versoes || {})
                        .map(([s, e]) => s + ': ' + e[e.length - 1])
                        .join('\n')
            );
        });
    }

    /**
     * Inicia o UserControl de Mensagens.
     */
    static async #initMessages() {
        GOVBRMessages.init({
            urlCDN: GOVBRPlataformaJSImpl.URL_CDN,
            versaoComponente: await GOVBRPlataformaJSImpl.getVersaoComponente('GOVBRMessages'),
            duracaoMensagens: 15000,
            exibeFechar: true,
            exibeTemporizador: true,
            localizacaoMensagens: 3,
        });
    }

    static DataGrid = GOVBRPlataformaDataGrid;
    static ColorUtils = GOVBRPlataformaColorUtils;
    static ComponentUtils = GOVBRPlataformaComponentUtils;

    /*
     *********************
     * LOGS E MENSAGENS
     *********************
     */
    static logs = GOVBRPlataformaLogs;
    /**
     * Dispara uma mensagem de alerta
     * @param {'alerta'|'aviso'|'erro'|'sucesso'} tipo
     * @param {string} mensagem
     */
    static mensagem(mensagem, tipo = 'aviso') {
        window.dispatchEvent(
            new CustomEvent('__govbr_msg', {
                detail: { tipo, mensagem },
            })
        );
    }

    /*
     *********************
     * IMPORTS
     *********************
     */
    /**
     * @type {string|null}
     */
    static URL_CDN = null;
    /**
     * Define a URL padrão para ser utilizada para a CDN.
     * @param {string} url
     */
    static setUrlCDNPadrao(url) {
        GOVBRPlataformaJSImpl.URL_CDN = url;
    }

    /**
     * Retorna a versão para o componente.
     * @param {string} nomeComponente
     */
    static async getVersaoComponente(nomeComponente = '') {
        if (!(await VERSOES_ARQUIVOS)[nomeComponente]?.length) {
            GOVBRPlataformaJSImpl.logs.warn('Versão do componente ' + nomeComponente + ' não encontrada, verifique se esta existe no controle de versões');
            return '1.0';
        }
        return (await VERSOES_ARQUIVOS)[nomeComponente][(await VERSOES_ARQUIVOS)[nomeComponente].length - 1];
    }

    /**
     * Importa um módulo javascript (mjs) do Resources caso este não tenha sido importado.<br/>
     * Retorna uma promise que resolve em uma importação bem feita e falha em caso de erro.
     * @param {string} urlCDN
     * @param {string} nomePasta
     * @param {string} versaoArquivo
     * @param {string} nomeModulo
     * @param {'module'|'script'} tipo
     */
    static async importarModulo(urlCDN, nomePasta, versaoArquivo, nomeModulo, tipo = 'module') {
        await GOVBRPlataformaJSImpl.promiseInit;
        if (!urlCDN && GOVBRPlataformaJSImpl.URL_CDN) {
            urlCDN = GOVBRPlataformaJSImpl.URL_CDN;
        }
        if (urlCDN.endsWith('/')) {
            urlCDN = urlCDN.substring(0, urlCDN.length - 1);
        }
        if (versaoArquivo === '' && nomeModulo) {
            versaoArquivo = await GOVBRImpl.getVersaoComponente(nomeModulo);
        }
        return await new Promise(function (resolve, reject) {
            if (nomeModulo) {
                const el = document.querySelector('#govbr_modulo_' + nomeModulo);
                if (el) {
                    if (el.hasAttribute('data-carregado')) {
                        resolve();
                        return;
                    }
                    GOVBRPlataformaJSImpl.bloqueiaElemento();
                    el.addEventListener('load', () => {
                        GOVBRPlataformaJSImpl.desbloqueiaElemento();
                        resolve();
                    });
                    el.addEventListener('error', () => {
                        GOVBRPlataformaJSImpl.desbloqueiaElemento();
                        reject();
                    });
                    return;
                }
            }
            GOVBRPlataformaJSImpl.bloqueiaElemento();
            const script = document.createElement('script');
            script.addEventListener('load', () => {
                GOVBRPlataformaJSImpl.desbloqueiaElemento();
                script.setAttribute('data-carregado', '');
                resolve();
            });
            script.addEventListener('error', () => {
                GOVBRPlataformaJSImpl.desbloqueiaElemento();
                script.remove();
                reject('Falha ao carregar ' + script.src);
            });
            let extensao;
            if (tipo === 'script') {
                extensao = '.js';
                script.type = '';
            } else {
                extensao = tipo === 'module' ? '.mjs' : '';
                script.type = tipo;
            }
            if (nomePasta) {
                let src = urlCDN + '/genexus/' + nomePasta + '/';
                if (versaoArquivo) {
                    src += versaoArquivo + '/';
                }
                src += nomeModulo + extensao;
                script.src = src;
            } else if (/^https?:\/\//.test(urlCDN)) {
                script.src = urlCDN;
            } else {
                script.src = './Resources/' + urlCDN + extensao;
            }
            if (nomeModulo) {
                script.id = 'govbr_modulo_' + nomeModulo;
            }
            document.head.append(script);
        });
    }
    /**
     * Importa uma folha de estilo não importada.<br/>
     * Retorna uma promise que resolve em uma importação bem feita e falha em caso de erro.
     * @param {string} urlCDN
     * @param {string} nomePasta
     * @param {string} versaoArquivo
     * @param {string} nomeFolha
     * @param {Node} alvo
     * @param {Node} alvoBloqueio
     */
    static async importarStyleSheet(urlCDN, nomePasta, versaoArquivo, nomeFolha, alvo, alvoBloqueio = null) {
        let alvoStyle = document.head;
        if (alvo && alvo.getRootNode() !== document) {
            const rootNode = /** @type {Document} */ (alvo.getRootNode());
            if (rootNode.head) {
                alvoStyle = rootNode.head;
            }
        }
        await GOVBRPlataformaJSImpl.promiseInit;
        if (!urlCDN && GOVBRPlataformaJSImpl.URL_CDN) {
            urlCDN = GOVBRPlataformaJSImpl.URL_CDN;
        }
        if (urlCDN.endsWith('/')) {
            urlCDN = urlCDN.substring(0, urlCDN.length - 1);
        }
        if (versaoArquivo === '' && nomeFolha) {
            versaoArquivo = await GOVBRImpl.getVersaoComponente(nomeFolha);
        }
        return await new Promise(function (resolve, reject) {
            const idFolha = nomeFolha.replace(/\W/g, '_');
            let styleFormato = /** @type {HTMLLinkElement} */ (alvoStyle.querySelector('#Style' + idFolha));
            if (styleFormato) {
                if (styleFormato.hasAttribute('data-carregado')) {
                    resolve();
                    return;
                }
                GOVBRPlataformaJSImpl.bloqueiaElemento(alvoBloqueio || alvo);
                styleFormato.addEventListener('load', () => {
                    GOVBRPlataformaJSImpl.desbloqueiaElemento(alvoBloqueio || alvo);
                    resolve();
                });
                styleFormato.addEventListener('error', () => {
                    GOVBRPlataformaJSImpl.desbloqueiaElemento(alvoBloqueio || alvo);
                    reject();
                });
            } else {
                GOVBRPlataformaJSImpl.bloqueiaElemento(alvoBloqueio || alvo);
                // Cria a folha de estilos personalizada dentro do IFRAME.
                styleFormato = document.createElement('link');
                styleFormato.id = 'Style' + idFolha;
                styleFormato.rel = 'stylesheet';
                styleFormato.href = urlCDN + '/genexus/' + nomePasta + '/' + versaoArquivo + '/' + nomeFolha + '.css';
                styleFormato.addEventListener('load', () => {
                    GOVBRPlataformaJSImpl.desbloqueiaElemento(alvoBloqueio || alvo);
                    styleFormato.setAttribute('data-carregado', '');
                    resolve();
                });
                styleFormato.addEventListener('error', () => {
                    GOVBRPlataformaJSImpl.desbloqueiaElemento(alvoBloqueio || alvo);
                    styleFormato.remove();
                    reject();
                });
                alvoStyle.append(styleFormato);
                if (alvoStyle !== document.head) {
                    GOVBRPlataformaJSImpl.importarStyleSheet(urlCDN, nomePasta, versaoArquivo, nomeFolha, document.body, alvo);
                }
            }
        });
    }

    /*
     ***********************
     * BLOQUEIO/DESBLOQUEIO
     ***********************
     */
    /**
     * @type {(bloqueia: boolean, alvo: HTMLElement) => void|null}
     */
    static metodoOverrideBloqueio = null;
    /**
     * Define um método customizado para realizar o bloqueio do alvo/sistema.
     * @param {(bloqueia: boolean, alvo: HTMLElement) => void|null} metodo
     */
    static setMetodoOverrideBloqueio(metodo) {
        GOVBRPlataformaJSImpl.metodoOverrideBloqueio = metodo;
    }

    /**
     * Bloqueia um elemento, impedindo sua utilização.
     * @param {Node} alvo
     */
    static bloqueiaElemento(alvo = document.body) {
        if (!(alvo instanceof HTMLElement) || !alvo?.getAttribute) {
            return;
        }
        if (alvo.getAttribute('data-govbr-timeout-bloqueio')) {
            clearTimeout(parseInt(alvo.getAttribute('data-govbr-timeout-bloqueio')));
            alvo.removeAttribute('data-govbr-timeout-bloqueio');
        }
        let idx = parseInt(alvo.getAttribute('data-govbr-idx-mask')) || 0;
        alvo.setAttribute('data-govbr-idx-mask', '' + (idx + 1));
        if (GOVBRPlataformaJSImpl.metodoOverrideBloqueio) {
            GOVBRPlataformaJSImpl.metodoOverrideBloqueio(true, alvo);
            return;
        }
        let mask = alvo.querySelector(':scope > .gx-mask');
        if (!mask) {
            // GeneXus possui um controle próprio que utiliza essa classe gx-mask, esse mesmo controle valida se o elemento é uma div.
            // Utilizando um article no lugar de div, enganamos o GeneXus para ele ver que o bloqueio que criamos não é um bloqueio dele.
            // Senão o GeneXus tenta tirar esse bloqueio e caga tudo. E ainda é horrível pra simular pois acontece em race condition.
            // A validação do genexus fica na função "unmask" e é algo tipo:
            // "DIV" == c.childNodes[k].tagName && this.hasClass(c.childNodes[k], this.MASK_CLASS) && !this.hasClass(c.childNodes[k], this.UNMASK_CLASS)
            mask = document.createElement('article');
            mask.className = 'gx-mask';
            alvo.append(mask);
        }
    }
    /**
     * Desloqueia um elemento préviamente bloqueado, permitindo sua utilização.
     * @param {Node} alvo
     */
    static desbloqueiaElemento(alvo = document.body) {
        if (!(alvo instanceof HTMLElement) || !alvo?.getAttribute) {
            return;
        }
        let idx = parseInt(alvo.getAttribute('data-govbr-idx-mask')) || 0;
        idx = Math.max(idx - 1, 0);
        if (!idx) {
            alvo.setAttribute(
                'data-govbr-timeout-bloqueio',
                '' +
                    setTimeout(() => {
                        idx = parseInt(alvo.getAttribute('data-govbr-idx-mask')) || 0;
                        alvo.removeAttribute('data-govbr-timeout-bloqueio');
                        if (!idx) {
                            if (GOVBRPlataformaJSImpl.metodoOverrideBloqueio) {
                                GOVBRPlataformaJSImpl.metodoOverrideBloqueio(false, alvo);
                                return;
                            }
                            const mask = alvo.querySelector(':scope > .gx-mask');
                            mask?.remove?.();
                        }
                    }, 500)
            );
        }
        alvo.setAttribute('data-govbr-idx-mask', '' + idx);
    }

    /*
     ******************************************
     * TRATAMENTOS PRÓPRIOS PARA O GENEXUS
     ******************************************
     */
    /**
     * Ajusta o tamanho do PopUP gerado pelo Genexus para ocupar o tamanho máximo disponível.
     */
    static popupTamanho() {
        const frame = /** @type {HTMLIFrameElement} */ (frameElement);
        if (
            window.location.href.indexOf('gxPopupLevel') > -1 &&
            window.location.href.indexOf('promptexterno') == -1 
        ) {

            document.body.classList.add('govbr-gx-popup');

            if(!frame.contentWindow.document.getElementById('TABLEMAIN').className.includes('popup-normal-size')){
                let width = parent.window.innerWidth - 40;
                let height = parent.window.innerHeight - 40;
                // @ts-ignore
                const popup = gx.popup.getPopup();
    
                setTimeout(function () {
                    const gxPopup = /** @type {HTMLCollectionOf<HTMLElement>} */ (parent.document.getElementsByClassName('gx-popup'));
                    if (gxPopup.length > 0) {
                        const carregando = parent.document.getElementById('carregando');
                        if (carregando) {
                            carregando.innerHTML = '';
                        }
                    }
                    Array.from(gxPopup).forEach((e) => {
                        e.style.width = width + 'px';
                        e.style.height = height + 'px';
                    });
                }, 100);
    
                popup.autoresize = 0;
                popup.width = width;
                popup.height = height;
    
                if (frame?.style) {
                    frame.style.width = width.toString() + 'px';
                    frame.style.height = height.toString() + 'px';
                }

            }
            
        }
    }
    /**
     * Ajusta os masks criados pelo GeneXus para mostrarem o ícone de carregando da GOVBR.
     */
    static loader() {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                mutation.addedNodes.forEach((node) => {
                    if (node instanceof HTMLElement && node.classList.contains('gx-mask') && node.parentElement && !node.parentElement.querySelector(':scope > .carregando')) {
                        const divCarregando = document.createElement('div');
                        divCarregando.className = 'carregando loader';
                        divCarregando.innerHTML = `
                            <img style="position: absolute; left: 50%; margin-left: -50px; top: 50%; margin-top: -50px; height: 200px; width: 200px; z-index:1000" src="Resources/CarregandoAnimacao.svg">
                        `;
                        node.parentElement.insertBefore(divCarregando, node);
                    }
                });
                mutation.removedNodes.forEach((node) => {
                    if (node instanceof HTMLElement && node.classList.contains('gx-mask')) {
                        const tg = /** @type {HTMLElement} */ (mutation.target);
                        tg.querySelector(':scope > .carregando')?.remove?.();
                    }
                });
            });
        });
        observer.observe(document, {
            subtree: true,
            childList: true,
        });
    }
    /**
     * Tratamentos específicos para as mensagens de erros em campos obrigatórios.
     */
    static trataErros() {
        const MutationObserver = window.MutationObserver;
        const observer = new MutationObserver(function (mutations, observer) {
            mutations
                .filter((e) => /** @type {HTMLElement} */ (e.target).className == 'gx-error__message-container')
                .forEach(function (e) {
                    const tg = /** @type {HTMLElement} */ (e.target);
                    const idTg = tg.children?.[0]?.id?.split?.('_')?.[0];
                    if (idTg) {
                        const campo = document.getElementById(idTg);
                        if (campo) {
                            if ((tg.getAttribute('style') || '').indexOf('block') != -1) {
                                campo.classList.remove('ObrigatorioAtributoAttributeRealWidth');
                                campo.classList.add('ObrigatorioAtributoAttributeRealWidthErro');
                            } else {
                                campo.classList.add('ObrigatorioAtributoAttributeRealWidth');
                                campo.classList.remove('ObrigatorioAtributoAttributeRealWidthErro');
                            }
                        }
                    }
                });
        });

        observer.observe(document, {
            subtree: true,
            attributes: true,
        });
    }
    /**
     * Trata os atalhos F5 e CTRL+R para atualizarem o genexus.
     */
    static trataRefresh() {
        window.addEventListener('keydown', (e) => {
            const key = (e.key || '').toUpperCase();
            if (key === 'F5' || (key === 'R' && e.ctrlKey)) {
                if (window === window.top) {
                    const frame = /** @type {HTMLIFrameElement} */ (document.querySelector('#TABLEMAIN .TableEmbed > iframe'));
                    if (frame) {
                        // Acessar o contentDocument, isso vai dizer se o iframe está em CORS ou não, se estiver em CORS, não será possível atualizar via contentWindow.
                        //https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/contentDocument
                        // If the iframe and the iframe's parent document are Same Origin, returns a Document, else returns null
                        if (frame.contentDocument) {
                            frame.contentWindow.location.reload();
                        } else {
                            // Se for CORS, atualiza via o src, o problema disto é que se o src não foi atualizado nas trocas de páginas,
                            // ele vai atualizar para a página errada.
                            const orig = frame.src;
                            frame.src = 'about://blank';
                            frame.src = orig;
                        }
                        e.preventDefault();
                    }
                } else {
                    window.location.reload();
                    e.preventDefault();
                }
            }
        });
    }
    /**
     * Trata a exibição do módulo dentro de IFrames.
     */
    static trataIFrame() {
        const MPage = document.querySelector('#LAYOUTMAINTABLE_MPAGE');
        // @ts-ignore
        if (window['gx'] && MPage && window.top !== window) {
            const load = () => {
                MPage.classList.add('govbr-masterpage-iframe');
                const observer = new MutationObserver((mutations) => {
                    if (!MPage.classList.contains('govbr-masterpage-iframe')) {
                        MPage.classList.add('govbr-masterpage-iframe');
                    }
                });
                observer.observe(MPage, {
                    attributes: true,
                });
            };
            // @ts-ignore
            gx.spa.addObserver('onnavigate', window, load);
            load();
        }
    }
}

/*
 *********************
 * LISTENERS
 *********************
 */
// @ts-ignore
if (window['gx']) {
    GOVBRPlataformaJSImpl.loader();
    window.addEventListener('load', () => {
        GOVBRPlataformaJSImpl.trataRefresh();
        GOVBRPlataformaJSImpl.popupTamanho();
        GOVBRPlataformaJSImpl.trataErros();
        GOVBRPlataformaJSImpl.trataIFrame();
    });
    window.addEventListener('resize', () => {
        GOVBRPlataformaJSImpl.popupTamanho();
        Array.from(/** @type {NodeListOf<HTMLIFrameElement>} */ (document.querySelectorAll('.gx-popup .gx-popup-content iframe')), (el) =>
            el.contentWindow.dispatchEvent(new Event('resize'))
        );
    });
}

// Alias para o GOVBRPlataformaJS.
/**
 * @type {GOVBRPlataformaJSImpl}
 */
class GOVBRImpl extends GOVBRPlataformaJSImpl {}

/**
 * @global
 */
globalThis.GOVBRPlataformaJS = GOVBRPlataformaJSImpl;
/**
 * @global
 */
globalThis.GOVBR = GOVBRImpl;
