import { ClassicEditor, Essentials, Paragraph, Bold, Italic, Font, Heading, List, Link } from './../../ckeditor/5-43.3.1/ckeditor5.js';
import translations from './../../ckeditor/5-43.3.1/translations/pt-br.js';
import { GOVBRFormsUCConfiguradorEventos } from './GOVBRFormsUCConfiguradorEventos.mjs';

/**
 * Classe base para componentes do GOVBRForms.
 */
export class GOVBRFormsUCComponente {
    /**
     * @type {string}
     */
    _identificador;
    /**
     * @type {import("./GOVBRFormsUC.mjs").GOVBRFormsUC}
     */
    form;
    /**
     * @type {GOVBRFormsUCComponenteContainer}
     */
    pai;

    /**
     * @type {import('./GOVBRFormsUCConfiguradorEventos.mjs').GOVBRFormsUCValidacao[]}
     */
    validacoes = [];
    /**
     * @type {import('./GOVBRFormsUCConfiguradorEventos.mjs').GOVBRFormsUCEvento[]}
     */
    eventos = [];

    /**
     * @type {string}
     */
    _dataIdentificador;
    /**
     * @type {string}
     */
    _dataColunasOcupadas;
    /**
     * @type {string}
     */
    _dataLinhasOcupadas;
    /**
     * @type {string}
     */
    _dataVisivel;
    /**
     * @type {string}
     */
    dataTitulo;
    /**
     * @type {string}
     */
    dataLocalExibicao;

    /**
     * @type {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement}
     */
    _input;

    /**
     * @param {string} identificador
     */
    constructor(identificador) {
        this.identificador = identificador;
        if (!this.tipo) {
            throw new Error('GOVBRFormsUCComponente é uma classe abstrata e não deve ser instanciada diretamente.');
        }
    }

    /**
     * Tratamentos a serem realizados após a construção e inicialização do componente para o modo de visualização.
     */
    afterInitVisualizacao() {}

    /**
     * Monta a visualização do componente quando este é apendado em tela.
     * @returns {HTMLElement}
     */
    montaVisualizacao() {
        const base = this.criaElementoBase();
        base.classList.add('govbr-forms-componente');
        // @ts-ignore
        base['__govbr_instance'] = this;
        base.setAttribute('data-identificador', this.identificador);
        if (this.identificadorOriginal !== this.identificador) {
            base.setAttribute('data-identificador-original', this.identificadorOriginal);
        }
        if (this.dataColunasOcupadas > 1) {
            base.style.setProperty('--colunas-ocupadas', '' + Math.min(this.dataColunasOcupadas, this.colunasMax));
        }
        if (this.dataLinhasOcupadas > 1) {
            base.style.setProperty('--linhas-ocupadas', '' + this.dataLinhasOcupadas);
        }
        if (!this.isVisivel()) {
            base.classList.add('govbr-forms-componente-oculto');
        }
        return base;
    }

    /**
     * Monta a visualização do componente quando este é apendado ao editor.
     * @returns {HTMLElement}
     */
    montaVisualizacaoEditor() {
        const base = this.criaElementoBase();
        base.classList.add('govbr-forms-componente');
        // @ts-ignore
        base['__govbr_instance'] = this;
        base.setAttribute('data-identificador', this.identificador);
        if (this.identificadorOriginal !== this.identificador) {
            base.setAttribute('data-identificador-original', this.identificadorOriginal);
        }
        base.setAttribute('data-titulo', this.titulo);
        if (!this.isVisivel()) {
            base.classList.add('govbr-forms-componente-oculto');
        }
        if (this.dataColunasOcupadas > 1) {
            base.style.setProperty('--colunas-ocupadas', '' + Math.min(this.dataColunasOcupadas, this.colunasMax));
        }
        if (this.dataLinhasOcupadas > 1) {
            base.style.setProperty('--linhas-ocupadas', '' + this.dataLinhasOcupadas);
        }
        return base;
    }

    /**
     * Cria e retorna o elemento usado como base do componente
     * @returns {HTMLElement}
     */
    criaElementoBase() {
        return document.createElement('span');
    }

    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        /**
         * @param {string} valor
         * @param {string} texto
         */
        const criaOption = (valor, texto) => {
            const option = document.createElement('option');
            option.value = valor;
            option.innerText = texto;
            return option;
        };
        const campoIdentificador = document.createElement('input');
        campoIdentificador.type = 'text';
        campoIdentificador.id = 'dataIdentificador';
        campoIdentificador.classList.add('govbr-forms-propriedades-campo-input');
        campoIdentificador.setAttribute(
            'data-hint',
            'Identificador único do componente, utilizado para se referênciar ao elemento em Eventos e Validações. Utilize apenas letras (a-z, A-Z), números(0-9) e hífen (-).'
        );
        campoIdentificador.setAttribute('data-order', '2');
        const fnChangeIdentificador = () => {
            campoIdentificador.value = campoIdentificador.value.replace(/[^a-zA-Z0-9\-]/g, '');
        };
        campoIdentificador.addEventListener('input', fnChangeIdentificador);
        campoIdentificador.addEventListener('change', fnChangeIdentificador);
        const campoVisivel = document.createElement('input');
        campoVisivel.type = 'checkbox';
        campoVisivel.id = 'dataVisivel';
        campoVisivel.classList.add('govbr-forms-propriedades-campo-input');
        campoVisivel.setAttribute('data-hint', 'Se o elemento está visível em tela ou não.');
        campoVisivel.setAttribute('data-order', '3');
        /**
         * @type {Record<string, HTMLElement>}
         */
        const retorno = {
            'Identificador do Campo': campoIdentificador,
            'Visível': campoVisivel,
        };
        if (this.colunasMax > 1) {
            const campoColunasOcupadas = document.createElement('input');
            campoColunasOcupadas.type = 'number';
            campoColunasOcupadas.id = 'dataColunasOcupadas';
            campoColunasOcupadas.step = '1';
            campoColunasOcupadas.min = '1';
            campoColunasOcupadas.max = '' + this.colunasMax;
            campoColunasOcupadas.classList.add('govbr-forms-propriedades-campo-input');
            campoColunasOcupadas.setAttribute(
                'data-hint',
                'O container deste elemento atualmente possui ' + this.colunasMax + ' colunas, indica quantas destas colunas o componente deve ocupar.'
            );
            retorno['Colunas Ocupadas'] = campoColunasOcupadas;
            const campoLinhasOcupadas = document.createElement('input');
            campoLinhasOcupadas.type = 'number';
            campoLinhasOcupadas.id = 'dataLinhasOcupadas';
            campoLinhasOcupadas.step = '1';
            campoLinhasOcupadas.min = '1';
            campoLinhasOcupadas.classList.add('govbr-forms-propriedades-campo-input');
            campoLinhasOcupadas.setAttribute('data-hint', 'Indica quantas linhas o componente deve ocupar.');
            retorno['Linhas Ocupadas'] = campoLinhasOcupadas;
        }
        const campoInternoExterno = document.createElement('select');
        campoInternoExterno.id = 'dataLocalExibicao';
        campoInternoExterno.classList.add('govbr-forms-propriedades-campo-input');
        campoInternoExterno.setAttribute('data-hint', 'Define os locais que o componente será exibido.');
        campoInternoExterno.append(criaOption('', 'Ambos (no Portal e na Plataforma)'));
        campoInternoExterno.append(criaOption('interno', 'Apenas Internamente na Plataforma GOVBR'));
        campoInternoExterno.append(criaOption('externo', 'Apenas Externalmente no Portal de Serviços'));
        retorno['Local de Exibição'] = campoInternoExterno;
        return retorno;
    }

    /**
     * Monta botões para ações adicionais para o componente.
     * @param {HTMLElement} alvo
     */
    montaAcoesAdicionais(alvo) {}

    /**
     * Valida se o valor do campo é valida.
     * @param {boolean} submit
     * @returns {boolean}
     */
    valida(submit) {
        /**
         * @type {GOVBRFormsUCComponente}
         */
        let atual = this;
        while (atual.pai) {
            if (!atual.isVisivel()) {
                return true;
            }
            atual = atual.pai;
        }
        if (!atual.isVisivel()) {
            return true;
        }
        if (this._input && !this._input.checkValidity()) {
            if (submit && this.form.userControl.EXIBE_VALIDACAO_PADRAO) {
                this._input.reportValidity();
            }
            return false;
        }
        return true;
    }

    /**
     * Converte o componente para um formato serializável JSON para que possa ser salvo.
     * @returns {import("./GOVBRFormsUserControl.mjs").GOVBRFormsComponente}
     */
    toJSON() {
        const retorno = {
            Id: this.identificadorOriginal,
            Tipo: this.tipo,
            Propriedades: this.toJSONPropriedades(),
            Filhos: this.toJSONFilhos(),
            Eventos: this.eventos,
            Validacoes: this.validacoes,
        };
        return retorno;
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return [
            { Nome: 'identificador', Valor: this.dataIdentificador },
            { Nome: 'colunasOcupadas', Valor: '' + this.dataColunasOcupadas },
            { Nome: 'linhasOcupadas', Valor: '' + this.dataLinhasOcupadas },
            { Nome: 'visivel', Valor: this.dataVisivel ? '1' : '0' },
            { Nome: 'localExibicao', Valor: this.dataLocalExibicao },
        ];
    }

    /**
     * Converte os Filhos para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsComponente[]}
     */
    toJSONFilhos() {
        return [];
    }

    /**
     * Trata o identificador do filho.
     * @param {string[]} contexto
     * @param {GOVBRFormsUCComponente} _filho
     */
    getInformacaoContexto(contexto, _filho) {
        return contexto;
    }

    /**
     * Retorna a mensagem a ser exibida devido ao campo estar inválido.
     * @returns {string}
     */
    get mensagemInvalida() {
        const titulo = this.dataTitulo || this.titulo;
        if (this._input) {
            if (this.valor && this._input.validationMessage) {
                return this._input.validationMessage;
            }
            return 'O valor do campo ' + titulo + ' ' + (this.valor ? 'está inválido.' : 'está vazio.');
        }
        return 'O valor do componente ' + titulo + '.';
    }

    /**
     * Retorna o valor armazenado do componente.
     * @returns {string|null}
     */
    get valor() {
        if (this._input) {
            /**
             * @type {GOVBRFormsUCComponente}
             */
            let atual = this;
            while (atual.pai) {
                if (!atual.isVisivel()) {
                    return null;
                }
                atual = atual.pai;
            }
            if (!atual.isVisivel()) {
                return null;
            }
            return this._input.value;
        }
        return null;
    }

    /**
     * Altera o valor armazenado do componente.
     */
    set valor(val) {
        if (this._input) {
            const valAnterior = this._input.value;
            this._input.value = val;
            if (valAnterior !== val) {
                this._input.dispatchEvent(new Event('change'));
            }
        }
    }

    /**
     * Retorna o tipo do componente para ser armazenado no servidor.
     */
    get tipo() {
        return '';
    }

    /**
     * Retorna o título usado para indicar o componente.
     */
    get titulo() {
        return '';
    }

    get dataVisivel() {
        return this._dataVisivel !== '0';
    }

    isVisivel() {
        return this.dataVisivel && (!this.pai || this.pai.dataVisivel);
    }

    /**
     * @param {boolean|number} val
     */
    set dataVisivel(val) {
        this._dataVisivel = val == 1 ? '1' : '0';
    }

    /**
     * @returns {number}
     */
    get colunasMax() {
        let colunasMax;
        if (this.pai) {
            colunasMax = this.pai.dataColunas;
        } else {
            colunasMax = this.form.propriedades['colunas'];
        }
        return parseInt(colunasMax) || 1;
    }

    /**
     * @returns {number}
     */
    get dataColunasOcupadas() {
        return parseInt(this._dataColunasOcupadas) || 1;
    }

    /**
     * @returns {number}
     */
    get dataLinhasOcupadas() {
        return parseInt(this._dataLinhasOcupadas) || 1;
    }

    /**
     * @param {string} val
     */
    set dataColunasOcupadas(val) {
        const valNum = parseInt(val);
        if (valNum > this.colunasMax) {
            val = '' + this.colunasMax;
        } else if (valNum < 1) {
            val = '1';
        }
        this._dataColunasOcupadas = val;
    }

    /**
     * @param {string} val
     */
    set dataLinhasOcupadas(val) {
        if (parseInt(val) < 1) {
            val = '1';
        }
        this._dataLinhasOcupadas = val;
    }

    get dataIdentificador() {
        return this._dataIdentificador ? this._dataIdentificador : this.identificadorOriginal;
    }

    set dataIdentificador(val) {
        this._dataIdentificador = val;
    }

    get identificador() {
        if (!this.form.previsualizando) {
            return this.identificadorOriginal;
        }
        const contexto = this.getContextoContainersMultilinha();
        if (contexto?.length) {
            return contexto.join('_') + '_' + this.identificadorOriginal;
        }
        return this.identificadorOriginal;
    }

    /**
     * Calcula e retorna o contexto com base nos containers multilinha onde o elemento se encontra
     * @returns {string[]}
     */
    getContextoContainersMultilinha() {
        /**
         * @type {string[]}
         */
        let contexto = [];
        /**
         * @type {GOVBRFormsUCComponente}
         */
        let atual = this;
        let filho = atual;
        while (atual.pai) {
            atual = atual.pai;
            atual.getInformacaoContexto(contexto, filho);
            filho = atual;
        }
        return contexto;
    }

    get identificadorOriginal() {
        return this._identificador ? this._identificador : '';
    }

    set identificador(val) {
        this._identificador = val;
    }

    /**
     * Retorna se o componente é um container (pode guardar outros componentes).
     * @returns {this is GOVBRFormsUCComponenteContainer}
     */
    get isContainer() {
        return false;
    }
}

/** @typedef {'texto'|'textarea'|'editor'|'numero'|'data-hora'|'lista'|'booleano'|'telefone'|'email'|'cpf-cnpj'} GOVBRFormsUCComponenteInputFormatos */

/**
 * Componente para campos/inputs.
 */
export class GOVBRFormsUCComponenteInput extends GOVBRFormsUCComponente {
    /**
     * @type {string}
     */
    _dataMinimoCaracteres;
    /**
     * @type {string}
     */
    _dataMaximoCaracteres;
    /**
     * @type {string}
     */
    _dataObrigatorio;
    /**
     * @type {string}
     */
    _dataLeitura;
    /**
     * @type {string}
     */
    dataValorDefault;
    /**
     * @type {string}
     */
    dataHint;

    /**
     * @type {((val: string) => (boolean|string))[]}
     */
    _funcoesValida = [];

    /**
     * Monta a visualização do componente quando este é apendado em tela.
     * @returns {HTMLElement}
     */
    montaVisualizacao() {
        const base = super.montaVisualizacao();
        base.classList.add('govbr-forms-componente-input');
        base.classList.add('govbr-forms-componente-input-' + this.dataFormato);
        const titulo = document.createElement('label');
        titulo.innerText = this.dataTitulo || 'Campo';
        if (this.dataObrigatorio) {
            titulo.innerText += '*';
        }
        titulo.classList.add('govbr-forms-componente-input-titulo');
        titulo.setAttribute('for', this.form.formulario.Id + '_' + this.identificador);
        base.append(titulo);
        this.montaCampo(base);
        return base;
    }

    /**
     * Monta a representação funcional do campo.
     * @param {HTMLElement} base
     */
    montaCampo(base) {
        const input = this.montaBaseCampo();
        input.id = this.form.formulario.Id + '_' + this.identificador;
        input.name = this.identificador;
        input.classList.add('govbr-forms-componente-input-campo');
        input.addEventListener('focus', () => {
            this.form.disparaEventos('entrar', this.eventos, this.getContextoContainersMultilinha());
        });
        input.addEventListener('change', () => {
            this.aplicaValidacaoCustomizada(input, this.getContextoContainersMultilinha());
            this.form.disparaEventos('mudar', this.eventos, this.getContextoContainersMultilinha());
        });
        input.addEventListener('blur', () => {
            this.form.disparaEventos('sair', this.eventos, this.getContextoContainersMultilinha());
        });
        input.addEventListener('input', () => {
            this.aplicaValidacaoCustomizada(input, this.getContextoContainersMultilinha());
        });
        if (this.dataObrigatorio && this.isVisivel()) {
            input.required = true;
        }
        if (this.dataLeitura || this.form.userControl.SOMENTE_LEITURA) {
            if (input instanceof HTMLSelectElement) {
                input.disabled = true;
            } else {
                input.readOnly = true;
            }
            input.addEventListener('click', (e) => e.preventDefault());
        }
        if (this.dataHint) {
            const containerCampo = document.createElement('div');
            containerCampo.classList.add('govbr-forms-componente-input-container-campo');
            containerCampo.append(input);
            const botaoHint = document.createElement('button');
            botaoHint.type = 'button';
            botaoHint.classList.add('govbr-forms-componente-input-hint');
            botaoHint.classList.add('fa-solid');
            botaoHint.classList.add('fa-info-circle');
            botaoHint.addEventListener('click', (event) => {
                event.preventDefault();
                const suggest = this.form.abreSuggest(botaoHint);
                const textoHint = document.createElement('p');
                textoHint.classList.add('govbr-forms-componente-hint');
                textoHint.innerText = this.dataHint;
                suggest.append(textoHint);
            });
            containerCampo.append(botaoHint);
            base.append(containerCampo);
        } else {
            base.append(input);
        }
        return input;
    }

    /**
     * Monta o elemento base para a representação funcional do campo.
     * @returns {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement}
     */
    montaBaseCampo() {
        const input = document.createElement('input');
        input.type = this.tipoCampo;
        this._input = input;
        if (this.dataMinimoCaracteres) {
            input.minLength = parseInt(this.dataMinimoCaracteres);
        }
        if (this.dataMaximoCaracteres) {
            input.maxLength = parseInt(this.dataMaximoCaracteres);
        }
        return input;
    }

    /**
     * Monta a visualização do componente quando este é apendado ao editor.
     * @returns {HTMLElement}
     */
    montaVisualizacaoEditor() {
        const base = super.montaVisualizacaoEditor();
        base.classList.add('govbr-forms-componente-input');
        base.classList.add('govbr-forms-componente-input-' + this.dataFormato);
        if (this.dataObrigatorio) {
            base.classList.add('govbr-forms-componente-input-obrigatorio');
        }
        if (this.dataLeitura) {
            base.classList.add('govbr-forms-componente-input-readonly');
        }
        const titulo = document.createElement('label');
        titulo.innerText = (this.dataTitulo || 'Campo') + (' (' + this.tituloFormato + ')');
        if (this.dataObrigatorio) {
            titulo.innerText += '*';
        }
        titulo.classList.add('govbr-forms-componente-input-titulo');
        base.append(titulo);
        this.montaRepresentacaoCampo(base);
        return base;
    }

    /**
     * Monta a representação visual do campo.
     * @param {HTMLElement} base
     */
    montaRepresentacaoCampo(base) {
        const input = document.createElement('span');
        input.classList.add('govbr-forms-componente-input-campo');
        input.innerText = this.getConteudoTextualCampo();
        base.append(input);
    }

    /**
     * Monta a representação visual do conteúdo textual do campo.
     */
    getConteudoTextualCampo() {
        if (this.dataValorDefault) {
            return this.dataValorDefault;
        }
        return 'ABC';
    }

    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        const campoTitulo = document.createElement('input');
        campoTitulo.type = 'text';
        campoTitulo.id = 'dataTitulo';
        campoTitulo.classList.add('govbr-forms-propriedades-campo-input');
        campoTitulo.setAttribute('data-order', '1');
        campoTitulo.setAttribute('data-hint', 'Título do campo, exibido para o Usuário quando este estiver preenchendo o Formulário.');
        campoTitulo.addEventListener('blur', () => {
            if (!this._dataIdentificador && campoTitulo.value) {
                let valor = campoTitulo.value || '';
                valor = valor
                    .toLowerCase()
                    .normalize('NFD')
                    .replace(/\s+/g, '-')
                    .replace(/[^0-9a-z-]/g, '');
                this.dataIdentificador = valor;
                /**
                 * @type {HTMLInputElement}
                 */
                const identificador = this.form.elPropriedades.querySelector('#dataIdentificador');
                identificador.value = this.dataIdentificador;
            }
        });
        const campoValorPadrao = document.createElement('input');
        campoValorPadrao.type = 'text';
        campoValorPadrao.id = 'dataValorDefault';
        campoValorPadrao.classList.add('govbr-forms-propriedades-campo-input');
        campoValorPadrao.setAttribute('data-hint', 'Valor padrão para o campo, carregado quando aberto a tela e não há nenhum valor informado.');
        const campoHint = document.createElement('textarea');
        campoHint.id = 'dataHint';
        campoHint.classList.add('govbr-forms-propriedades-campo-input');
        campoHint.setAttribute('data-order', '3');
        campoHint.setAttribute('data-hint', 'Texto de ajuda, exibido ao lado do campo, auxilia o Usuário a preencher o formulário. Descrever neste o que o usuário deve preencher');
        const campoMinCaracteres = document.createElement('input');
        campoMinCaracteres.type = 'number';
        campoMinCaracteres.id = 'dataMinimoCaracteres';
        campoMinCaracteres.step = '1';
        campoMinCaracteres.min = '0';
        campoMinCaracteres.classList.add('govbr-forms-propriedades-campo-input');
        campoMinCaracteres.setAttribute('data-hint', 'Indica o mínimo de caractéres que o Usuário deve informar para o campo.');
        const campoMaxCaracteres = document.createElement('input');
        campoMaxCaracteres.type = 'number';
        campoMaxCaracteres.id = 'dataMaximoCaracteres';
        campoMaxCaracteres.step = '1';
        campoMaxCaracteres.min = '0';
        campoMaxCaracteres.classList.add('govbr-forms-propriedades-campo-input');
        campoMaxCaracteres.setAttribute('data-hint', 'Indica o máximo de caractéres que o Usuário pode informar para o campo.');
        const campoObrigatorio = document.createElement('input');
        campoObrigatorio.type = 'checkbox';
        campoObrigatorio.id = 'dataObrigatorio';
        campoObrigatorio.classList.add('govbr-forms-propriedades-campo-input');
        campoObrigatorio.setAttribute('data-hint', 'Marca o campo como obrigatório, campos obrigatórios devem ser preenchidos pelos Usuário para enviar o formulário.');
        const campoLeitura = document.createElement('input');
        campoLeitura.type = 'checkbox';
        campoLeitura.id = 'dataLeitura';
        campoLeitura.classList.add('govbr-forms-propriedades-campo-input');
        campoLeitura.setAttribute(
            'data-hint',
            'Marca o campo como somente leitura, campos somente leitura não podem ter seu valor alterado pelo Usuário ao preencher o formulário.'
        );
        retorno['Título'] = campoTitulo;
        retorno['Texto de Ajuda'] = campoHint;
        retorno['Valor Padrão'] = campoValorPadrao;
        /**
         * @type {Record<string, HTMLElement>}
         */
        const propsCampo = {};
        if (this.getPodeAlterarMaxCaracteres()) {
            propsCampo['Mínimo de Caractéres'] = campoMinCaracteres;
            propsCampo['Máximo de Caractéres'] = campoMaxCaracteres;
        }
        propsCampo['Obrigatório'] = campoObrigatorio;
        propsCampo['Somente Leitura'] = campoLeitura;
        retorno['Propriedades do Campo'] = propsCampo;
        return retorno;
    }

    /**
     * Retorna se pode ou não alterar o máximo de caractéres.
     * @returns {boolean}
     */
    getPodeAlterarMaxCaracteres() {
        return true;
    }

    /**
     * Aplica a função de validação customizada do campo.
     * @param {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement} input
     * @param {string[]} contexto
     * @returns {boolean}
     */
    aplicaValidacaoCustomizada(input, contexto) {
        input = input || this._input;
        /**
         * @param {((val: string) => boolean|string)[]} validacoes
         */
        const aplicaValidacaoCustomizada = (validacoes) => {
            const funcoes = validacoes.slice();
            /**
             * @type {boolean|string}
             */
            let retorno = true;
            while (retorno === true && funcoes.length) {
                const funcao = funcoes.pop();
                retorno = funcao(input.value);
            }
            if (retorno === false) {
                input.setCustomValidity('Forneça um valor válido para o campo.');
            } else if (retorno !== true) {
                input.setCustomValidity(retorno.toString());
            }
            return retorno !== true;
        };
        if (
            this.validacoes.length &&
            aplicaValidacaoCustomizada(
                this.validacoes.map((validacao) => {
                    return () => {
                        /**
                         * @type {boolean|string}
                         */
                        let retorno = true;
                        validacao.Condicionantes.some((condicionante) => {
                            if (this.form.validaCondicional(condicionante.Condicional, contexto)) {
                                retorno = condicionante.Resultado.Mensagem || condicionante.Resultado.Retorno;
                                return true;
                            }
                        });
                        return retorno;
                    };
                })
            )
        ) {
            return;
        }
        if (this.funcoesValida.length && aplicaValidacaoCustomizada(this.funcoesValida)) {
            return;
        }
        input.setCustomValidity('');
    }

    /**
     * Monta botões para ações adicionais para o componente.
     * @param {HTMLElement} alvo
     */
    montaAcoesAdicionais(alvo) {
        const botaoValidacoes = document.createElement('button');
        botaoValidacoes.className = 'govbr-forms-propriedades-acoes-botao govbr-forms-propriedades-acoes-validacoes';
        botaoValidacoes.type = 'button';
        botaoValidacoes.innerText = 'Validações';
        botaoValidacoes.title = 'Validações';
        if (!this.validacoes) {
            this.validacoes = [];
        }
        botaoValidacoes.addEventListener('click', () => {
            GOVBRFormsUCConfiguradorEventos.abreJanelaConfiguracaoValidacoes(
                this.form,
                (this.dataTitulo || '') + ' - ' + this.titulo,
                this.validacoes,
                (resultado) => (this.validacoes = resultado),
                this.identificador,
                this.getContextoContainersMultilinha()
            );
        });
        const iconeValidacoes = document.createElement('i');
        iconeValidacoes.className = 'fa-solid fa-check-double';
        botaoValidacoes.prepend(iconeValidacoes);
        alvo.append(botaoValidacoes);

        const botaoEventos = document.createElement('button');
        botaoEventos.className = 'govbr-forms-propriedades-acoes-botao govbr-forms-propriedades-acoes-eventos';
        botaoEventos.type = 'button';
        botaoEventos.innerText = 'Eventos';
        botaoEventos.title = 'Eventos';
        if (!this.eventos) {
            this.eventos = [];
        }
        botaoEventos.addEventListener('click', () => {
            GOVBRFormsUCConfiguradorEventos.abreJanelaConfiguracaoEventos(
                this.form,
                (this.dataTitulo || '') + ' - ' + this.titulo,
                this.eventos,
                [
                    ['entrar', 'Ao Entrar', 'Evento disparado ao usuário entrar no campo para iniciar o preenchimento.'],
                    ['sair', 'Ao Sair', 'Evento disparado ao usuário sair do campo, independente dele ter alterado ou não o valor.'],
                    ['mudar', 'Ao Mudar o Valor', 'Evento disparado ao usuário sair do campo após ter alterado o valor dele.'],
                ],
                (resultado) => (this.eventos = resultado),
                this.identificador,
                this.getContextoContainersMultilinha()
            );
        });
        const iconeEventos = document.createElement('i');
        iconeEventos.className = 'fa-solid fa-forward';
        botaoEventos.prepend(iconeEventos);
        alvo.append(botaoEventos);
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return super.toJSONPropriedades().concat([
            { Nome: 'titulo', Valor: this.dataTitulo },
            { Nome: 'valorDefault', Valor: this.dataValorDefault },
            { Nome: 'minimoCaracteres', Valor: this.dataMinimoCaracteres },
            { Nome: 'maximoCaracteres', Valor: this.dataMaximoCaracteres },
            { Nome: 'obrigatorio', Valor: this.dataObrigatorio ? '1' : '0' },
            { Nome: 'leitura', Valor: this.dataLeitura ? '1' : '0' },
            { Nome: 'hint', Valor: this.dataHint },
            { Nome: 'formato', Valor: this.dataFormato },
        ]);
    }

    /**
     * Retorna as funções que recebem um valor e retorna se esta e válida. Deve retornar true se for válida, false se não for, ou uma string para invalidar e exibir uma mensagem customizada.
     * @return {((val: string) => (boolean|string))[]}
     */
    get funcoesValida() {
        return this._funcoesValida || [];
    }

    /**
     * Retorna o tipo do componente para ser armazenado no servidor.
     */
    get tipo() {
        return 'input';
    }

    /**
     * Retorna o tipo do campo no HTML.
     */
    get tipoCampo() {
        return 'text';
    }

    /**
     * Retorna o título usado para indicar o componente.
     */
    get titulo() {
        return 'Campo de formato ' + this.tituloFormato;
    }

    /**
     * Retorna o formato do campo.
     * @returns {GOVBRFormsUCComponenteInputFormatos}
     */
    get dataFormato() {
        return 'texto';
    }

    /**
     * Retorna o título do campo com base no formato.
     */
    get tituloFormato() {
        return 'Texto';
    }

    get dataMinimoCaracteres() {
        return this._dataMinimoCaracteres || '';
    }

    set dataMinimoCaracteres(val) {
        if (parseInt(val) < 0) {
            val = '0';
        }
        this._dataMinimoCaracteres = val;
    }

    get dataMaximoCaracteres() {
        return this._dataMaximoCaracteres || '';
    }

    set dataMaximoCaracteres(val) {
        if (parseInt(val) < 0) {
            val = '0';
        }
        this._dataMaximoCaracteres = val;
    }

    get dataObrigatorio() {
        return this._dataObrigatorio === '1';
    }

    /**
     * @param {boolean|number} val
     */
    set dataObrigatorio(val) {
        this._dataObrigatorio = val == 1 ? '1' : '0';
    }

    get dataLeitura() {
        return this._dataLeitura === '1';
    }

    /**
     * @param {boolean|number} val
     */
    set dataLeitura(val) {
        this._dataLeitura = val == 1 ? '1' : '0';
    }
}

/**
 * Componente para campos/inputs de Texto.
 */
export class GOVBRFormsUCComponenteInputTexto extends GOVBRFormsUCComponenteInput {
    /**
     * @type {string}
     */
    _dataMascara;

    /**
     * Monta o elemento base do campo.
     * @returns {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement}
     */
    montaBaseCampo() {
        const input = /** @type {HTMLInputElement} */ (super.montaBaseCampo());
        if (this.dataMascara) {
            input.placeholder = this.getConteudoTextualCampo();
            this.criaMascaraCampo(input);
        }
        return input;
    }

    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        const propriedades = /** @type {Record<string, HTMLInputElement>} */ (retorno['Propriedades do Campo']);

        const campoMascara = document.createElement('input');
        campoMascara.type = 'text';
        campoMascara.id = 'dataMascara';
        campoMascara.classList.add('govbr-forms-propriedades-campo-input');
        campoMascara.setAttribute('data-order', '2');
        campoMascara.setAttribute(
            'data-hint',
            'Máscara do Campo, utilize os caractéres coringa: "9": Qualquer número, "X": Qualquer letra, "Z": Qualquer letra ou número. Utilize o caractére PIPE (|) para separar uma máscara da outra. Por exemplo, para a máscara "Z.9-X", o Usuário deverá informar uma letra ou número, um ponto, um número, um traço e após, uma letra.'
        );
        campoMascara.addEventListener('change', () => {
            if (!this.getPodeAlterarMaxCaracteres()) {
                return;
            }
            // Timeout para aguardar o valor do atributo atualizar antes de atualizar os outros campos, pois este evento dispara antes do evento do FormsUC que atualiza o valor do atributo.
            setTimeout(() => {
                if (this.dataMascara) {
                    propriedades['Máximo de Caractéres'].readOnly = true;
                    propriedades['Máximo de Caractéres'].value = '';
                    this.dataMinimoCaracteres = '';
                    this.dataMaximoCaracteres = '';
                } else {
                    propriedades['Máximo de Caractéres'].readOnly = false;
                }
            }, 0);
        });
        if (this.dataMascara && this.getPodeAlterarMaxCaracteres()) {
            propriedades['Máximo de Caractéres'].readOnly = true;
        }
        propriedades['Máscara'] = campoMascara;
        return retorno;
    }

    /**
     * Monta a representação visual do conteúdo textual do campo.
     */
    getConteudoTextualCampo() {
        if (this.dataValorDefault) {
            return this.dataValorDefault;
        }
        return this.dataMascara ? this.dataMascara.split('|').join(' ou ') : 'ABC';
    }

    /**
     * Trata o comportamento de máscara do campo.
     * @param {HTMLInputElement} campo
     */
    criaMascaraCampo(campo) {
        campo.addEventListener('focus', (event) => this.aplicaMascaraInicialCampo(event));
        campo.addEventListener('input', (event) => this.trataMascaraCampo(event));
        campo.addEventListener('change', (event) => this.trataMascaraCampo(event));
        campo.addEventListener('blur', (event) => this.trataMascaraCampo(event));
    }

    /**
     * Aplica a masca inicial ao focar no campo.
     * @param {Event} event
     */
    aplicaMascaraInicialCampo(event) {
        const input = /** @type {HTMLInputElement} */ (event.target);
        let mascaras = this.dataMascara.split('|');
        if (Array.isArray(mascaras) && mascaras.length) {
            // Formata a máscara, removendo caractéres especiais e textos e aplica esta no campo.
            const valorOriginal = input.value || '';
            const valorFormatado = valorOriginal.replace(/[^a-zA-Z0-9]/g, '');
            if (valorFormatado.length === 0) {
                mascaras.sort((a, b) => {
                    return a.length - b.length;
                });
                const mascaraAplicar = mascaras[0];
                input.value = /^([^0-9a-zA-Z]*)/.exec(mascaraAplicar)[0];
                input.setSelectionRange(input.value.length, input.value.length);
            } else {
                this.trataMascaraCampo(event);
            }
        }
    }

    /**
     * Trata a máscara de um campo.
     * @param {Event} event
     */
    trataMascaraCampo(event) {
        if (event instanceof InputEvent && event.inputType === 'deleteContentBackward') {
            return;
        }
        const input = /** @type {HTMLInputElement} */ (event.target);
        let mascaras = this.dataMascara.split('|');
        if (Array.isArray(mascaras) && mascaras.length) {
            let start = input.selectionStart;
            let end = input.selectionEnd;
            // Formata a máscara, removendo caractéres especiais e textos e aplica esta no campo.
            const valorOriginal = input.value || '';
            const valorFormatado = valorOriginal.replace(/[^a-zA-Z0-9]/g, '');
            if (valorFormatado.length === 0) {
                input.value = '';
                input.setCustomValidity('');
            } else {
                const posicaoDireita = end - input.value.length;
                mascaras.sort((a, b) => {
                    return a.length - b.length;
                });
                const mascaraAplicar =
                    mascaras.find((mascara) => {
                        const mascaraFormatada = mascara.replace(/[^a-zA-Z0-9]/g, '');
                        return mascaraFormatada.length >= valorFormatado.length;
                    }) || mascaras[mascaras.length - 1];
                let valorResultante = '';
                let idxValorOriginal = 0;
                /**
                 * @param {string} caractere
                 */
                const fnAplicaMascara = (caractere) => {
                    const caractereOriginal = valorFormatado[idxValorOriginal];
                    let bateuMascara = false;
                    switch (caractere) {
                        case '9':
                            // Qualquer número
                            bateuMascara = /\d/.test(caractereOriginal);
                            break;
                        case 'X':
                            // Qualquer letra
                            bateuMascara = /[a-zA-Z]]/.test(caractereOriginal);
                            break;
                        case 'Z':
                            // Qualquer letra ou número
                            bateuMascara = /[0-9a-zA-Z]/.test(caractereOriginal);
                            break;
                        default:
                            // Se não for nenhum, salva o caractére da máscara
                            valorResultante += caractere;
                            if (caractere === caractereOriginal) {
                                idxValorOriginal++;
                            }
                            return true;
                    }
                    // Usuário ainda não informou um valor para este item.
                    if (!caractereOriginal) {
                        return false;
                    }
                    // Aumenta o idx para avaliar o próximo caractére digitado.
                    idxValorOriginal++;
                    // Se bateu a mascara, armazena o valor digitado.
                    if (bateuMascara) {
                        valorResultante += caractereOriginal;
                        return true;
                    }
                    // Senão tenta aplicar a mascara sobre o próximo caractére.
                    return fnAplicaMascara(caractere);
                };
                mascaraAplicar.split('').every(fnAplicaMascara);
                input.value = valorResultante;
                let novaPosicaoDireita = end - input.value.length;
                while (novaPosicaoDireita < posicaoDireita) {
                    novaPosicaoDireita++;
                    start++;
                    end++;
                }
                // Se não completou a máscara, marca o campo como inválido.
                if (mascaraAplicar.length > input.value.length) {
                    input.setCustomValidity('O valor deve equivaler ' + (mascaras.length > 1 ? `as máscaras ${mascaras.join(', ')}` : `a máscara ${mascaras[0]}`) + '.');
                } else {
                    this.aplicaValidacaoCustomizada(input, this.getContextoContainersMultilinha());
                }
            }
            input.setSelectionRange(start, end);
        }
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return super.toJSONPropriedades().concat([
            { Nome: 'mascara', Valor: this.dataMascara },
        ]);
    }

    /**
     * Retorna o título do campo com base no formato.
     * @type {string}
     */
    get tituloFormato() {
        if (this._dataMascara) {
            return 'Texto com Máscara';
        }
        return 'Texto';
    }

    /**
     * Retorna o formato do campo.
     * @returns {GOVBRFormsUCComponenteInputFormatos}
     */
    get dataFormato() {
        return 'texto';
    }

    get dataMascara() {
        return this._dataMascara || '';
    }

    set dataMascara(val) {
        this._dataMascara = val;
    }
}

/**
 * Componente para campos/inputs de Texto Multilinha.
 */
export class GOVBRFormsUCComponenteInputTextArea extends GOVBRFormsUCComponenteInput {
    /**
     * @type {string}
     */
    _dataNumeroLinhas;

    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        const propriedades = /** @type {Record<string, HTMLElement>} */ (retorno['Propriedades do Campo']);

        const campoNumeroLinhas = document.createElement('input');
        campoNumeroLinhas.type = 'number';
        campoNumeroLinhas.id = 'dataNumeroLinhas';
        campoNumeroLinhas.step = '1';
        campoNumeroLinhas.min = '1';
        campoNumeroLinhas.classList.add('govbr-forms-propriedades-campo-input');
        campoNumeroLinhas.setAttribute('data-hint', 'Número de linhas de altura do campo, este é o número de linhas antes de gerar rolagem vertical no campo.');
        propriedades['Número de Linhas'] = campoNumeroLinhas;
        return retorno;
    }

    /**
     * Monta o elemento base para a representação funcional do campo.
     * @returns {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement}
     */
    montaBaseCampo() {
        const input = document.createElement('textarea');
        input.style.setProperty('--numero-linhas', '' + this.dataNumeroLinhas);
        this._input = input;
        return input;
    }

    /**
     * Monta o elemento base para a representação funcional do campo.
     * @param {HTMLElement} base
     */
    montaRepresentacaoCampo(base) {
        const input = document.createElement('span');
        input.style.setProperty('--numero-linhas', '' + this.dataNumeroLinhas);
        input.classList.add('govbr-forms-componente-input-campo');
        input.innerText = this.getConteudoTextualCampo();
        base.append(input);
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return super.toJSONPropriedades().concat([
            { Nome: 'numeroLinhas', Valor: '' + this.dataNumeroLinhas },
        ]);
    }

    /**
     * Retorna se pode ou não alterar o máximo de caractéres.
     * @returns {boolean}
     */
    getPodeAlterarMaxCaracteres() {
        return false;
    }

    /**
     * Retorna o título do campo com base no formato.
     */
    get tituloFormato() {
        return 'Texto - Multilinha';
    }

    /**
     * Retorna o formato do campo.
     * @returns {GOVBRFormsUCComponenteInputFormatos}
     */
    get dataFormato() {
        return 'textarea';
    }

    /**
     * @returns {number}
     */
    get dataNumeroLinhas() {
        return parseInt(this._dataNumeroLinhas) || 4;
    }

    /**
     * @param {string} val
     */
    set dataNumeroLinhas(val) {
        if (parseInt(val) < 1) {
            val = '1';
        }
        this._dataNumeroLinhas = val;
    }
}

/**
 * Componente para campos/inputs de Texto Editor.
 */
export class GOVBRFormsUCComponenteInputEditor extends GOVBRFormsUCComponenteInput {
    /**
     * @type {ClassicEditor}
     */
    _editor;

    /**
     * Monta a representação funcional do campo.
     * @param {HTMLElement} base
     */
    montaCampo(base) {
        const input = super.montaCampo(base);
        if (this.dataLeitura || this.form.userControl.SOMENTE_LEITURA) {
            const divValor = document.createElement('div');
            divValor.classList.add('govbr-forms-componente-input-editor-valor');
            input.addEventListener('change', () => {
                divValor.innerHTML = input.value || '';
            });
            divValor.innerHTML = input.value || '';
            input.parentElement.insertBefore(divValor, input);
        }
        return input;
    }

    /**
     * Monta o elemento base para a representação funcional do campo.
     * @returns {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement}
     */
    montaBaseCampo() {
        if (this.dataLeitura || this.form.userControl.SOMENTE_LEITURA) {
            const input = document.createElement('textarea');
            input.style.display = 'none';
            this._input = input;
            return input;
        }
        const input = document.createElement('textarea');
        ClassicEditor.create(input, {
            plugins: [
                Essentials,
                Paragraph,
                Bold,
                Italic,
                Font,
                Heading,
                List,
                Link,
            ],
            toolbar: [
                'undo',
                'redo',
                '|',
                'heading',
                '|',
                'bold',
                'italic',
                'fontColor',
                '|',
                'numberedList',
                'bulletedList',
                '|',
                'link',
                'accessibilityHelp',
            ],
            language: 'pt-br',
            translations,
        })
            .then((editor) => {
                this._editor = editor;
                this._editor.setData(this._input.value);
            })
            .catch((error) => {
                GOVBR.logs.error(error);
            });
        this._input = input;
        return input;
    }

    /**
     * Retorna se pode ou não alterar o máximo de caractéres.
     * @returns {boolean}
     */
    getPodeAlterarMaxCaracteres() {
        return false;
    }

    /**
     * Retorna o valor armazenado do componente.
     */
    get valor() {
        if (this._input) {
            /**
             * @type {GOVBRFormsUCComponente}
             */
            let atual = this;
            while (atual.pai) {
                if (!atual.isVisivel()) {
                    return null;
                }
                atual = atual.pai;
            }
            if (!atual.isVisivel()) {
                return null;
            }
            this._input.value = this._editor?.getData() || this._input.value;
            return this._input.value;
        }
        return null;
    }

    /**
     * Altera o valor armazenado do componente.
     */
    set valor(val) {
        if (this._input) {
            const valAnterior = this._input.value;
            this._input.value = val;
            if (valAnterior !== val) {
                this._input.dispatchEvent(new Event('change'));
            }
            if (this._editor) {
                this._editor.setData(this._input.value);
            }
        }
    }

    /**
     * Retorna o título do campo com base no formato.
     */
    get tituloFormato() {
        return 'Texto - Formatado';
    }

    /**
     * Retorna o formato do campo.
     * @returns {GOVBRFormsUCComponenteInputFormatos}
     */
    get dataFormato() {
        return 'editor';
    }
}

/**
 * Componente para campos/inputs de número.
 */
export class GOVBRFormsUCComponenteInputNumerico extends GOVBRFormsUCComponenteInput {
    /**
     * @type {string}
     */
    _dataCasasDecimais;
    /**
     * @type {string}
     */
    _dataValorMinimo;
    /**
     * @type {string}
     */
    _dataValorMaximo;

    /**
     * Monta o elemento base do campo.
     * @returns {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement}
     */
    montaBaseCampo() {
        const input = /**@type {HTMLInputElement}*/ (super.montaBaseCampo());
        let step;
        if (this.dataCasasDecimais == 0) {
            step = '1';
        } else {
            step = '0.' + '0'.repeat(this.dataCasasDecimais - 1) + '1';
        }
        input.step = step;
        if (this.dataValorMinimo !== '') {
            input.min = this.dataValorMinimo;
        }
        if (this.dataValorMaximo !== '') {
            input.max = this.dataValorMaximo;
        }
        let maxLength = parseInt(this.dataMaximoCaracteres);
        input.removeAttribute('maxLength');
        input.addEventListener('input', () => {
            if (!input.readOnly) {
                let val = input.value.replace(/\./g, '').replace(/,/g, '.');
                /**
                 * @param {string} val
                 */
                const fnParseItem = (val) => {
                    if (step && parseFloat(step) < 1) {
                        const valNum = parseFloat(val);
                        if (!isNaN(valNum)) {
                            const decimais = step.split('.')[1].length;
                            val = valNum.toFixed(decimais);
                        }
                        return val;
                    }
                    return parseInt(val);
                };
                if (isNaN(/** @type {any} */ (val))) {
                    input.value = val;
                }
                if (this.dataValorMinimo && fnParseItem(val) < fnParseItem(this.dataValorMinimo)) {
                    val = this.dataValorMinimo;
                } else if (this.dataValorMaximo && fnParseItem(val) > fnParseItem(this.dataValorMaximo)) {
                    val = this.dataValorMaximo;
                }
                let start = input.selectionStart;
                let end = input.selectionEnd;
                val = (fnParseItem(val) || '') + '';
                val = val.replace(/,/g, '').replace(/\./g, ',');
                if (this.dataMaximoCaracteres === '') {
                    input.value = val;
                } else if (val.indexOf(',') < maxLength) {
                    input.value = val.substring(0, maxLength + 1);
                } else {
                    input.value = val.substring(0, maxLength);
                }
                input.selectionStart = start;
                input.selectionEnd = end;
            }
        });
        return input;
    }

    /**
     * Monta a representação visual do conteúdo textual do campo.
     */
    getConteudoTextualCampo() {
        if (this.dataCasasDecimais == 0) {
            return '0';
        }
        return '0,' + '0'.repeat(this.dataCasasDecimais);
    }

    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        const propriedades = /** @type {Record<string, HTMLElement | Record<string, HTMLElement>>} */ (retorno['Propriedades do Campo']);

        propriedades['Númerico'] = {};
        const campoValorMinimo = document.createElement('input');
        campoValorMinimo.type = 'number';
        campoValorMinimo.id = 'dataValorMinimo';
        campoValorMinimo.step = this.dataCasasDecimais === 0 ? '1' : '0.' + '0'.repeat(this.dataCasasDecimais - 1) + 1;
        campoValorMinimo.classList.add('govbr-forms-propriedades-campo-input');
        campoValorMinimo.setAttribute('data-hint', 'Valor mínimo que o Usuário pode informar para o campo.');
        const campoValorMaximo = document.createElement('input');
        campoValorMaximo.type = 'number';
        campoValorMaximo.id = 'dataValorMaximo';
        campoValorMaximo.step = this.dataCasasDecimais === 0 ? '1' : '0.' + '0'.repeat(this.dataCasasDecimais - 1) + 1;
        campoValorMaximo.classList.add('govbr-forms-propriedades-campo-input');
        campoValorMaximo.setAttribute('data-hint', 'Valor mínimo que o Usuário pode informar para o campo.');

        const campoCasasDecimais = document.createElement('input');
        campoCasasDecimais.type = 'number';
        campoCasasDecimais.id = 'dataCasasDecimais';
        campoCasasDecimais.step = '1';
        campoCasasDecimais.min = '0';
        campoCasasDecimais.classList.add('govbr-forms-propriedades-campo-input');
        campoCasasDecimais.setAttribute('data-hint', 'Número de casas decimais disponíveis no campo. Se 0, apenas irá permitir inteiros.');
        campoCasasDecimais.addEventListener('change', () => {
            // Timeout para aguardar o valor do atributo atualizar antes de atualizar os outros campos, pois este evento dispara antes do evento do FormsUC que atualiza o valor do atributo.
            setTimeout(() => {
                campoValorMinimo.step = this.dataCasasDecimais === 0 ? '1' : '0.' + '0'.repeat(this.dataCasasDecimais - 1) + 1;
                campoValorMaximo.step = this.dataCasasDecimais === 0 ? '1' : '0.' + '0'.repeat(this.dataCasasDecimais - 1) + 1;
                const event = new InputEvent('input');
                campoValorMinimo.dispatchEvent(event);
                campoValorMaximo.dispatchEvent(event);
            }, 0);
        });
        propriedades['Númerico']['Casas Decimais'] = campoCasasDecimais;
        propriedades['Númerico']['Valor Mínimo'] = campoValorMinimo;
        propriedades['Númerico']['Valor Máximo'] = campoValorMaximo;
        return retorno;
    }

    /**
     * Retorna o valor armazenado do componente.
     */
    get valor() {
        if (this._input) {
            /**
             * @type {GOVBRFormsUCComponente}
             */
            let atual = this;
            while (atual.pai) {
                if (!atual.isVisivel()) {
                    return null;
                }
                atual = atual.pai;
            }
            if (!atual.isVisivel()) {
                return null;
            }
            return (this._input.value || '').replace(/\./g, '').replace(/,/g, '.');
        }
        return null;
    }

    /**
     * Retorna o valor armazenado do componente.
     */
    set valor(val) {
        if (this._input) {
            val = (val || '').replace(/,/g, '').replace(/\./g, ',');
            const valAnterior = this._input.value;
            this._input.value = val;
            if (valAnterior !== val) {
                this._input.dispatchEvent(new Event('change'));
            }
        }
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return super.toJSONPropriedades().concat([
            { Nome: 'casasDecimais', Valor: '' + this.dataCasasDecimais },
            { Nome: 'valorMinimo', Valor: this.dataValorMinimo },
            { Nome: 'valorMaximo', Valor: this.dataValorMaximo },
        ]);
    }

    /**
     * Retorna o tipo do campo no HTML.
     */
    get tipoCampo() {
        return 'text';
    }

    /**
     * Retorna o título do campo com base no formato.
     */
    get tituloFormato() {
        return 'Numérico';
    }

    /**
     * Retorna o formato do campo.
     * @returns {GOVBRFormsUCComponenteInputFormatos}
     */
    get dataFormato() {
        return 'numero';
    }

    /**
     * @returns {number}
     */
    get dataCasasDecimais() {
        return parseInt(this._dataCasasDecimais) || 0;
    }

    /**
     * @param {string} val
     */
    set dataCasasDecimais(val) {
        if (parseInt(val) < 1) {
            val = '0';
        }
        this._dataCasasDecimais = val;
    }

    get dataValorMinimo() {
        return this._dataValorMinimo || '';
    }

    /**
     * @param {string} val
     */
    set dataValorMinimo(val) {
        if (val !== '' && isNaN(parseFloat(val))) {
            val = '';
        }
        this._dataValorMinimo = val;
    }

    get dataValorMaximo() {
        return this._dataValorMaximo || '';
    }

    set dataValorMaximo(val) {
        if (val !== '' && isNaN(parseFloat(val))) {
            val = '';
        }
        this._dataValorMaximo = val;
    }
}

/**
 * Componente para campos/inputs de Data/Hora.
 */
export class GOVBRFormsUCComponenteInputDataHora extends GOVBRFormsUCComponenteInput {
    /**
     * @type {string}
     */
    _dataMostraHora;

    /**
     * Monta a representação visual do conteúdo textual do campo.
     */
    getConteudoTextualCampo() {
        return this.dataMostraHora ? '01/01/2000 00:00:00' : '01/01/2000';
    }

    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        const propriedades = /** @type {Record<string, HTMLElement>} */ (retorno['Propriedades do Campo']);

        const campoMostraHora = document.createElement('input');
        campoMostraHora.type = 'checkbox';
        campoMostraHora.id = 'dataMostraHora';
        campoMostraHora.classList.add('govbr-forms-propriedades-campo-input');
        campoMostraHora.setAttribute('data-hint', 'Se deve mostrar a hora, trabalhando com data e hora, ou somente a data.');
        propriedades['Mostra a Hora'] = campoMostraHora;
        return retorno;
    }

    /**
     * Monta o elemento base do campo.
     * @returns {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement}
     */
    montaBaseCampo() {
        const input = /** @type {HTMLInputElement} */ (super.montaBaseCampo());
        if (this.dataMostraHora) {
            input.step = '1';
        }
        return input;
    }

    /**
     * Retorna se pode ou não alterar o máximo de caractéres.
     * @returns {boolean}
     */
    getPodeAlterarMaxCaracteres() {
        return false;
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return super.toJSONPropriedades().concat([
            { Nome: 'mostraHora', Valor: this.dataMostraHora ? '1' : '0' },
        ]);
    }

    /**
     * Retorna o tipo do campo no HTML.
     */
    get tipoCampo() {
        return this.dataMostraHora ? 'datetime-local' : 'date';
    }

    /**
     * Retorna o título do campo com base no formato.
     */
    get tituloFormato() {
        if (!this.dataMostraHora) {
            return 'Data';
        }
        return 'Data/Hora';
    }

    /**
     * Retorna o formato do campo.
     * @returns {GOVBRFormsUCComponenteInputFormatos}
     */
    get dataFormato() {
        return 'data-hora';
    }

    get dataMostraHora() {
        return this._dataMostraHora !== '0';
    }

    /**
     * @param {number|boolean} val
     */
    set dataMostraHora(val) {
        this._dataMostraHora = val == 1 ? '1' : '0';
    }
}

/**
 * Componente para campos/inputs de Lista/Seleção.
 */
export class GOVBRFormsUCComponenteInputLista extends GOVBRFormsUCComponenteInput {
    /**
     * @type {string}
     */
    _dataMultiSelecao;
    /**
     * @type {string}
     */
    _dataInline;
    /**
     * @type {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlComponenteListaItem[]}
     */
    _dataLista;

    /**
     * Monta a representação funcional do campo.
     * @param {HTMLElement} base
     */
    montaCampo(base) {
        const input = /** @type {HTMLInputElement} */ (super.montaCampo(base));
        input.setAttribute('tabindex', '-1');
        const containerLista = this.montaContainerSelecao(input);
        /**
         * @type {HTMLElement}
         */
        let titulo;
        /**
         * @type {HTMLElement}
         */
        let dropdown;
        if (this.dataInline) {
            input.parentElement.insertBefore(containerLista, input);
        } else {
            dropdown = document.createElement('div');
            dropdown.tabIndex = 0;
            dropdown.classList.add('govbr-forms-input-lista-dropdown');
            /**
             * @type {HTMLElement}
             */
            let suggest;
            if (this.dataLeitura || this.form.userControl.SOMENTE_LEITURA) {
                dropdown.classList.add('govbr-forms-input-lista-dropdown-leitura');
            } else {
                const toggleDropdown = () => {
                    if (suggest) {
                        return;
                    }
                    suggest = this.form.abreSuggest(dropdown, () => {
                        suggest = null;
                        dropdown.focus();
                    });
                    suggest.classList.add('govbr-forms-suggest-lista-dropdown');
                    suggest.append(containerLista);
                };
                dropdown.addEventListener('keydown', (e) => {
                    if (e.key === 'Enter' || e.key === 'Arrow Down') {
                        toggleDropdown();
                        if (suggest) {
                            // @TODO isto deveria ser controlado pelo suggest
                            setTimeout(() => {
                                /**
                                 * @type {HTMLElement}
                                 */
                                const item = suggest.querySelector('li[tabindex="0"]');
                                item?.focus?.();
                            }, 50);
                        }
                    }
                });
                dropdown.addEventListener('click', () => {
                    toggleDropdown();
                });
            }

            titulo = document.createElement('span');
            titulo.classList.add('govbr-forms-input-lista-dropdown-titulo');
            if (!input.value) {
                titulo.classList.add('govbr-forms-input-lista-dropdown-titulo-vazio');
            } else {
                titulo.classList.remove('govbr-forms-input-lista-dropdown-titulo-vazio');
            }
            titulo.innerText = this.getTituloValor(input.value);
            dropdown.append(titulo);

            const icone = document.createElement('i');
            icone.classList.add('fa-solid');
            icone.classList.add('fa-chevron-down');
            dropdown.append(icone);

            input.parentElement.insertBefore(dropdown, input);

            input.addEventListener('focus', () => {
                if (this.dataInline) {
                    /**
                     * @type {HTMLElement}
                     */
                    const containerListaItem = containerLista.querySelector('.govbr-forms-input-lista-container-item');
                    containerListaItem?.focus?.();
                } else {
                    dropdown.dispatchEvent(new MouseEvent('click'));
                }
            });
            input.addEventListener('blur', () => {
                this.form.fechaSuggest();
            });
        }
        input.addEventListener('change', () => {
            if (titulo) {
                if (!input.value) {
                    titulo.classList.add('govbr-forms-input-lista-dropdown-titulo-vazio');
                } else {
                    titulo.classList.remove('govbr-forms-input-lista-dropdown-titulo-vazio');
                }
                titulo.innerText = this.getTituloValor(input.value);
            }
            this.atualizaContainerSelecao(containerLista);
        });
        this.atualizaContainerSelecao(containerLista);
        return input;
    }

    /**
     * Monta o container para seleção dos itens.
     * @param {HTMLInputElement} input
     */
    montaContainerSelecao(input) {
        const container = document.createElement('ul');
        container.classList.add('govbr-forms-input-lista-container-itens');
        const lista = (this._dataLista || []).slice();
        if (this.dataMultiSelecao) {
            container.classList.add('govbr-forms-input-lista-container-multisselecao');
        } else {
            lista.unshift({ Valor: '', Nome: 'Selecione...' });
            container.classList.add('govbr-forms-input-lista-container-simples');
            if (this.dataInline) {
                container.classList.add('govbr-forms-input-lista-container-radio');
            }
        }
        lista.forEach(({ Valor: valor, Nome: nome }) => {
            const li = document.createElement('li');
            li.classList.add('govbr-forms-input-lista-container-item');
            li.setAttribute('data-valor', valor);
            li.setAttribute('tabindex', '0');
            li.innerText = nome;
            const iconeSelecionado = document.createElement('i');
            iconeSelecionado.classList.add('govbr-forms-input-lista-container-icone-selecionado');
            iconeSelecionado.classList.add('fa-regular');
            const iconeDeselecionado = document.createElement('i');
            iconeDeselecionado.classList.add('govbr-forms-input-lista-container-icone-deselecionado');
            iconeDeselecionado.classList.add('fa-regular');
            if (this.dataMultiSelecao) {
                iconeSelecionado.classList.add('fa-square-check');
                iconeDeselecionado.classList.add('fa-square');
            } else {
                iconeSelecionado.classList.add('fa-circle-dot');
                iconeDeselecionado.classList.add('fa-circle');
            }
            const ativaItem = () => {
                if (this.dataMultiSelecao) {
                    let valores;
                    try {
                        valores = JSON.parse(input.value || '[]');
                    } catch (ex) {
                        GOVBR.logs.error(ex);
                        valores = [];
                    }
                    const idx = valores.indexOf(valor);
                    if (idx > -1) {
                        valores.splice(idx, 1);
                    } else {
                        valores.push(valor);
                    }
                    input.value = JSON.stringify(valores);
                } else {
                    input.value = valor;
                    this.form.fechaSuggest();
                }
                input.dispatchEvent(new Event('change'));
            };
            li.addEventListener('keydown', (e) => {
                if (e.key === 'Enter') {
                    ativaItem();
                }
            });
            li.addEventListener('click', () => {
                ativaItem();
            });
            li.prepend(iconeSelecionado);
            li.prepend(iconeDeselecionado);
            container.append(li);
        });
        return container;
    }

    /**
     * Atualiza o(s) item(ns) selecionado(s) no container de itens.
     * @param {HTMLElement} container
     */
    atualizaContainerSelecao(container) {
        /**
         * @type {string|any[]}
         */
        let valor = this.valor;
        if (this.dataMultiSelecao) {
            try {
                valor = JSON.parse(valor || '[]');
            } catch (ex) {
                GOVBR.logs.error(ex);
                valor = [];
            }
        }
        container.querySelectorAll('.govbr-forms-input-lista-container-item').forEach((item) => {
            const valorItem = item.getAttribute('data-valor');
            if (this.dataMultiSelecao) {
                if (valor.includes(valorItem)) {
                    item.classList.add('govbr-forms-input-lista-container-item-selecionado');
                } else {
                    item.classList.remove('govbr-forms-input-lista-container-item-selecionado');
                }
            } else if (valor === valorItem) {
                item.classList.add('govbr-forms-input-lista-container-item-selecionado');
            } else {
                item.classList.remove('govbr-forms-input-lista-container-item-selecionado');
            }
        });
    }

    /**
     * Retorna o título/nome a ser exibido para um valor.
     * @param {string} novoValor
     * @returns {string}
     */
    getTituloValor(novoValor) {
        if (this.dataMultiSelecao) {
            let valores;
            try {
                valores = JSON.parse(novoValor || '[]') || [];
            } catch (ex) {
                valores = [];
                GOVBR.logs.error(ex);
            }
            return (
                (this._dataLista || [])
                    .filter(({ Valor }) => valores.includes(Valor))
                    .map((e) => e.Nome)
                    .join(', ') || 'Selecione...'
            );
        }
        return (this._dataLista || []).find(({ Valor }) => Valor === novoValor)?.Nome || 'Selecione...';
    }

    /**
     * Monta a representação visual do campo.
     * @param {HTMLElement} base
     */
    montaRepresentacaoCampo(base) {
        if (!this.dataInline) {
            return super.montaRepresentacaoCampo(base);
        }
        const input = document.createElement('div');
        input.classList.add('govbr-forms-componente-input-campo');
        input.classList.add('govbr-forms-componente-input-campo-inline');
        base.append(input);
        const icone = document.createElement('span');
        icone.classList.add('govbr-forms-componente-input-campo-icone');
        icone.classList.add('fa-regular');
        icone.classList.add(this.dataMultiSelecao ? 'fa-check-square' : 'fa-circle-dot');
        input.append(icone);
        const texto = document.createElement('span');
        texto.classList.add('govbr-forms-componente-input-campo-texto');
        texto.innerText = this.getConteudoTextualCampo();
        input.append(texto);
    }

    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        const propriedades = /** @type {Record<string, HTMLElement|Record<string, HTMLElement>>} */ (retorno['Propriedades do Campo']);

        propriedades['Lista'] = {};
        const campoMultiSelecao = document.createElement('input');
        campoMultiSelecao.type = 'checkbox';
        campoMultiSelecao.id = 'dataMultiSelecao';
        campoMultiSelecao.classList.add('govbr-forms-propriedades-campo-input');
        campoMultiSelecao.setAttribute('data-hint', 'Indica se o Usuário pode selecionar mais de um valor para o campo.');
        const campoInline = document.createElement('input');
        campoInline.type = 'checkbox';
        campoInline.id = 'dataInline';
        campoInline.classList.add('govbr-forms-propriedades-campo-input');
        campoInline.setAttribute('data-hint', 'Indica se as opções do campo devem ser listadas in-line (dispostas na tela, sempre visíveis) ou no dropdown.');
        propriedades['Lista']['Itens'] = this.montaCampoConfiguracaoItensLista();
        propriedades['Lista']['Seleção Múltipla'] = campoMultiSelecao;
        propriedades['Lista']['Inline'] = campoInline;
        return retorno;
    }

    /**
     * Monta os campos de configuração de itens do campo de lista.
     * @returns {HTMLElement}
     */
    montaCampoConfiguracaoItensLista() {
        const containerItensLista = document.createElement('div');
        containerItensLista.classList.add('govbr-forms-propriedades-campo-input');
        containerItensLista.classList.add('govbr-forms-propriedades-campo-input-lista');
        const campoItensLista = document.createElement('input');
        campoItensLista.type = 'text';
        campoItensLista.id = 'dataLista';
        campoItensLista.classList.add('govbr-forms-propriedades-campo-input-campo-oculto');
        campoItensLista.addEventListener('change', () => {
            // Timeout para aguardar o valor do atributo atualizar antes de atualizar os outros campos, pois este evento dispara antes do evento do FormsUC que atualiza o valor do atributo.
            setTimeout(() => {
                const totalItens = (this._dataLista || []).length;
                itensLista.innerText = totalItens ? 'Lista com ' + totalItens + ' itens.' : 'Lista vazia.';
            }, 0);
        });
        const itensLista = document.createElement('p');
        itensLista.classList.add('govbr-forms-propriedades-campo-input-texto');
        const totalItens = (this._dataLista || []).length;
        itensLista.innerText = totalItens ? 'Lista com ' + totalItens + ' itens.' : 'Lista vazia.';
        const botaoItensLista = document.createElement('button');
        botaoItensLista.classList.add('govbr-forms-propriedades-campo-input-botao');
        botaoItensLista.type = 'button';
        botaoItensLista.title = 'Configurar Lista';
        botaoItensLista.addEventListener('click', () => {
            this.montaPopupLista(campoItensLista);
        });
        const iconeConfigurar = document.createElement('i');
        iconeConfigurar.classList.add('fa-solid');
        iconeConfigurar.classList.add('fa-cog');
        botaoItensLista.append(iconeConfigurar);
        containerItensLista.append(itensLista);
        containerItensLista.append(campoItensLista);
        containerItensLista.append(botaoItensLista);
        return containerItensLista;
    }

    /**
     * Monta o popup para configurar a lista.
     * @param {HTMLInputElement} campoItensLista
     */
    montaPopupLista(campoItensLista) {
        let itens = (this._dataLista || []).slice();
        const dialog = this.form.abreDialog('Configurar Lista', () => {
            campoItensLista.value = JSON.stringify(itens);
            campoItensLista.dispatchEvent(new Event('change'));
        });
        const gridLista = document.createElement('div');
        gridLista.classList.add('govbr-forms-dialog-grid-configurar-lista');

        const elItens = document.createElement('div');
        elItens.classList.add('govbr-forms-dialog-grid-configurar-lista-itens');
        gridLista.append(elItens);
        const montaItens = () => {
            elItens.innerHTML = '';
            itens.forEach(({ Valor: valor, Nome: nome }, idx) => {
                const linha = document.createElement('div');
                linha.classList.add('govbr-forms-dialog-grid-linha');

                const lblValor = document.createElement('label');
                lblValor.innerText = 'Valor*';
                const inputValor = document.createElement('input');
                inputValor.type = 'text';
                inputValor.id = 'valor_' + idx;
                inputValor.name = 'valor_' + idx;
                inputValor.value = valor;
                inputValor.addEventListener('change', () => {
                    if (!inputValor.value || itens.find(({ Valor }) => Valor === inputValor.value)) {
                        inputValor.value = valor;
                    } else {
                        itens[idx].Valor = inputValor.value;
                    }
                });
                const lblNome = document.createElement('label');

                const inputNome = document.createElement('input');
                inputNome.type = 'text';
                inputNome.id = 'nome_' + idx;
                inputNome.name = 'nome_' + idx;
                inputNome.value = nome;
                inputNome.addEventListener('change', () => {
                    if (!inputNome.value) {
                        inputNome.value = nome;
                    } else {
                        itens[idx].Nome = inputNome.value;
                    }
                });
                lblNome.innerText = 'Nome*';

                const botaoRemoverItem = document.createElement('button');
                botaoRemoverItem.type = 'button';
                botaoRemoverItem.classList.add('govbr-form-dialog-botao');
                botaoRemoverItem.classList.add('govbr-form-dialog-botao-grid-remover-item');
                botaoRemoverItem.title = 'Remover Item';
                const iconeRemover = document.createElement('i');
                iconeRemover.classList.add('fa-solid');
                iconeRemover.classList.add('fa-trash-can');
                botaoRemoverItem.append(iconeRemover);
                botaoRemoverItem.addEventListener('click', () => {
                    itens.splice(idx, 1);
                    montaItens();
                });

                linha.append(lblValor);
                linha.append(inputValor);
                linha.append(lblNome);
                linha.append(inputNome);
                linha.append(botaoRemoverItem);
                elItens.append(linha);
            });
        };
        montaItens();

        const botaoAdicionarItem = document.createElement('button');
        botaoAdicionarItem.type = 'button';
        botaoAdicionarItem.classList.add('govbr-form-dialog-botao');
        botaoAdicionarItem.classList.add('govbr-form-dialog-botao-grid-adicionar-item');
        botaoAdicionarItem.innerText = 'Adicionar Item';
        botaoAdicionarItem.addEventListener('click', () => {
            let idx = itens.length + 1;
            const mapaItens = itens.reduce(
                /** @param {any} accum */ (accum, dados) => {
                    const { Valor, Nome } = dados;
                    accum[Valor] = Nome;
                    return accum;
                },
                {}
            );
            while (mapaItens['' + idx]) {
                idx++;
            }
            itens.push({ Valor: '' + idx, Nome: 'Item ' + idx });
            montaItens();
        });
        gridLista.append(botaoAdicionarItem);

        dialog.append(gridLista);
    }

    /**
     * Monta a representação visual do conteúdo textual do campo.
     */
    getConteudoTextualCampo() {
        return 'Selecione...';
    }

    /**
     * Retorna se pode ou não alterar o máximo de caractéres.
     * @returns {boolean}
     */
    getPodeAlterarMaxCaracteres() {
        return false;
    }

    /**
     * Retorna o valor armazenado do componente.
     */
    get valor() {
        if (this._input) {
            /**
             * @type {GOVBRFormsUCComponente}
             */
            let atual = this;
            while (atual.pai) {
                if (!atual.isVisivel()) {
                    return null;
                }
                atual = atual.pai;
            }
            if (!atual.isVisivel()) {
                return null;
            }
            if (this.dataLeitura) {
                return this._input.getAttribute('data-valor');
            }
            return this._input.value;
        }
        return null;
    }

    /**
     * Altera o valor armazenado do componente.
     */
    set valor(val) {
        if (this._input) {
            if (this.dataLeitura) {
                this._input.setAttribute('data-valor', val);
                val = this.getTituloValor(val);
            }
            const valAnterior = this._input.value;
            this._input.value = val;
            if (valAnterior !== val) {
                this._input.dispatchEvent(new Event('change'));
            }
        }
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return super.toJSONPropriedades().concat([
            { Nome: 'lista', Valor: this.dataLista },
            { Nome: 'multiSelecao', Valor: this.dataMultiSelecao ? '1' : '0' },
            { Nome: 'inline', Valor: this.dataInline ? '1' : '0' },
        ]);
    }

    /**
     * Retorna o título do campo com base no formato.
     */
    get tituloFormato() {
        if (this.dataMultiSelecao) {
            if (this.dataInline) {
                return 'Lista - Múltipla Inline';
            }
            return 'Lista - Múltipla';
        }
        if (this.dataInline) {
            return 'Lista - Radial';
        }
        return 'Lista';
    }

    /**
     * @returns {string}
     */
    get dataLista() {
        return JSON.stringify(this._dataLista || []);
    }

    /**
     * @param {string|import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlComponenteListaItem[]} val
     */
    set dataLista(val) {
        if (typeof val !== 'string') {
            val = '[]';
        }
        try {
            val = JSON.parse(val);
        } catch (ex) {
            GOVBR.logs.error('Falha ao carregar os valores da lista.', ex);
            val = [];
        }
        if (!Array.isArray(val)) {
            val = [];
        }
        this._dataLista = val.map((item) => {
            if (Object.prototype.hasOwnProperty.apply(item, ['nome']) && item.nome && Object.prototype.hasOwnProperty.apply(item, ['valor'])) {
                return {
                    Nome: item.nome,
                    Valor: item.valor,
                };
            }
            return item;
        });
    }

    /**
     * Retorna o formato do campo.
     * @returns {GOVBRFormsUCComponenteInputFormatos}
     */
    get dataFormato() {
        return 'lista';
    }

    get dataMultiSelecao() {
        return this._dataMultiSelecao === '1';
    }

    /**
     * @param {number|boolean} val
     */
    set dataMultiSelecao(val) {
        this._dataMultiSelecao = val == 1 ? '1' : '0';
    }

    get dataInline() {
        return this._dataInline === '1';
    }

    /**
     * @param {number|boolean} val
     */
    set dataInline(val) {
        this._dataInline = val == 1 ? '1' : '0';
    }
}

/**
 * Componente para campos/inputs de CheckBox.
 */
export class GOVBRFormsUCComponenteInputBooleano extends GOVBRFormsUCComponenteInput {
    /**
     * @type {HTMLInputElement}
     */
    _input = null;

    /**
     * Monta a representação visual do campo.
     * @param {HTMLElement} base
     */
    montaRepresentacaoCampo(base) {
        const input = document.createElement('span');
        input.classList.add('govbr-forms-componente-input-campo');
        input.classList.add('fa-regular');
        input.classList.add('fa-check-square');
        base.append(input);
    }

    /**
     * Retorna se pode ou não alterar o máximo de caractéres.
     * @returns {boolean}
     */
    getPodeAlterarMaxCaracteres() {
        return false;
    }

    /**
     * Retorna o valor armazenado do componente.
     */
    get valor() {
        if (this._input) {
            /**
             * @type {GOVBRFormsUCComponente}
             */
            let atual = this;
            while (atual.pai) {
                if (!atual.isVisivel()) {
                    return null;
                }
                atual = atual.pai;
            }
            if (!atual.isVisivel()) {
                return null;
            }
            return this._input.checked ? '1' : '0';
        }
        return null;
    }

    /**
     * Altera o valor armazenado do componente.
     * @param {string} val
     */
    set valor(val) {
        if (this._input) {
            const valBool = val === '1';
            const valAnterior = !!this._input.checked;
            this._input.checked = valBool;
            if (valAnterior !== valBool) {
                this._input.dispatchEvent(new Event('change'));
            }
        }
    }

    /**
     * Retorna o tipo do campo no HTML.
     */
    get tipoCampo() {
        return 'checkbox';
    }

    /**
     * Retorna o título do campo com base no formato.
     */
    get tituloFormato() {
        return 'Checkbox';
    }

    /**
     * Retorna o formato do campo.
     * @returns {GOVBRFormsUCComponenteInputFormatos}
     */
    get dataFormato() {
        return 'booleano';
    }
}

/**
 * Componente para campos/inputs de Telefone.
 */
export class GOVBRFormsUCComponenteInputTelefone extends GOVBRFormsUCComponenteInputTexto {
    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        const propriedades = /** @type {Record<string, HTMLElement>} */ (retorno['Propriedades do Campo']);
        delete propriedades['Máscara'];
        return retorno;
    }

    /**
     * Retorna se pode ou não alterar o máximo de caractéres.
     * @returns {boolean}
     */
    getPodeAlterarMaxCaracteres() {
        return false;
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return super.toJSONPropriedades().filter((e) => e.Nome != 'mascara');
    }

    /**
     * Retorna o título do campo com base no formato.
     */
    get tituloFormato() {
        return 'Telefone';
    }

    /**
     * Retorna o formato do campo.
     * @returns {GOVBRFormsUCComponenteInputFormatos}
     */
    get dataFormato() {
        return 'telefone';
    }

    get dataMascara() {
        return '(99) 99999-9999|(99) 9999-9999';
    }
}

/**
 * Componente para campos/inputs de Email.
 */
export class GOVBRFormsUCComponenteInputEmail extends GOVBRFormsUCComponenteInputTexto {
    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        const propriedades = /** @type {Record<string, HTMLElement>} */ (retorno['Propriedades do Campo']);
        delete propriedades['Máscara'];
        return retorno;
    }

    /**
     * Monta o elemento base do campo.
     * @returns {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement}
     */
    montaBaseCampo() {
        const input = /** @type {HTMLInputElement} */ (super.montaBaseCampo());
        input.placeholder = this.getConteudoTextualCampo();
        return input;
    }

    /**
     * Monta a representação visual do conteúdo textual do campo.
     */
    getConteudoTextualCampo() {
        return 'email@dominio';
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return super.toJSONPropriedades().filter((e) => e.Nome != 'mascara');
    }

    /**
     * Retorna o tipo do campo no HTML.
     */
    get tipoCampo() {
        return 'email';
    }

    /**
     * Retorna o título do campo com base no formato.
     */
    get tituloFormato() {
        return 'Email';
    }

    /**
     * Retorna o formato do campo.
     * @returns {GOVBRFormsUCComponenteInputFormatos}
     */
    get dataFormato() {
        return 'email';
    }
}

/**
 * Componente para campos/inputs de CPF/CNPJ.
 */
export class GOVBRFormsUCComponenteInputCPFCNPJ extends GOVBRFormsUCComponenteInputTexto {
    /**
     * @type {string}
     */
    dataFormatoCPFCNPJ;

    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        const propriedades = /** @type {Record<string, HTMLElement>} */ (retorno['Propriedades do Campo']);
        delete propriedades['Máscara'];
        const campoFormatoCPFCNPJ = document.createElement('select');
        campoFormatoCPFCNPJ.id = 'dataFormatoCPFCNPJ';
        campoFormatoCPFCNPJ.classList.add('govbr-forms-propriedades-campo-input');
        [
            ['', 'CPF e CNPJ'],
            ['cpf', 'CPF apenas'],
            ['cnpj', 'CNPJ apenas'],
        ].forEach(([valor, texto]) => {
            campoFormatoCPFCNPJ.classList.add('govbr-forms-propriedades-campo-input');
            const option = document.createElement('option');
            option.value = valor;
            option.innerText = texto;
            campoFormatoCPFCNPJ.append(option);
        });
        propriedades['Formato'] = campoFormatoCPFCNPJ;
        campoFormatoCPFCNPJ.setAttribute('data-hint', 'Indica o formato de valores válidos para o campo.');
        return retorno;
    }

    /**
     * Retorna se pode ou não alterar o máximo de caractéres.
     * @returns {boolean}
     */
    getPodeAlterarMaxCaracteres() {
        return false;
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return super
            .toJSONPropriedades()
            .filter((e) => e.Nome != 'mascara')
            .concat([
                { Nome: 'formatoCPFCNPJ', Valor: this.dataFormatoCPFCNPJ },
            ]);
    }

    /**
     * Retorna o título do campo com base no formato.
     */
    get tituloFormato() {
        switch (this.dataFormatoCPFCNPJ) {
            case 'cpf':
                return 'CPF';
            case 'cnpj':
                return 'CNPJ';
        }
        return 'CPF/CNPJ';
    }

    /**
     * Retorna o formato do campo.
     * @returns {GOVBRFormsUCComponenteInputFormatos}
     */
    get dataFormato() {
        return 'cpf-cnpj';
    }

    get dataMascara() {
        switch (this.dataFormatoCPFCNPJ) {
            case 'cpf':
                return '999.999.999-99';
            case 'cnpj':
                return '99.999.999/9999-99';
        }
        return '999.999.999-99|99.999.999/9999-99';
    }

    /**
     * Retorna as funções que recebem um valor e retorna se esta e válida. Deve retornar true se for válida, false se não for, ou uma string para invalidar e exibir uma mensagem customizada.
     * @return {((val: string) => (boolean|string))[]}
     */
    get funcoesValida() {
        const funcoes = this._funcoesValida || [];
        funcoes.push((valor) => {
            switch (this.dataFormatoCPFCNPJ) {
                case 'cpf':
                    return Validadores.validaCpf(valor);
                case 'cnpj':
                    return Validadores.validaCnpj(valor);
            }
            return Validadores.validaCpfCnpj(valor);
        });
        return funcoes;
    }
}

/**
 * Classe base para componentes de containers do GOVBRForms.
 */
export class GOVBRFormsUCComponenteContainer extends GOVBRFormsUCComponente {
    /**
     * @type {GOVBRFormsUCComponente[]|GOVBRFormsUCComponente[][]}
     */
    filhos = [];

    /**
     * @type {string}
     */
    _dataColunas;

    /**
     * Monta a visualização do componente quando este é apendado em tela.
     * @returns {HTMLElement}
     */
    montaVisualizacao() {
        const base = this.montaBaseContainer(true);
        this.montaFilhos(base, this.filhos, true);
        return base;
    }

    /**
     * Monta a visualização do componente quando este é apendado ao editor.
     * @returns {HTMLElement}
     */
    montaVisualizacaoEditor() {
        const base = this.montaBaseContainer(false);
        this.montaFilhos(base, this.filhos, false);
        return base;
    }

    /**
     * Monta o elemento de base do componente que depois terão seus filhos apendados.
     * @param {boolean} _preencher
     * @returns {HTMLElement}
     */
    montaBaseContainer(_preencher) {
        const base = super.montaVisualizacaoEditor();
        base.classList.add('govbr-forms-componente-container');
        if (parseInt(this.dataColunas) > 1) {
            base.style.setProperty('--numero-colunas', this.dataColunas);
        }
        return base;
    }

    /**
     * Cria e retorna o elemento usado como base do componente
     * @returns {HTMLElement}
     */
    criaElementoBase() {
        return document.createElement('div');
    }

    /**
     * Monta os elementos de filhos sobre a base.
     * @param {HTMLElement} base
     * @param {GOVBRFormsUCComponente[]|GOVBRFormsUCComponente[][]} filhos
     * @param {boolean} _preencher
     */
    montaFilhos(base, filhos, _preencher) {
        /** @type {GOVBRFormsUCComponente[]} */ (filhos).forEach((componente) => {
            this.form.montaComponente(base, componente);
        });
        const adicionarNovo = this.criaBotaoAdicionarNovo();
        if (adicionarNovo) {
            base.append(adicionarNovo);
        }
    }

    /**
     * Cria o botão para adicionar um novo componente.
     */
    criaBotaoAdicionarNovo() {
        return this.form.criaBotaoAdicionarNovo('', this);
    }

    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        const campoColunas = document.createElement('input');
        campoColunas.type = 'number';
        campoColunas.id = 'dataColunas';
        campoColunas.step = '1';
        campoColunas.min = '1';
        campoColunas.classList.add('govbr-forms-propriedades-campo-input');
        campoColunas.setAttribute(
            'data-hint',
            'Número de colunas disponíveis para o componente internos, se for maior que 1, os componentes internos do container serão dispostos conforme o fluxo normal, nesta situação, caso um dos componentes internos deva ocupar mais de uma coluna, utilize a propriedade Colunas Ocupadas no respectivo componente.'
        );
        retorno['Colunas Disponíveis'] = campoColunas;
        return retorno;
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return super.toJSONPropriedades().concat([
            { Nome: 'colunas', Valor: this.dataColunas },
        ]);
    }

    /**
     * Converte os Filhos para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsComponente[]}
     */
    toJSONFilhos() {
        return /** @type {GOVBRFormsUCComponente[]} */ (this.filhos).map((e) => e.toJSON());
    }

    /**
     * Retorna os filhos em formato linear, necessário para tratar fieldsets multilinha que possuem arrays de filhos.
     * @returns {GOVBRFormsUCComponente[]}
     */
    getFilhosLinear() {
        return /** @type {GOVBRFormsUCComponente[]} */ (this.filhos);
    }

    /**
     * Retorna o título usado para indicar o componente.
     */
    get titulo() {
        return 'Container';
    }

    /**
     * Retorna o tipo do componente para ser armazenado no servidor.
     */
    get tipo() {
        return 'tela';
    }

    /**
     * Retorna se o componente é um container (pode guardar outros componentes).
     * @returns {this is GOVBRFormsUCComponenteContainer}
     */
    get isContainer() {
        return true;
    }

    get dataColunas() {
        return this._dataColunas || '';
    }

    set dataColunas(val) {
        if (parseInt(val) < 1) {
            val = '1';
        }
        this._dataColunas = val;
    }
}

/**
 * Componente para fieldsets.
 */
export class GOVBRFormsUCComponenteFieldset extends GOVBRFormsUCComponenteContainer {
    /**
     * @type {GOVBRFormsUCComponente[]|GOVBRFormsUCComponente[][]}
     */
    filhos = [];
    /**
     * @type {string}
     */
    dataMultilinha;
    /**
     * @type {string}
     */
    dataMinLinhas;
    /**
     * @type {string}
     */
    dataLinhasDefault;
    /**
     * @type {string}
     */
    dataUsaMaxLinhas;
    /**
     * @type {string}
     */
    dataMaxLinhas;

    /**
     * @type {import('./GOVBRFormsUserControl.mjs').GOVBRFormsComponente[]}
     */
    filhosVirtuais;
    /**
     * @type {HTMLElement}
     */
    base;

    /**
     * Tratamentos a serem realizados após a construção e inicialização do componente para o modo de visualização.
     */
    afterInitVisualizacao() {
        if (this.dataMultilinha) {
            this.filhosVirtuais = this.toJSONFilhos();
            this.filhos = [];
            this.montaLinhasIniciais();
        }
    }

    /**
     * Monta as linhas iniciais do componente.
     */
    montaLinhasIniciais() {
        let numLinhas = parseInt(this.dataLinhasDefault);
        if (this.form.possuiValoresIniciais) {
            const idCampo = this.identificador;
            let maiorIdx = parseInt(this.dataMinLinhas);
            Object.keys(this.form.valores).forEach((e) => {
                const idx = parseInt(e.match(new RegExp(idCampo + '-(\\d+)'))?.[1]) + 1;
                if (idx && idx > maiorIdx) {
                    maiorIdx = idx;
                }
            });
            if (this.dataUsaMaxLinhas && maiorIdx > parseInt(this.dataMaxLinhas)) {
                maiorIdx = parseInt(this.dataMaxLinhas);
            }
            numLinhas = maiorIdx;
        }
        while (this.filhos.length < numLinhas) {
            this.adicionaLinha();
        }
    }

    /**
     * Monta o elemento de base do componente que depois terão seus filhos apendados.
     * @param {boolean} preencher
     * @returns {HTMLElement}
     */
    montaBaseContainer(preencher) {
        const base = super.montaBaseContainer(preencher);
        base.classList.add('govbr-forms-componente-fieldset');
        const titulo = document.createElement('div');
        titulo.classList.add('govbr-forms-componente-fieldset-titulo');
        titulo.innerText = (this.dataTitulo || 'Grupo') + (!preencher && this.dataMultilinha ? ' (Multilinha)' : '');
        base.append(titulo);
        if (this.dataMultilinha) {
            base.classList.add('govbr-forms-componente-fieldset-multilinha');
        }
        return base;
    }

    /**
     * Cria o botão de adicionar linha no container.
     * @param {HTMLElement} container
     * @param {number} idx
     */
    montaBotaoAdicionarLinha(container, idx) {
        if (this.form.userControl.SOMENTE_LEITURA) {
            return;
        }
        const botao = document.createElement('button');
        botao.type = 'button';
        botao.title = 'Adicionar Linha';
        botao.classList.add('govbr-forms-componente-fieldset-linha-botao', 'govbr-forms-componente-fieldset-linha-add', 'fa-solid', 'fa-plus');
        botao.addEventListener('click', () => {
            this.adicionaLinha(idx);
            this.form.recriaAreaFormulario().then(() => {
                this.base.querySelectorAll('input, textarea, select').forEach((e) => {
                    e.dispatchEvent(new Event('change'));
                });
            });
        });
        container.append(botao);
    }

    /**
     * Cria o botão de remover linha no container.
     * @param {HTMLElement} container
     * @param {number} idx
     */
    montaBotaoRemoverLinha(container, idx) {
        if (this.form.userControl.SOMENTE_LEITURA) {
            return;
        }
        const botao = document.createElement('button');
        botao.type = 'button';
        botao.title = 'Remover Linha';
        botao.classList.add('govbr-forms-componente-fieldset-linha-botao', 'govbr-forms-componente-fieldset-linha-remove', 'fa-solid', 'fa-minus');
        botao.addEventListener('click', () => {
            this.removerLinha(idx);
            this.form.recriaAreaFormulario();
        });
        container.append(botao);
    }

    /**
     * Adiciona uma linha quando multilinha.
     * @param {number} idx
     */
    adicionaLinha(idx = null) {
        if (!this.dataMultilinha || (this.dataUsaMaxLinhas && this.filhos.length === parseInt(this.dataMaxLinhas))) {
            return;
        }
        const filhos = this.filhosVirtuais.map((filho) => {
            return GOVBRFormsUCComponenteFactory.getInstanciaComponente(this.form, filho, this, false);
        });
        if (idx === null) {
            /** @type {GOVBRFormsUCComponente[][]} */ (this.filhos).push(filhos);
        } else {
            this.filhos.splice(idx + 1, 0, filhos);
        }
        filhos.forEach((e) => e.afterInitVisualizacao());
    }

    /**
     * Remove uma linha quando multilinha.
     * @param {number} idx
     */
    removerLinha(idx = null) {
        if (!this.dataMultilinha || this.filhos.length === parseInt(this.dataMinLinhas)) {
            return;
        }
        if (idx === null) {
            this.filhos.pop();
        } else {
            this.filhos.splice(idx, 1);
        }
    }

    /**
     * Monta os elementos de filhos sobre a base.
     * @param {HTMLElement} base
     * @param {GOVBRFormsUCComponente[] | GOVBRFormsUCComponente[][]} filhos
     * @param {boolean} preencher
     */
    montaFilhos(base, filhos, preencher) {
        if (preencher && this.dataMultilinha) {
            if (filhos.length) {
                this.base = base;
                filhos.forEach((filhos, idx) => {
                    const container = document.createElement('div');
                    container.classList.add('govbr-forms-componente-fieldset-linha');
                    super.montaFilhos(container, /** @type {GOVBRFormsUCComponente[]} */ (filhos), preencher);
                    if (!this.dataUsaMaxLinhas || this.filhos.length < parseInt(this.dataMaxLinhas)) {
                        this.montaBotaoAdicionarLinha(container, idx);
                    }
                    if (this.filhos.length > parseInt(this.dataMinLinhas)) {
                        this.montaBotaoRemoverLinha(container, idx);
                    }
                    base.append(container);
                });
            } else {
                const container = document.createElement('div');
                container.tabIndex = -1;
                container.classList.add('govbr-forms-componente-fieldset-linha', 'govbr-forms-componente-fieldset-linha-fantasma');
                super.montaFilhos(
                    container,
                    this.filhosVirtuais.map((filho) => {
                        return GOVBRFormsUCComponenteFactory.getInstanciaComponente(this.form, filho, this);
                    }),
                    preencher
                );
                // Isto remove todos os eventos dos elementos.
                container.innerHTML = container.innerHTML;
                container.querySelectorAll('input, textarea, select').forEach(
                    /** @param {HTMLInputElement} e */ (e) => {
                        e.required = false;
                        e.disabled = e.readOnly = true;
                        e.removeAttribute('id');
                        e.removeAttribute('lame');
                    }
                );
                container.querySelectorAll('.govbr-forms-componente-fieldset-linha-botao').forEach((e) => {
                    e.remove();
                });
                if (!this.dataUsaMaxLinhas || 0 < parseInt(this.dataMaxLinhas)) {
                    this.montaBotaoAdicionarLinha(container, 0);
                }
                base.append(container);
            }
        } else {
            super.montaFilhos(base, filhos, preencher);
        }
    }

    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        const campoTitulo = document.createElement('input');
        campoTitulo.type = 'text';
        campoTitulo.id = 'dataTitulo';
        campoTitulo.classList.add('govbr-forms-propriedades-campo-input');
        campoTitulo.setAttribute('data-order', '1');
        campoTitulo.setAttribute('data-hint', 'Título do campo, exibido para o Usuário quando este estiver preenchendo o Formulário.');
        campoTitulo.addEventListener('blur', () => {
            if (!this._dataIdentificador && campoTitulo.value) {
                let valor = campoTitulo.value || '';
                valor = valor
                    .toLowerCase()
                    .normalize('NFD')
                    .replace(/\s+/g, '-')
                    .replace(/[^0-9a-z-]/g, '');
                this.dataIdentificador = valor;
                /**
                 * @type {HTMLInputElement}
                 */
                const identificador = this.form.elPropriedades.querySelector('#dataIdentificador');
                identificador.value = this.dataIdentificador;
            }
        });
        retorno['Título'] = campoTitulo;

        /**
         * @type {Record<string, HTMLElement>}
         */
        const multilinhas = {};
        const campoMultilinha = document.createElement('input');
        campoMultilinha.type = 'checkbox';
        campoMultilinha.id = 'dataMultilinha';
        campoMultilinha.classList.add('govbr-forms-propriedades-campo-input');
        campoMultilinha.setAttribute(
            'data-hint',
            'Se marcado irá habilitar o recurso de multilinhas que permite "duplicar" os campos e outro conteúdo presente no Grupo múltiplas vezes de forma a permitir o usuário cadastrar múltiplas instâncias dos dados.'
        );
        campoMultilinha.addEventListener('change', () => {
            if (campoMultilinha.checked) {
                campoMinLinhas.value = campoMinLinhas.value || '0';
                campoLinhasDefault.value = campoMaxLinhas.value || '2';
                campoUsaMaxLinhas.checked = false;

                campoMinLinhas.disabled = false;
                campoLinhasDefault.disabled = false;
                campoUsaMaxLinhas.disabled = false;
                campoMinLinhas.readOnly = false;
                campoLinhasDefault.readOnly = false;
                campoUsaMaxLinhas.readOnly = false;
                [campoMinLinhas, campoLinhasDefault, campoUsaMaxLinhas, campoMaxLinhas].forEach((e) => e.dispatchEvent(new Event('change')));
            } else {
                campoMinLinhas.value = '';
                campoLinhasDefault.value = '';
                campoUsaMaxLinhas.checked = false;
                [campoMinLinhas, campoLinhasDefault, campoUsaMaxLinhas].forEach((e) => e.dispatchEvent(new Event('change')));

                campoMinLinhas.disabled = true;
                campoLinhasDefault.disabled = true;
                campoUsaMaxLinhas.disabled = true;
                campoMinLinhas.readOnly = true;
                campoLinhasDefault.readOnly = true;
                campoUsaMaxLinhas.readOnly = true;
            }
        });
        const campoMinLinhas = document.createElement('input');
        campoMinLinhas.type = 'number';
        campoMinLinhas.id = 'dataMinLinhas';
        campoMinLinhas.step = '1';
        campoMinLinhas.min = '0';
        campoMinLinhas.classList.add('govbr-forms-propriedades-campo-input');
        campoMinLinhas.setAttribute('data-hint', 'Indica o mínimo de linhas que o Usuário pode ter em um grupo.');
        const campoLinhasDefault = document.createElement('input');
        campoLinhasDefault.type = 'number';
        campoLinhasDefault.id = 'dataLinhasDefault';
        campoLinhasDefault.step = '1';
        campoLinhasDefault.min = '0';
        campoLinhasDefault.classList.add('govbr-forms-propriedades-campo-input');
        campoLinhasDefault.setAttribute('data-hint', 'Indica o número de linhas padrão que o grupo possuirá ao abrir a tela.');
        const campoUsaMaxLinhas = document.createElement('input');
        campoUsaMaxLinhas.type = 'checkbox';
        campoUsaMaxLinhas.id = 'dataUsaMaxLinhas';
        campoUsaMaxLinhas.classList.add('govbr-forms-propriedades-campo-input');
        campoUsaMaxLinhas.setAttribute('data-hint', 'Indica se deve limitar quantas linhas o Usuário pode ter em um mesmo grupo.');
        campoUsaMaxLinhas.addEventListener('change', () => {
            if (campoUsaMaxLinhas.checked) {
                campoMaxLinhas.value = campoMaxLinhas.value || '5';
                campoMaxLinhas.disabled = false;
                campoMaxLinhas.readOnly = false;
                campoMaxLinhas.dispatchEvent(new Event('change'));
            } else {
                campoMaxLinhas.value = '';
                campoMaxLinhas.dispatchEvent(new Event('change'));
                campoMaxLinhas.disabled = true;
                campoMaxLinhas.readOnly = true;
            }
        });
        const campoMaxLinhas = document.createElement('input');
        campoMaxLinhas.type = 'number';
        campoMaxLinhas.id = 'dataMaxLinhas';
        campoMaxLinhas.step = '1';
        campoMaxLinhas.min = '0';
        campoMaxLinhas.classList.add('govbr-forms-propriedades-campo-input', 'govbr-forms-propriedades-campo-duas-colunas');
        campoMaxLinhas.setAttribute('data-hint', 'Indica o máximo de linhas que o Usuário pode ter em um grupo.');
        multilinhas['Utiliza Multilinhas'] = campoMultilinha;
        multilinhas['Mínimo de Linhas'] = campoMinLinhas;
        multilinhas['Número de Linhas iniciais'] = campoLinhasDefault;
        multilinhas['Limita o Máximo de Linhas'] = campoUsaMaxLinhas;
        multilinhas['Máximo de Linhas'] = campoMaxLinhas;
        retorno['Multilinha'] = multilinhas;
        return retorno;
    }

    /**
     * Converte os Filhos para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsComponente[]}
     */
    toJSONFilhos() {
        if (this.dataMultilinha && this.filhosVirtuais) {
            return this.filhosVirtuais;
        }
        return /** @type {GOVBRFormsUCComponente[]} */ (this.filhos).map((e) => e.toJSON());
    }

    /**
     * Trata o identificador do filho.
     * @param {string[]} contexto
     * @param {GOVBRFormsUCComponente} filhoOrig
     */
    getInformacaoContexto(contexto, filhoOrig) {
        if (this.dataMultilinha) {
            if (this.filhosVirtuais) {
                const idx =
                    this.filhos.findIndex((filhos) => {
                        return /** @type {GOVBRFormsUCComponente[]} */ (filhos).some((filho) => filho === filhoOrig);
                    }) || 0;
                contexto.unshift(this.identificadorOriginal + '-' + idx);
            } else {
                contexto.unshift(this.identificadorOriginal + '-idx');
            }
        }
        return contexto;
    }

    /**
     * Retorna os filhos em formato linear, necessário para tratar fieldsets multilinha que possuem arrays de filhos.
     * @returns {GOVBRFormsUCComponente[]}
     */
    getFilhosLinear() {
        if (this.dataMultilinha) {
            return /** @type {GOVBRFormsUCComponente[][]} */ (this.filhos).reduce((accum, filho) => {
                if (Array.isArray(filho)) {
                    accum = accum.concat(filho);
                } else {
                    accum.push(filho);
                }
                return accum;
            }, []);
        }
        return /** @type {GOVBRFormsUCComponente[]} */ (this.filhos);
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return super.toJSONPropriedades().concat([
            { Nome: 'titulo', Valor: this.dataTitulo },
            { Nome: 'multilinha', Valor: this.dataMultilinha },
            { Nome: 'minLinhas', Valor: this.dataMinLinhas },
            { Nome: 'linhasDefault', Valor: this.dataLinhasDefault },
            { Nome: 'usaMaxLinhas', Valor: this.dataUsaMaxLinhas },
            { Nome: 'maxLinhas', Valor: this.dataMaxLinhas },
        ]);
    }

    /**
     * Retorna o título usado para indicar o componente.
     */
    get titulo() {
        return 'Grupo';
    }

    /**
     * Retorna o tipo do componente para ser armazenado no servidor.
     */
    get tipo() {
        return 'fieldset';
    }
}

/**
 * Componente para o container de tabs.
 */
export class GOVBRFormsUCComponenteTabsContainer extends GOVBRFormsUCComponenteContainer {
    /**
     * @type {number}
     */
    tabAtiva;
    /**
     * @type {number}
     */
    tabAtivaAnt;

    /**
     * @type {GOVBRFormsUCComponente[]}
     */
    filhos = [];

    /**
     * Monta a visualização do componente quando este é apendado em tela.
     * @returns {HTMLElement}
     */
    montaVisualizacao() {
        if (!this.tabAtiva) {
            this.tabAtiva = 0;
        }
        return super.montaVisualizacao();
    }

    /**
     * Monta o elemento de base do componente que depois terão seus filhos apendados.
     * @param {boolean} _preencher
     * @returns {HTMLElement}
     */
    montaBaseContainer(_preencher) {
        if (!this.filhos.length) {
            const aba = GOVBRFormsUCComponenteFactory.getInstanciaComponente(this.form, {
                Id: this.form.getIdentificadorNovoComponente('tabs'),
                Tipo: 'tabs',
            });
            this.filhos.push(aba);
            aba.pai = this;
            this.tabAtiva = 0;
        }
        const base = super.montaBaseContainer(_preencher);
        base.classList.add('govbr-forms-componente-tabs-container');
        return base;
    }

    /**
     * Monta os elementos de filhos sobre a base.
     * @param {HTMLElement} base
     * @param {GOVBRFormsUCComponente[]} filhos
     * @param {boolean} _preencher
     */
    montaFilhos(base, filhos, _preencher) {
        super.montaFilhos(base, filhos, _preencher);
        if (this.form.previsualizando) {
            let tab = this.tabAtiva;
            while (tab < this.filhos.length && (!base.children[tab] || base.children[tab].classList.contains('govbr-forms-componente-oculto'))) {
                tab++;
            }
            if (tab >= this.filhos.length) {
                tab = 0;
                while (tab < this.tabAtiva && (!base.children[tab] || base.children[tab].classList.contains('govbr-forms-componente-oculto'))) {
                    tab++;
                }
            }
            this.tabAtiva = tab;
            if (this.tabAtiva !== this.tabAtivaAnt && base.children[tab]) {
                this.tabAtivaAnt = this.tabAtiva;
                setTimeout(() => {
                    // Aguarda finalizar a renderização pois não é possível disparar eventos durante a renderização.
                    // @ts-ignore
                    const instance = base.children[tab]['__govbr_instance'];
                    this.form.disparaEventos('abrir', instance.eventos, this.getContextoContainersMultilinha());
                }, 0);
            }
        }
        if (this.tabAtiva) {
            let tab = base.children[this.tabAtiva];
            if (!tab) {
                tab = base.children[0];
            }
            tab?.classList.add('govbr-forms-componente-tabs-ativa');
        } else {
            base.children[0]?.classList.add('govbr-forms-componente-tabs-ativa');
        }
    }

    /**
     * Cria o botão para adicionar um novo componente.
     */
    criaBotaoAdicionarNovo() {
        return this.form.criaBotaoAdicionarNovo('Adicionar uma Aba', this, () => {
            const aba = GOVBRFormsUCComponenteFactory.getInstanciaComponente(this.form, {
                Id: this.form.getIdentificadorNovoComponente('tabs'),
                Tipo: 'tabs',
            });
            this.filhos.push(aba);
            aba.pai = this;
            this.form.selecionaComponente(aba);
            this.tabAtiva = this.filhos.length - 1;
            this.form.recriaAreaFormulario();
        });
    }

    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        delete retorno['Colunas Disponíveis'];
        return retorno;
    }

    /**
     * Retorna o título usado para indicar o componente.
     */
    get titulo() {
        return 'Container de Abas';
    }

    /**
     * Retorna o tipo do componente para ser armazenado no servidor.
     */
    get tipo() {
        return 'tabs-container';
    }
}

/**
 * Componente para tabs.
 */
export class GOVBRFormsUCComponenteTabs extends GOVBRFormsUCComponenteContainer {
    /**
     * Monta o elemento de base do componente que depois terão seus filhos apendados.
     * @param {boolean} _preencher
     * @returns {HTMLElement}
     */
    montaBaseContainer(_preencher) {
        const base = super.montaBaseContainer(_preencher);
        base.classList.add('govbr-forms-componente-tabs');
        const titulo = document.createElement('span');
        titulo.classList.add('govbr-forms-componente-tabs-titulo');
        titulo.setAttribute('tabindex', '0');
        titulo.innerText = this.dataTitulo || 'Aba';
        const ativaAba = () => {
            const parent = base.parentElement;
            /**
             * @type {GOVBRFormsUCComponenteTabsContainer}
             */
            // @ts-ignore
            const container = parent['__govbr_instance'];
            container.tabAtiva = Array.from(parent.children).indexOf(base);
            this.form.recriaAreaFormulario();
        };
        titulo.addEventListener('pointerdown', () => {
            ativaAba();
        });
        titulo.addEventListener('keydown', (event) => {
            if (event?.key === 'Enter') {
                ativaAba();
            }
        });
        // @ts-ignore
        titulo['__govbr_instance'] = this;
        base.append(titulo);
        const elementos = document.createElement('div');
        elementos.classList.add('govbr-forms-componente-tabs-itens');
        elementos.classList.add('govbr-forms-componente-container');
        // @ts-ignore
        elementos['__govbr_instance'] = this;
        base.append(elementos);
        return base;
    }

    /**
     * Monta os elementos de filhos sobre a base.
     * @param {HTMLElement} base
     * @param {GOVBRFormsUCComponente[]} filhos
     * @param {boolean} _preencher
     */
    montaFilhos(base, filhos, _preencher) {
        super.montaFilhos(base.querySelector(':scope > .govbr-forms-componente-tabs-itens'), filhos, _preencher);
    }

    /**
     * Monta os campos para a tela de propridades.
     * @returns {Record<string, Record<string, HTMLElement>|HTMLElement>}
     */
    montaCamposPropriedades() {
        const retorno = super.montaCamposPropriedades();
        const campoTitulo = document.createElement('input');
        campoTitulo.type = 'text';
        campoTitulo.id = 'dataTitulo';
        campoTitulo.classList.add('govbr-forms-propriedades-campo-input');
        campoTitulo.setAttribute('data-order', '1');
        campoTitulo.setAttribute('data-hint', 'Título do campo, exibido para o Usuário quando este estiver preenchendo o Formulário.');
        campoTitulo.addEventListener('blur', () => {
            if (!this._dataIdentificador && campoTitulo.value) {
                let valor = campoTitulo.value || '';
                valor = valor
                    .toLowerCase()
                    .normalize('NFD')
                    .replace(/\s+/g, '-')
                    .replace(/[^0-9a-z-]/g, '');
                this.dataIdentificador = valor;
                /**
                 * @type {HTMLInputElement}
                 */
                const identificador = this.form.elPropriedades.querySelector('#dataIdentificador');
                identificador.value = this.dataIdentificador;
            }
        });
        retorno['Título'] = campoTitulo;
        return retorno;
    }

    /**
     * Converte as Propriedades para um formato JSON serializável.
     * @returns {import('./GOVBRFormsUserControl.mjs').GOVBRFormsUserControlPropriedade[]}
     */
    toJSONPropriedades() {
        return super.toJSONPropriedades().concat([
            { Nome: 'titulo', Valor: this.dataTitulo },
        ]);
    }

    /**
     * Monta botões para ações adicionais para o componente.
     * @param {HTMLElement} alvo
     */
    montaAcoesAdicionais(alvo) {
        const botaoEventos = document.createElement('button');
        botaoEventos.className = 'govbr-forms-propriedades-acoes-botao govbr-forms-propriedades-acoes-eventos';
        botaoEventos.type = 'button';
        botaoEventos.innerText = 'Eventos';
        botaoEventos.title = 'Eventos';
        if (!this.eventos) {
            this.eventos = [];
        }
        botaoEventos.addEventListener('click', () => {
            GOVBRFormsUCConfiguradorEventos.abreJanelaConfiguracaoEventos(
                this.form,
                (this.dataTitulo || '') + ' - ' + this.titulo,
                this.eventos,
                [
                    ['abrir', 'Ao Abrir', 'Evento disparado ao usuário clicar na aba e ver os campos.'],
                ],
                (resultado) => (this.eventos = resultado),
                null,
                this.getContextoContainersMultilinha()
            );
        });
        const iconeEventos = document.createElement('i');
        iconeEventos.className = 'fa-solid fa-forward';
        botaoEventos.prepend(iconeEventos);
        alvo.append(botaoEventos);
    }

    /**
     * Retorna o título usado para indicar o componente.
     */
    get titulo() {
        return 'Aba';
    }

    /**
     * Retorna o tipo do componente para ser armazenado no servidor.
     */
    get tipo() {
        return 'tabs';
    }
}

/**
 * Fabrica estática utilizada para criação de componentes.
 */
export class GOVBRFormsUCComponenteFactory {
    constructor() {
        throw new Error('GOVBRFormsUCComponenteFactory é uma classe estática e não deve ser chamada diretamente.');
    }

    /**
     * Retorna os elementos de lista dos tipos dos componentes que o usuário pode adicionar.
     * @returns {(HTMLHRElement|HTMLLIElement)[]}
     */
    static montaListaComponentes() {
        /**
         * @param {string} icone
         * @param {string} id
         * @param {string} descricao
         * @param {Record<string, string>} props
         * @returns {HTMLHRElement|HTMLLIElement}
         */
        const montaItemLista = (icone, id, descricao, props) => {
            if (!id) {
                const el = document.createElement('hr');
                el.className = 'govbr-forms-lista-componente-separador';
                return el;
            }
            const el = document.createElement('li');
            el.className = 'govbr-forms-lista-componente-item';
            el.setAttribute('data-id', id);
            el.innerText = descricao;
            el.setAttribute('data-props', JSON.stringify(Object.entries(props || {}).map(([nome, valor]) => ({ Nome: nome, Valor: valor }))));
            const iconeHTML = document.createElement('i');
            iconeHTML.className = 'govbr-forms-lista-componente-icone ' + icone;
            el.prepend(iconeHTML);
            return el;
        };
        return /** @type {[string, string, string, Record<string, string>][]}*/ ([
            ['fa-solid fa-object-group', 'fieldset', 'Grupo', {}],
            ['fa-solid fa-table-columns', 'tabs-container', 'Abas', {}],
            ['', '', '', {}],
            ['fa-solid fa-font', 'input', 'Texto', { formato: 'texto' }],
            ['fa-solid fa-align-left', 'input', 'Texto Multi-Linha', { formato: 'textarea' }],
            ['fa-solid fa-code', 'input', 'Texto Formatado', { formato: 'editor' }],
            ['fa-solid fa-0', 'input', 'Número', { formato: 'numero' }],
            ['fa-regular fa-clock', 'input', 'Data/Hora', { formato: 'data-hora' }],
            ['fa-solid fa-list', 'input', 'Lista', { formato: 'lista' }],
            ['fa-regular fa-square-check', 'input', 'Checkbox', { formato: 'booleano' }],
            ['fa-solid fa-mobile-screen', 'input', 'Telefone', { formato: 'telefone' }],
            ['fa-regular fa-envelope', 'input', 'Email', { formato: 'email' }],
            ['fa-regular fa-id-card', 'input', 'CPF/CNPJ', { formato: 'cpf-cnpj' }],
        ]).map(([icone, id, descricao, props]) => montaItemLista(icone, id, descricao, props));
    }

    /**
     * Retorna uma instância de um componente do tipo especificado, carregando as propriedades dele.
     * @param {import("./GOVBRFormsUC.mjs").GOVBRFormsUC} formsUC
     * @param {import("./GOVBRFormsUserControl.mjs").GOVBRFormsComponente} dadosComponente
     * @param {GOVBRFormsUCComponenteContainer} pai
     * @param {boolean} disparaAfterInit
     * @returns {GOVBRFormsUCComponente}
     */
    static getInstanciaComponente(formsUC, dadosComponente, pai = null, disparaAfterInit = true) {
        const { Tipo, Id, Propriedades, Filhos, Eventos, Validacoes } = dadosComponente;
        /**
         * @type {GOVBRFormsUCComponente}
         */
        let componente = null;
        switch (Tipo) {
            case 'fieldset':
                componente = new GOVBRFormsUCComponenteFieldset(Id);
                break;
            case 'input':
                componente = GOVBRFormsUCComponenteFactory.getInstanciaComponenteInput(Id, (Propriedades || []).find((e) => e.Nome === 'formato')?.Valor || '');
                break;
            case 'tabs-container':
                componente = new GOVBRFormsUCComponenteTabsContainer(Id);
                break;
            case 'tabs':
                componente = new GOVBRFormsUCComponenteTabs(Id);
                break;
        }
        if (componente) {
            if (pai) {
                componente.pai = pai;
            }
            componente.form = formsUC;
            if (Array.isArray(Propriedades)) {
                /**
                 * @param {any} objeto
                 * @returns {string[]}
                 */
                const getPropriedades = (objeto) => {
                    if (!Object.getPrototypeOf(objeto)) {
                        return [];
                    }
                    const props = Object.entries(Object.getOwnPropertyDescriptors(objeto))
                        .filter(([prop, desc]) => prop.startsWith('data') && (desc.set || desc.writable))
                        .map(([e]) => e);
                    return getPropriedades(Object.getPrototypeOf(objeto)).concat(props);
                };
                const listaPropriedades = new Set(getPropriedades(componente));
                Propriedades.forEach(({ Nome: nome, Valor: valor }) => {
                    nome = 'data' + nome[0].toUpperCase() + nome.substring(1);
                    if (listaPropriedades.has(nome)) {
                        // @ts-ignore
                        componente[nome] = valor;
                    }
                });
            }
            if (Array.isArray(Eventos)) {
                componente.eventos = Eventos;
            }
            if (Array.isArray(Validacoes)) {
                componente.validacoes = Validacoes;
            }
            if (componente.isContainer && Filhos?.length) {
                Filhos.forEach((dadosComponente) => {
                    const container = /** @type {GOVBRFormsUCComponenteContainer} */ (componente);
                    const componenteFilho = GOVBRFormsUCComponenteFactory.getInstanciaComponente(formsUC, dadosComponente, container);
                    /** @type {GOVBRFormsUCComponente[]} */ (container.filhos).push(componenteFilho);
                });
            }
            if (formsUC.previsualizando && disparaAfterInit) {
                componente.afterInitVisualizacao();
            }
        }
        return componente;
    }

    /**
     * Retorna uma instância do componente de Input/Campo
     * @param {string} identificador
     * @param {string} formato
     */
    static getInstanciaComponenteInput(identificador, formato) {
        switch (formato) {
            case 'textarea':
                return new GOVBRFormsUCComponenteInputTextArea(identificador);
            case 'editor':
                return new GOVBRFormsUCComponenteInputEditor(identificador);
            case 'numero':
                return new GOVBRFormsUCComponenteInputNumerico(identificador);
            case 'data-hora':
                return new GOVBRFormsUCComponenteInputDataHora(identificador);
            case 'lista':
                return new GOVBRFormsUCComponenteInputLista(identificador);
            case 'booleano':
                return new GOVBRFormsUCComponenteInputBooleano(identificador);
            case 'telefone':
                return new GOVBRFormsUCComponenteInputTelefone(identificador);
            case 'email':
                return new GOVBRFormsUCComponenteInputEmail(identificador);
            case 'cpf-cnpj':
                return new GOVBRFormsUCComponenteInputCPFCNPJ(identificador);
        }
        return new GOVBRFormsUCComponenteInputTexto(identificador);
    }
}

/**
 * Classe para validadores de valores de campos.
 */
export class Validadores {
    /**
     * Verifica se o valor é um CPF ou CNPJ válido.
     * @param {string} cpfCnpj
     */
    static validaCpfCnpj(cpfCnpj) {
        const isCpf = Validadores.validaCpf(cpfCnpj);
        if (isCpf === true) {
            return true;
        }
        const isCnpj = Validadores.validaCnpj(cpfCnpj);
        if (isCnpj === true) {
            return true;
        }
        return 'O valor ' + cpfCnpj + ' não é um CPF ou CNPJ válido.';
    }

    /**
     * Verifica se o CPF é válido.
     * @param {string} cpfOriginal
     */
    static validaCpf(cpfOriginal) {
        const cpf = cpfOriginal.replace(/\D+/g, '');
        if (
            !cpf ||
            cpf.length != 11 ||
            cpf == '00000000000' ||
            cpf == '11111111111' ||
            cpf == '22222222222' ||
            cpf == '33333333333' ||
            cpf == '44444444444' ||
            cpf == '55555555555' ||
            cpf == '66666666666' ||
            cpf == '77777777777' ||
            cpf == '88888888888' ||
            cpf == '99999999999'
        ) {
            return 'O CPF ' + cpfOriginal + ' informado é inválido.';
        }
        let soma = 0;
        let resto = 0;
        for (let i = 1; i <= 9; i++) {
            soma = soma + parseInt(cpf.substring(i - 1, i)) * (11 - i);
        }
        resto = (soma * 10) % 11;
        if (resto == 10 || resto == 11) {
            resto = 0;
        }
        if (resto != parseInt(cpf.substring(9, 10))) {
            return 'O CPF ' + cpfOriginal + ' informado é inválido.';
        }
        soma = 0;
        for (let i = 1; i <= 10; i++) {
            soma = soma + parseInt(cpf.substring(i - 1, i)) * (12 - i);
        }
        resto = (soma * 10) % 11;
        if (resto == 10 || resto == 11) {
            resto = 0;
        }
        if (resto != parseInt(cpf.substring(10, 11))) {
            return 'O CPF ' + cpfOriginal + ' informado é inválido.';
        }
        return true;
    }

    /**
     * Verifica se o CNPJ é válido.
     * @param {string} cnpjOriginal
     */
    static validaCnpj(cnpjOriginal) {
        const cnpj = cnpjOriginal.replace(/\D+/g, '');
        if (
            !cnpj ||
            cnpj.length != 14 ||
            cnpj == '00000000000000' ||
            cnpj == '11111111111111' ||
            cnpj == '22222222222222' ||
            cnpj == '33333333333333' ||
            cnpj == '44444444444444' ||
            cnpj == '55555555555555' ||
            cnpj == '66666666666666' ||
            cnpj == '77777777777777' ||
            cnpj == '88888888888888' ||
            cnpj == '99999999999999'
        ) {
            return 'O CNPJ ' + cnpjOriginal + ' informado é inválido.';
        }
        let tamanho = cnpj.length - 2;
        let numeros = cnpj.substring(0, tamanho);
        const digitos = cnpj.substring(tamanho);
        let soma = 0;
        let pos = tamanho - 7;
        for (let i = tamanho; i >= 1; i--) {
            soma += parseInt(numeros.charAt(tamanho - i)) * pos--;
            if (pos < 2) {
                pos = 9;
            }
        }
        let resultado = soma % 11 < 2 ? 0 : 11 - (soma % 11);
        if (resultado !== parseInt(digitos.charAt(0))) {
            return 'O CNPJ ' + cnpjOriginal + ' informado é inválido.';
        }
        tamanho = tamanho + 1;
        numeros = cnpj.substring(0, tamanho);
        soma = 0;
        pos = tamanho - 7;
        for (let i = tamanho; i >= 1; i--) {
            soma += parseInt(numeros.charAt(tamanho - i)) * pos--;
            if (pos < 2) {
                pos = 9;
            }
        }
        resultado = soma % 11 < 2 ? 0 : 11 - (soma % 11);
        if (resultado !== parseInt(digitos.charAt(1))) {
            return 'O CNPJ ' + cnpjOriginal + ' informado é inválido.';
        }
        return true;
    }
}
