const template = `
<div class="govbr-mensagens">
	<div class="govbr-mensagens-templates" hidden>
		<article class="govbr-mensagem govbr-mensagem-erro" data-id="erro">
			<i class="govbr-icone-mensagem fas fa-circle-xmark" aria-hidden="true"></i>
			<h2 class="govbr-titulo-mensagem">Erro!</h2>
			<button class="govbr-fechar-mensagem fas fa-times" title="Fechar" type="button"></button>
			<p class="govbr-texto-mensagem"></p>
			<div class="govbr-temporizador-mensagem"></div>
		</article>
		<article class="govbr-mensagem govbr-mensagem-alerta" data-id="atencao">
			<i class="govbr-icone-mensagem fas fa-triangle-exclamation" aria-hidden="true"></i>
			<h2 class="govbr-titulo-mensagem">Atenção!</h2>
			<button class="govbr-fechar-mensagem fas fa-times" title="Fechar" type="button"></button>
			<p class="govbr-texto-mensagem"></p>
			<div class="govbr-temporizador-mensagem"></div>
		</article>
		<article class="govbr-mensagem govbr-mensagem-aviso" data-id="aviso">
			<i class="govbr-icone-mensagem fas fa-circle-exclamation" aria-hidden="true"></i>
			<h2 class="govbr-titulo-mensagem">Aviso!</h2>
			<button class="govbr-fechar-mensagem fas fa-times" title="Fechar" type="button"></button>
			<p class="govbr-texto-mensagem"></p>
			<div class="govbr-temporizador-mensagem"></div>
		</article>
		<article class="govbr-mensagem govbr-mensagem-sucesso" data-id="sucesso">
			<i class="govbr-icone-mensagem fas fa-circle-check" aria-hidden="true"></i>
			<h2 class="govbr-titulo-mensagem">Sucesso!</h2>
			<button class="govbr-fechar-mensagem fas fa-times" title="Fechar" type="button"></button>
			<p class="govbr-texto-mensagem"></p>
			<div class="govbr-temporizador-mensagem"></div>
		</article>
	</div>
	<div class="govbr-mensagens-container"></div>
</div>
`;

/**
 * @typedef {Object} Mensagem
 * @prop {string} id
 * @prop {string} type
 * @prop {string} text
 * @prop {string} target
 * @prop {number} key
 * @prop {number} t
 * @prop {number} timeout
 * @prop {HTMLElement} obj
 */

/**
 * Classe para o controle de mensagens enviadas pelo GeneXus.
 */
class GOVBRMessages {
    /**
     * Localizações possíveis das mensagens.
     */
    static LOCALIZACAO_MENSAGEM = {
        TOPO_ESQUERDA: 1,
        TOPO_CENTRO: 2,
        TOPO_DIREITA: 3,
        DIREITA_CENTRO: 4,
        BASE_DIREITA: 5,
        BASE_CENTRO: 6,
        BASE_ESQUERDA: 7,
        ESQUERDA_CENTRO: 8,
        CENTRO: 9,
    };

    duracaoMensagens = 15000;
    exibeFechar = true;
    exibeTemporizador = true;
    localizacaoMensagens = GOVBRMessages.LOCALIZACAO_MENSAGEM.TOPO_DIREITA;
    margens = [0, 0, 0, 0];

    /**
     * @type {Record<string, Record<string, Mensagem[]>>}
     */
    mensagensVisiveis = {};
    /**
     * @type {number}
     */
    queryAtualizar = null;

    /**
     * @type {HTMLElement}
     */
    templates;
    /**
     * @type {HTMLElement}
     */
    container;

    /**
     * @type {Mensagem[]}
     */
    todasMensagens = [];

    /**
     * @type {GOVBRMessages}
     */
    static GOVBRMessagesInstance = null;

    /**
     * Tipos possíveis para as mensagens.
     */
    static TIPOS_MENSAGEM = {
        ALERTA: 'alerta',
        AVISO: 'aviso',
        ERRO: 'erro',
        SUCESSO: 'sucesso',
    };

    /**
     * Retorna a instância do controle de mensagens.
     */
    static getInstance() {
        if (!this.GOVBRMessagesInstance) {
            this.GOVBRMessagesInstance = new GOVBRMessages();
            //@ts-ignore
            gx.spa.addObserver(
                'onnavigate',
                window,
                () => {
                    GOVBRMessages.init();
                },
                {}
            );
        }
        return this.GOVBRMessagesInstance;
    }

    /**
     * Inicia a instância do GOVBRMessages. Adicionando os observers caso não presentes.
     * Após isto, reavalia o DOM para exibir as mensagens no local correto.
     * @param {any} config
     */
    static init(config) {
        const instance = GOVBRMessages.getInstance();
        instance.load(config);
    }

    /**
     * Carrega as configurações a partir de um objeto.
     * @param {any} config
     */
    load(config) {
        if (!document.body.querySelector('#UCGOVBRMessage')) {
            const div = document.createElement('div');
            div.id = 'UCGOVBRMessage';
            div.innerHTML = template;
            document.body.append(div);
            if (config.urlCDN && config.versaoComponente) {
                GOVBRPlataformaJS.importarStyleSheet(config.urlCDN, 'mensagens', config.versaoComponente, 'GOVBRMessages', div);
            }
        }
        config = typeof config === 'object' ? config : {};
        Object.entries({
            duracaoMensagens: /** @param {any} v */ (v) => parseInt(v) || 15000,
            exibeFechar: /** @param {any} v */ (v) => v !== false,
            exibeTemporizador: /** @param {any} v */ (v) => v !== false,
            localizacaoMensagens: /** @param {any} v */ (v) => parseInt(v) || GOVBRMessages.LOCALIZACAO_MENSAGEM.TOPO_DIREITA,
            margens: /** @param {any} v */ (v) => (Array.isArray(v) ? v : [0, 0, 0, 0]),
        }).forEach(([prop, fn]) => {
            if (config.hasOwnProperty(prop)) {
                // @ts-ignore
                this[prop] = fn(config[prop]);
            }
        });
        this.templates = document.body.querySelector('#UCGOVBRMessage .govbr-mensagens-templates');
        this.container = document.body.querySelector('#UCGOVBRMessage .govbr-mensagens-container');
        Object.entries(this.mensagensVisiveis).forEach(([id, types]) => {
            Object.entries(types).forEach(([type, msgs]) => {
                msgs.forEach((_, idx) => {
                    this.mensagensVisiveis[id][type][idx] = null;
                });
            });
        });
        this.todasMensagens = [];
        this.adicionaObservers();
        this.avaliaDOM();
    }

    /**
     * Adiciona os observers a página.
     */
    adicionaObservers() {
        /**
         * @param {any} msgs
         */
        const processaMensagens = (msgs) => {
            if (!Array.isArray(msgs)) {
                if (msgs?.msgs) {
                    return processaMensagens(msgs.msgs);
                }
                return GOVBR.logs.error('Entrada inválida para mensagens: ', msgs);
            }
            msgs.forEach((msg) => {
                this.processaMensagem(msg);
            });
        };
        //@ts-ignore
        if (window['gx']) {
            //@ts-ignore
            gx.fx.obs.addObserver(
                'gx.onmessages',
                this,
                /**
                 * @param {any} msgs
                 */
                (msgs) => {
                    if (Array.isArray(msgs)) {
                        processaMensagens(msgs);
                        setTimeout(() => {
                            document.querySelectorAll('.ErrorViewer').forEach((e) => e.remove());
                        });
                    } else if (typeof msgs == 'object') {
                        Object.entries(msgs).forEach(([v, k]) => {
                            processaMensagens(k);
                        });
                        document.querySelectorAll('.ErrorViewer').forEach((e) => e.remove());
                        setTimeout(() => {
                            document.querySelectorAll('.ErrorViewer').forEach((e) => e.remove());
                        });
                    } else {
                        GOVBR.logs.error('Entrada inválida para mensagens: ', msgs);
                    }
                }
            );
            //@ts-ignore
            gx.fx.obs.addObserver('gx.onaftervalidatecompleted', this, () => {
                //@ts-ignore
                if (!gx.csv.anyError) {
                    return;
                }
                //@ts-ignore
                Object.values(gx.util.balloon.balloons).forEach((balloon) => {
                    if ((balloon.active && balloon.messageErr) || balloon.messageWar) {
                        const msg = balloon.messageErr || balloon.messageWar;
                        this.processaMensagem({
                            text: msg,
                            id: 'default',
                            type: GOVBRMessages.TIPOS_MENSAGEM.ALERTA,
                        });
                    }
                });
            });
        }

        window.addEventListener(
            '__govbr_msg',
            /**
             * @param {CustomEvent<any>} event
             */
            (event) => {
                const { tipo, mensagem } = event.detail;
                this.processaMensagem({
                    text: mensagem,
                    id: 'default',
                    type: tipo || GOVBRMessages.TIPOS_MENSAGEM.AVISO,
                });
            }
        );
        window.addEventListener(
            'error',
            /**
             * @param {ErrorEvent} event
             */
            (event) => {
                if (!(Object.prototype.hasOwnProperty.apply(event, ['filename']) && /gxgral\.js/.test(/** @type {any} */ (event).filename))) {
                    this.trataMensagemErroJS(event.message);
                }
            }
        );
        window.addEventListener(
            'unhandledrejection',
            /**
             * @param {PromiseRejectionEvent} event
             */
            (event) => {
                if (!(Object.prototype.hasOwnProperty.apply(event, ['filename']) && /gxgral\.js/.test(/** @type {any} */ (event).filename))) {
                    this.trataMensagemErroJS(event.reason);
                }
            }
        );
    }

    /**
     * Avalia o estado do DOM, removendo mensagens presentes no HTML e exibindo estas no PopUp.
     */
    avaliaDOM() {
        const errorViewers = Array.from(document.querySelectorAll('#gxErrorViewer'));
        const containers = Array.from(document.querySelectorAll('[data-gx-id="gxErrorViewer"],.ErrorViewer.gx_ev[data-gx-id]'));
        const msgs = containers.flatMap((e) => Array.from(e.children));
        msgs.forEach((msg) => {
            this.processaMensagem({
                text: msg.innerHTML,
                id: 'default',
                type: msg.classList.contains('gx-error-message') ? GOVBRMessages.TIPOS_MENSAGEM.ERRO : GOVBRMessages.TIPOS_MENSAGEM.ALERTA,
            });
            msg.remove();
        });
        errorViewers.forEach((e) => e.remove());
    }

    /**
     * Trata uma mensagem de erro que estourou no JS.
     * @param {any} msg
     */
    trataMensagemErroJS(msg) {
        const texto = typeof msg == 'string' ? msg : msg?.message || msg?.mensagem || msg?.Status || msg?.status || null;
        if (!texto) {
            return;
        }
        this.processaMensagem({
            text: 'Houve um erro de script:\n' + texto + '.\nEntre em contato com o suporte...',
            id: 'default',
            type: GOVBRMessages.TIPOS_MENSAGEM.ERRO,
        });
    }

    /**
     * Exibe um alerta globalmente.
     * @param {any} msg
     * @param {number} type
     */
    static exibeAlerta(msg, type) {
        const texto = typeof msg == 'string' ? msg : msg?.message || msg?.mensagem || msg?.Status || msg?.status || null;
        if (!texto) {
            return;
        }
        GOVBRMessages.getInstance().processaMensagem({
            text: msg,
            id: 'default',
            type: type,
        });
    }

    /**
     * Processa as mensagens recebidas do servidor e a serem exibidas no FrontEnd.
     * @param {any} msg
     */
    processaMensagem(msg) {
        let { text, id, type, target } = msg;
        if (text) {
            if (text.startsWith('{')) {
                text = text.replace(/\r\n|\n|\r|\\r\\n|\\n|\\r/g, '<br/>');
                try {
                    const val = eval('(' + text + ')');
                    if (val?.text) {
                        ({ text, id, type, target } = val);
                    }
                } catch (ex) {
                    GOVBR.logs.error(ex);
                }
            }
            if (/é obrigatóri[oa]\.?/.test(text)) {
                id = 'InputRequired';
                type = GOVBRMessages.TIPOS_MENSAGEM.ALERTA;
            } else if (!id) {
                id = 'default';
            }
            if (!this.mensagensVisiveis[id]) {
                this.mensagensVisiveis[id] = {};
            }
            if (!Object.values(GOVBRMessages.TIPOS_MENSAGEM).includes(type)) {
                switch (type) {
                    case 'warning': // Warning
                    case 0: // Warning
                        type = GOVBRMessages.TIPOS_MENSAGEM.ALERTA;
                        break;
                    case 1: // Error
                        type = GOVBRMessages.TIPOS_MENSAGEM.ALERTA;
                        break;
                    case 'information': // Info
                    case 'info': // Info
                    case 2: // Info
                        type = GOVBRMessages.TIPOS_MENSAGEM.AVISO;
                        break;
                    case 'debug': // Debug
                    case 3: // Debug
                        type = GOVBRMessages.TIPOS_MENSAGEM.AVISO;
                        break;
                    case 'error': // Error
                        type = GOVBRMessages.TIPOS_MENSAGEM.ERRO;
                        break;
                    case 'success': // Sucesso
                        type = GOVBRMessages.TIPOS_MENSAGEM.SUCESSO;
                        break;
                    default:
                        type = GOVBRMessages.TIPOS_MENSAGEM.ALERTA;
                        break;
                }
            }
            if (!this.mensagensVisiveis[id][type]) {
                this.mensagensVisiveis[id][type] = [];
            }

            const msgObj = this.mensagensVisiveis[id][type];
            const key = msgObj.length;

            const atual = msgObj.find((e) => e?.text.replace(/\.+$/, '') == text.replace(/\.+$/, ''));
            if (atual) {
                const instance = msgObj.splice(atual.key, 1, null);
                instance[0]?.obj?.remove();
            }

            /**
             * @type {Mensagem}
             */
            const novaMsg = {
                id,
                type,
                text,
                target,
                key,
                t: new Date().getTime(),
                timeout: setTimeout(() => {
                    msgObj.splice(key, 1, null);
                    this.atualizaMensagensVisiveis();
                }, this.duracaoMensagens),
                obj: null,
            };
            msgObj.push(novaMsg);
            this.todasMensagens.push(novaMsg);
            this.atualizaMensagensVisiveis();
        }
    }

    /**
     * Exibe todas as mensagens que já foram exibidas.
     */
    static exibeTodasMensagens() {
        const instance = GOVBRMessages.getInstance();
        if (!instance?.todasMensagens?.length) {
            return false;
        }
        const mensagensExibindo = instance.todasMensagens.filter((msg) => {
            return instance.mensagensVisiveis[msg.id][msg.type][msg.key];
        });
        if (mensagensExibindo.length) {
            mensagensExibindo.forEach((msg) => {
                if (msg.timeout) {
                    clearTimeout(msg.timeout);
                }
                instance.mensagensVisiveis[msg.id][msg.type].splice(msg.key, 1, null);
            });
            instance.atualizaMensagensVisiveis();
            return true;
        }

        instance.todasMensagens.forEach((msg) => {
            const msgObj = instance.mensagensVisiveis[msg.id][msg.type];
            msg.t = new Date().getTime();
            if (msg.timeout) {
                clearTimeout(msg.timeout);
            }
            if (msg.obj) {
                msg.obj.remove();
            }
            msg.timeout = setTimeout(() => {
                msgObj.splice(msg.key, 1, null);
                instance.atualizaMensagensVisiveis();
            }, instance.duracaoMensagens);

            const atual = msgObj.find((e) => e?.text.replace(/\.+$/, '') == msg.text.replace(/\.+$/, ''));
            if (atual) {
                msgObj.splice(atual.key, 1, null);
            }
            msgObj[msg.key] = msg;
        });
        instance.atualizaMensagensVisiveis();
        return true;
    }

    /**
     * Atualiza as mensagens visíveis para que apareçam em tela.
     */
    atualizaMensagensVisiveis() {
        if (this.queryAtualizar) {
            clearTimeout(this.queryAtualizar);
        }
        this.queryAtualizar = setTimeout(() => {
            this.container.setAttribute('data-localizacao-mensagens', '' + this.localizacaoMensagens);
            if (this.margens) {
                this.container.style.setProperty('padding-top', this.margens[0] + 'px');
                this.container.style.setProperty('padding-right', this.margens[1] + 'px');
                this.container.style.setProperty('padding-bottom', this.margens[2] + 'px');
                this.container.style.setProperty('padding-left', this.margens[3] + 'px');
            }
            this.container.querySelectorAll('.govbr-mensagem:not(.govbr-mensagem-removida)').forEach((e) => e.classList.add('govbr-mensagem-removendo'));
            this.container.querySelectorAll('.govbr-mensagem-stack').forEach((e) => e.classList.remove('govbr-mensagem-stack'));
            /** @type {NodeListOf<HTMLElement>} */ (this.container.querySelectorAll('[data-texto-original]')).forEach((msg) => {
                if (msg) {
                    msg.innerText = msg.getAttribute('data-texto-original');
                }
            });
            Object.entries(this.mensagensVisiveis).forEach(([id, types]) => {
                Object.entries(types).forEach(([type, msgs]) => {
                    msgs.forEach((msg) => {
                        if (msg) {
                            msg.obj = this.atualizaMensagem(id, type, msg);
                        }
                    });
                });
            });
            /** @type {HTMLElement[]} */ (Array.from(this.container.querySelectorAll('.govbr-mensagem-removendo:not(.govbr-mensagem-removida)')))
                .map(
                    /**
                     * @param {HTMLElement} msg
                     * @returns {[HTMLElement, number]}
                     */
                    (msg) => [msg, msg.clientHeight]
                )
                .forEach(([msg, height]) => {
                    msg.style.setProperty('--altura-msg', -height - 5 + 'px');
                    msg.classList.add('govbr-mensagem-removida');
                    setTimeout(() => {
                        msg.remove();
                    }, 250);
                });
            this.queryAtualizar = null;
        }, 250);
    }

    /**
     * Atualiza o estado de uma mensagem.
     * @param {string} id
     * @param {string} type
     * @param {any} msg
     * @returns {HTMLElement}
     */
    atualizaMensagem(id, type, msg) {
        const { text, target, key } = msg;
        let atual = /** @type {HTMLElement} */ (this.container.querySelector('[data-id-msg="' + key + '"][data-type-msg="' + id + '_' + type + '"]'));
        if (!atual) {
            const template = this.templates.querySelector('.govbr-mensagem-' + type);
            const clone = /** @type {HTMLElement} */ (template.cloneNode(true));
            atual = clone.querySelector('.govbr-texto-mensagem');
            atual.setAttribute('data-id-msg', key);
            atual.setAttribute('data-type-msg', id + '_' + type);
            atual.setAttribute('data-texto-original', text);
            atual.innerHTML = text || '';
            if (target) {
                clone.classList.add('govbr-mensagem-clicavel');
                clone.addEventListener('click', (e) => {
                    if (e.target != fechar) {
                        target?.focus?.();
                    }
                });
            }
            const fechar = clone.querySelector('.govbr-fechar-mensagem');
            if (this.exibeFechar) {
                fechar.addEventListener('click', (e) => {
                    this.mensagensVisiveis[id][type].splice(key, 1, null);
                    if (atual.getAttribute('stack')) {
                        (JSON.parse(atual.getAttribute('stack')) || []).forEach(
                            /**
                             * @param {[string, string, number]} _
                             */
                            ([id, type, key]) => {
                                const instance = this.mensagensVisiveis[id][type].splice(key, 1, null);
                                instance[0]?.obj?.remove();
                            }
                        );
                    }
                    this.atualizaMensagensVisiveis();
                });
            } else {
                fechar.remove();
            }
            const temporizador = /** @type {HTMLElement} */ (clone.querySelector('.govbr-temporizador-mensagem'));
            if (this.exibeTemporizador) {
                temporizador.style.setProperty('--duracao-msg', this.duracaoMensagens - (new Date().getTime() - msg.t) + 'ms');
            } else {
                temporizador.remove();
            }
            this.container.append(clone);
        }
        atual.parentElement.classList.remove('govbr-mensagem-removendo');
        this.avaliaStack(id, type, atual);
        return atual.parentElement;
    }

    /**
     * Avalia se é possível realizar o stack de uma mensagem stackável.
     * @param {string} id
     * @param {string} type
     * @param {HTMLElement} msg
     */
    avaliaStack(id, type, msg) {
        let replace = null;
        let textoReplace = '';
        switch (id) {
            case 'ForeignKeyNotFound':
                replace = / já cadastrad([oa])s?\.?$/;
                textoReplace = ' já cadastrad$1s.';
                break;
            case 'InputRequired':
                replace = / (?:é|são) obrigatóri([oa])s?\.?$/;
                textoReplace = ' são obrigatóri$1s.';
                break;
        }
        if (replace) {
            const alvoStack = /** @type {HTMLElement} */ (document.querySelectorAll('.govbr-mensagem:not(.govbr-mensagem-removendo) [data-type-msg="' + id + '_' + type + '"]')[0]);
            if (alvoStack && alvoStack != msg) {
                const stack = JSON.parse(alvoStack.getAttribute('data-stack') || '[]') || [];
                const key = msg.getAttribute('data-id-msg');
                const [id, type] = msg.getAttribute('data-type-msg').split('_');
                stack.push([id, type, key]);
                alvoStack.setAttribute('data-stack', JSON.stringify(stack));
                msg.parentElement.classList.add('govbr-mensagem-stack');
                alvoStack.innerText = alvoStack.innerText.replace(replace, ', ' + msg.innerText.replace(replace, '') + textoReplace);
            }
        }
    }
}

// Caso esteja no browser, disponibiliza a classe no objeto de window.
if (typeof window !== 'undefined') {
    // @ts-ignore
    window['GOVBRMessages'] = GOVBRMessages;
}
export { GOVBRMessages };
