import { CATALOGO_ARQUIVOS } from '../../imgs/arquivos/catalog.js';

const TAMANHO_MAXIMO_UPLOAD_PADRAO = 16;

// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
const FORMATOS_PADROES_DOCUMENTOS = [
    // TXT
    'text/plain',
    'application/rtf',
    // PDF
    'application/pdf',
    // Imagens
    'image/png',
    'image/jpeg',
    'image/gif',
    'image/bmp',
    'image/png',
    // Planilhas
    'text/csv',
    'application/vnd.ms-excel', // xls
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // xlsx
    'application/vnd.oasis.opendocument.presentation', // odx
    // Documentos do Word
    'application/msword', // doc
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // docx
    'application/vnd.oasis.opendocument.text', // odt
    // Apresentações
    'application/vnd.ms-powerpoint', // ppt
    'application/vnd.openxmlformats-officedocument.presentationml.presentation', // pptx
    'application/vnd.oasis.opendocument.spreadsheet', // odp
];

const FORMATOS_PADROES_DOCUMENTOS_TEXTUAIS = [
    // TXT
    'text/plain',
    'application/rtf',
    // PDF
    'application/pdf',
    // Documentos do Word
    'application/msword', // doc
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // docx
    'application/vnd.oasis.opendocument.text', // odt
];

const FORMATOS_PADROES_DOCUMENTOS_IMAGENS = [
    // Imagens
    'image/png',
    'image/jpeg',
    'image/gif',
    'image/bmp',
    'image/webp',
];

const FORMATOS_PADROES_DOCUMENTOS_PLANILHAS = [
    // Planilhas
    'text/csv',
    'application/vnd.ms-excel', // xls
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // xlsx
    'application/vnd.oasis.opendocument.presentation', // odx
];

const FORMATOS_PADROES_DOCUMENTOS_APRESENTACOES = [
    // Apresentações
    'application/vnd.ms-powerpoint', // ppt
    'application/vnd.openxmlformats-officedocument.presentationml.presentation', // pptx
    'application/vnd.oasis.opendocument.spreadsheet', // odp
];

const FORMATOS_PADROES_DOCUMENTOS_PDF = [
    // PDF
    'application/pdf', // pdf
];

/**
 * @type {Record<string, string[]>}
 */
const FORMATOS = {
    '': FORMATOS_PADROES_DOCUMENTOS,
    'CUSTOM': null,
    'APRESENTACAO': FORMATOS_PADROES_DOCUMENTOS_APRESENTACOES,
    'IMAGEM': FORMATOS_PADROES_DOCUMENTOS_IMAGENS,
    'PLANILHA': FORMATOS_PADROES_DOCUMENTOS_PLANILHAS,
    'TEXTUAIS': FORMATOS_PADROES_DOCUMENTOS_TEXTUAIS,
    'PDF': FORMATOS_PADROES_DOCUMENTOS_PDF,
};

/**
 * @typedef {HTMLInputElement & {__GOVBR_ULTIMOS_ARQUIVOS: File[], __GOVBR_CHANGE: () => void}} FileDragDropInput
 */

/**
 * Classe para componente de arrastar/soltar arquivos para upload no sistema.
 */
class GOVBRFileDragDropUC {
    /**
     * @type {import('./GOVBRFileDragDropUserControl.mjs').GOVBRFileDragDropUserControl}
     */
    userControl;
    /**
     * @type {HTMLElement}
     */
    host;

    /**
     * @type {HTMLElement}
     */
    containerRaiz;

    /**
     * @type {Record<string, HTMLElement>}
     */
    containersCategorias = {};
    /**
     * @type {HTMLElement}
     */
    containerPrincipal;

    /**
     * Evita que o evento de click do container seja disparado ao clicar em um arquivo.
     * @type {boolean}
     */
    preventClick;
    /**
     * Evita que o evento de preview do arquivo seja disparado ao clicar no Remover de um arquivo.
     * @type {boolean}
     */
    preventPreview;

    /**
     * @param {import('./GOVBRFileDragDropUserControl.mjs').GOVBRFileDragDropUserControl} userControl
     * @param {HTMLElement} host
     */
    constructor(userControl, host) {
        this.userControl = userControl;
        this.host = host;
        this.host.style.setProperty('height', this.userControl.ALTURA_COMPONENTE || '100%');
        this.host.style.setProperty('width', this.userControl.LARGURA_COMPONENTE || '100%');

        GOVBR.importarStyleSheet(this.userControl.URL_CDN, 'file-drag-drop', this.userControl.VERSAO_JS_COMPONENTE, 'GOVBRFileDragDropUC', this.host).then(() => {
            GOVBR.logs.debug('File DragDrop - Carregado');
            GOVBR.logs.debug(this);
            this.recriaRaiz();
        });
    }

    /**
     * Recria a seção raiz, removendo/adicionando categorias e atualizando configurações conforme necessário.
     */
    recriaRaiz() {
        this.host.innerHTML = '';
        /**
         * @type {import('./GOVBRFileDragDropUserControl.mjs').GOVBRFileDragDropCategoriaConfig[]}
         */
        const categorias = (this.userControl.CATEGORIAS || []).filter((e) => !e.Config?.Oculto);
        /**
         * @type {import('./GOVBRFileDragDropUserControl.mjs').GOVBRFileDragDropConfig}
         */
        const configBase = this.userControl.CONFIG_BASE || {};
        const template = `
            <div class=${'govbr-file-drag-drop' + (this.userControl.COMPACTO ? ' govbr-file-drag-drop-compacto' : '')}>
                ${categorias.length ? '<div class="govbr-file-drag-drop-categorias"></div>' : ''}
                ${!configBase.Oculto ? '<div class="govbr-file-drag-drop-principal"></div>' : ''}
            </div>
        `;
        this.host.innerHTML = template;
        if (configBase.Oculto) {
            this.containerPrincipal = null;
        } else {
            if (!this.containerPrincipal) {
                this.containerPrincipal = this.montaSecaoUploadArquivos(null, null);
            }
            this.atualizaConfigSecao(this.containerPrincipal, configBase);
            const container = this.host.querySelector('.govbr-file-drag-drop-principal');
            if (categorias.length) {
                const titulo = document.createElement('h3');
                titulo.className = 'govbr-file-drag-drop-categoria-titulo';
                titulo.innerText = 'Outros';
                container.append(titulo);
            }
            container.append(this.containerPrincipal);
        }
        /**
         * @type {Record<string, boolean>}
         */
        const categoriasRenderizada = {};
        categorias.forEach((categoria) => {
            if (!categoria.Id) {
                GOVBR.logs.error(categoria, ' não possui id, isto poderá gerar problemas e conflitos na geração de valores...');
            }
            categoriasRenderizada[categoria.Id] = true;
            if (!this.containersCategorias[categoria.Id]) {
                this.containersCategorias[categoria.Id] = this.montaSecaoUploadArquivos(categoria.Id, categoria.Titulo);
            }
            this.atualizaConfigSecao(this.containersCategorias[categoria.Id], categoria.Config || {});

            const container = document.createElement('section');
            container.className = 'govbr-file-drag-drop-categoria';
            const titulo = document.createElement('h3');
            titulo.className = 'govbr-file-drag-drop-categoria-titulo';
            titulo.innerText = categoria.Titulo;
            container.append(titulo);
            container.append(this.containersCategorias[categoria.Id]);

            this.host.querySelector('.govbr-file-drag-drop-categorias').append(container);
        });
        Object.keys(this.containersCategorias).forEach((idCategoria) => {
            if (!categoriasRenderizada[idCategoria]) {
                delete this.containersCategorias[idCategoria];
            }
        });
    }

    /**
     * Monta uma seção para envio de documentos.
     * @param {string|null} id
     * @param {string|null} titulo
     * @returns {HTMLElement}
     */
    montaSecaoUploadArquivos(id, titulo) {
        const container = document.createElement('article');
        container.className = 'govbr-file-drag-drop-secao-drag-over';
        container.title = 'Lista de Arquivos' + (titulo ? ' da categoria ' + titulo : '');
        const idInput = GOVBR.ComponentUtils.escapaValorAtributoHTML(this.userControl.IDENTIFICADOR_COMPONENTE + (id ? '_' + id : ''));
        container.innerHTML = `
            <i class="govbr-file-drag-drop-icone-drag-over fa-solid fa-upload"></i>
            <span aria-hidden="true" class="govbr-file-drag-drop-texto-drag-over">
                Arraste e solte arquivos, ou clique aqui para enviar.
            </span>
            <div class="govbr-file-drag-drop-arquivos"></div>
            <input class="govbr-file-drag-drop-input" title="${titulo || 'Outros'}" type="file" id="${idInput}" name="${idInput}"></input>
        `;
        container.addEventListener('click', () => this.onClick(container));
        container.addEventListener('dragenter', (event) => this.onDragEnter(container, event));
        container.addEventListener('dragleave', (event) => this.onDragLeave(container, event));
        container.addEventListener('dragover', (event) => this.onDragOver(container, event));
        container.addEventListener('drop', (event) => this.onDrop(container, event));
        return container;
    }

    /**
     * Atualiza as configurações de uma seção.
     * @param {HTMLElement} container
     * @param {import('./GOVBRFileDragDropUserControl.mjs').GOVBRFileDragDropConfig} config
     */
    atualizaConfigSecao(container, config) {
        const arquivos = container.querySelector('.govbr-file-drag-drop-arquivos');
        if (config.Obrigatorio) {
            arquivos.classList.add('govbr-file-drag-drop-obrigatorio');
        } else {
            arquivos.classList.remove('govbr-file-drag-drop-obrigatorio');
        }
        const input = /** @type {FileDragDropInput} */ (container.querySelector('.govbr-file-drag-drop-input'));
        input.__GOVBR_CHANGE =
            input.__GOVBR_CHANGE ||
            (() => {
                const dataTransfer = new DataTransfer();
                Array.from(input.files).forEach((file) => {
                    dataTransfer.items.add(file);
                });
                Array.from(input.__GOVBR_ULTIMOS_ARQUIVOS || []).forEach((file) => {
                    dataTransfer.items.add(file);
                });
                input.files = dataTransfer.files;
                this.validaArquivos(input, config);
                input.__GOVBR_ULTIMOS_ARQUIVOS = Array.from(input.files);
                this.atualizaListaArquivos(container);
            });
        input.addEventListener('change', input.__GOVBR_CHANGE);
        let accept = FORMATOS[config.FormatoPadrao || ''];
        if (!accept) {
            if (config.FormatoPadrao !== 'CUSTOM') {
                GOVBR.logs.warn('Formato "' + config.FormatoPadrao + '" desconhecido, assuming "CUSTOM";');
            }
            accept = config.ListaFormatos;
        }
        input.accept = (accept || []).join(', ');
        input.multiple = config.Multiplo;
        input.required = config.Obrigatorio;
    }

    /**
     * Evento ao clicar em um container de arquivos.
     * @param {HTMLElement} container
     */
    onClick(container) {
        if (this.preventClick) {
            return;
        }
        /** @type {FileDragDropInput} */ (container.querySelector('.govbr-file-drag-drop-input')).showPicker();
    }

    /**
     * Evento ao entrar com um arquivo arrastando em um container de arquivos.
     * @param {HTMLElement} container
     * @param {DragEvent} event
     */
    onDragEnter(container, event) {
        event.preventDefault();
        container.classList.add('govbr-file-drag-drop-recebendo-arquivo');
    }

    /**
     * Evento ao sair com um arquivo arrastando em um container de arquivos.
     * @param {HTMLElement} container
     * @param {DragEvent} event
     */
    onDragLeave(container, event) {
        event.preventDefault();
        container.classList.remove('govbr-file-drag-drop-recebendo-arquivo');
    }

    /**
     * Evento ao arrastar sobre com um arquivo em um container de arquivos.
     * @param {HTMLElement} container
     * @param {DragEvent} event
     */
    onDragOver(container, event) {
        event.preventDefault();
        container.classList.add('govbr-file-drag-drop-recebendo-arquivo');
    }

    /**
     * Evento ao soltar um arquivo arrastado em um container de arquivos.
     * @param {HTMLElement} container
     * @param {DragEvent} event
     */
    onDrop(container, event) {
        event.preventDefault();
        const input = /** @type {FileDragDropInput} */ (container.querySelector('.govbr-file-drag-drop-input'));
        input.files = event.dataTransfer.files;
        input.dispatchEvent(new Event('change'));
        container.classList.remove('govbr-file-drag-drop-recebendo-arquivo');
    }

    /**
     * Valida os arquivos presentes no input.
     * @param {FileDragDropInput} input
     * @param {import('./GOVBRFileDragDropUserControl.mjs').GOVBRFileDragDropConfig} config
     */
    validaArquivos(input, config) {
        let files = Array.from(input.files);
        files = files.filter((file) => {
            let accept = FORMATOS[config.FormatoPadrao || ''];
            if (!accept) {
                accept = config.ListaFormatos;
            }
            return this.validaFormato(file, accept) && this.validaTamanho(file, config.TamanhoMaximoMegaBytes || TAMANHO_MAXIMO_UPLOAD_PADRAO);
        });
        if (files.length && !config.Multiplo) {
            files = [files[0]];
        }
        input.files = files.reduce((data, file) => {
            data.items.add(file);
            return data;
        }, new DataTransfer()).files;
    }

    /**
     * Valida se o formato do arquivo é válido.
     * @param {File} file
     * @param {string[]} formatos
     * @returns {boolean}
     */
    validaFormato(file, formatos) {
        if (!formatos?.length) {
            return true;
        }
        if (!new RegExp('^' + formatos.join('$|^').replace(/\*/, '.*') + '$').test(file.type)) {
            GOVBR.mensagem('O formato do arquivo ' + file.name + ' não é suportado.', 'alerta');
            return false;
        }
        return true;
    }

    /**
     * Valida se o tamanho do arquivo é válido.
     * @param {File} file
     * @param {number} maximo
     * @returns {boolean}
     */
    validaTamanho(file, maximo) {
        if (file.size <= 0) {
            GOVBR.mensagem('O arquivo ' + file.name + ' está vazio ou corrompido.', 'alerta');
            return false;
        }
        if (maximo > 0 && file.size > maximo * 1024 * 1024) {
            GOVBR.mensagem('O arquivo ' + file.name + ' é maior do que o permitido (' + maximo + 'MB).', 'alerta');
            return false;
        }
        return true;
    }

    /**
     * Atualiza a lista de arquivos no container.
     * @param {HTMLElement} container
     */
    atualizaListaArquivos(container) {
        const input = /** @type {FileDragDropInput} */ (container.querySelector('.govbr-file-drag-drop-input'));
        const lista = container.querySelector('.govbr-file-drag-drop-arquivos');
        lista.innerHTML = '';
        Array.from(input.files).forEach((file, idx) => {
            const divFile = document.createElement('div');
            divFile.classList.add('govbr-file-drag-drop-arquivo');
            divFile.title = file.name;

            const podeVisualizar = this.trataFormatoPreviewArquivo(divFile, file);

            const titulo = document.createElement('span');
            titulo.classList.add('govbr-file-drag-drop-titulo-arquivo');
            titulo.innerText = file.name;
            const remover = document.createElement('button');
            remover.type = 'button';
            remover.classList.add('govbr-file-drag-drop-arquivo-remover');
            remover.addEventListener('click', () => {
                const files = Array.from(input.files);
                files.splice(idx, 1);
                input.files = files.reduce((data, file) => {
                    data.items.add(file);
                    return data;
                }, new DataTransfer()).files;
                input.__GOVBR_ULTIMOS_ARQUIVOS = [];
                input.dispatchEvent(new Event('change'));
                // Evita abrir o preview ao remover um arquivo.
                this.preventPreview = true;
                setTimeout(() => {
                    this.preventPreview = false;
                }, 0);
            });
            const iconeRemover = document.createElement('i');
            iconeRemover.classList.add('fa-solid');
            iconeRemover.classList.add('fa-times');

            remover.append(iconeRemover);
            titulo.prepend(remover);
            divFile.append(titulo);

            if (podeVisualizar) {
                divFile.classList.add('govbr-file-drag-drop-arquivo-pode-visualizar');
            }
            divFile.addEventListener('click', () => {
                if (!this.preventPreview && podeVisualizar) {
                    GOVBR.bloqueiaElemento();
                    this.montaPreviewArquivo(file).then(
                        (preview) => {
                            GOVBR.desbloqueiaElemento();
                            const popup = GOVBR.ComponentUtils.abreDialog(file.name + ' (' + GOVBR.ComponentUtils.converteUMTamanhoBytes(file.size) + ')');
                            popup.classList.add('govbr-file-drag-drop-visualizar');
                            preview.classList.add('govbr-file-drag-drop-visualizar-conteudo');
                            popup.append(preview);
                        },
                        (error) => {
                            GOVBR.mensagem('Não foi possível carregar a pré-visualização do arquivo.', 'aviso');
                            GOVBR.logs.error(error);
                            GOVBR.desbloqueiaElemento();
                        }
                    );
                }
                // Evita abrir o file picker ao clicar em um arquivo.
                this.preventClick = true;
                setTimeout(() => {
                    this.preventClick = false;
                }, 0);
            });

            lista.append(divFile);
        });
        if (input.files.length) {
            container.classList.add('govbr-file-drag-drop-possui-arquivos');
        } else {
            container.classList.remove('govbr-file-drag-drop-possui-arquivos');
        }
    }

    /**
     * Trata a previsualização do arquivo pelo formato
     * @param {HTMLElement} divFile
     * @param {File} file
     */
    trataFormatoPreviewArquivo(divFile, file) {
        const preview = document.createElement('img');
        preview.classList.add('govbr-file-drag-drop-preview-arquivo');
        const baseUrl = new URL('../../imgs/arquivos/1.0/', import.meta.url);
        divFile.append(preview);
        const [fileTypeIni, fileTypeFim] = file.type.split('/');
        let fallback = 'blank';
        let podeVisualizar = false;
        let imagemFormato = '';

        switch (fileTypeIni) {
            case 'image':
                this.montaThumbnail(file).then(
                    (img) => {
                        preview.parentElement.prepend(img);
                    },
                    (error) => {
                        GOVBR.mensagem('Não foi possível carregar a pré-visualização do arquivo.', 'aviso');
                        GOVBR.logs.error(error);
                    }
                );
                if (fileTypeFim === 'svg+xml') {
                    imagemFormato = 'svg';
                } else {
                    imagemFormato = fileTypeFim;
                }
                fallback = 'image';
                podeVisualizar = file.size > 0;
                break;
            case 'text':
                imagemFormato = fileTypeFim;
                fallback = 'txt';
                podeVisualizar = file.size > 0;
                break;
            case 'application':
                switch (fileTypeFim) {
                    case 'vnd.ms-powerpoint':
                    case 'vnd.openxmlformats-officedocument.presentationml.presentation':
                    case 'vnd.oasis.opendocument.spreadsheet':
                        imagemFormato = 'pptx';
                        break;
                    case 'msword':
                    case 'vnd.openxmlformats-officedocument.wordprocessingml.document':
                    case 'vnd.oasis.opendocument.text':
                        imagemFormato = 'docx';
                        break;
                    case 'vnd.ms-excel':
                    case 'vnd.openxmlformats-officedocument.spreadsheetml.sheet':
                    case 'vnd.oasis.opendocument.presentation':
                        imagemFormato = 'xlsx';
                        break;
                    case 'pdf':
                        imagemFormato = 'pdf';
                        podeVisualizar = true;
                        break;
                    case 'json':
                        imagemFormato = 'json';
                        podeVisualizar = true;
                        break;
                    case 'x-zip-compressed':
                        imagemFormato = 'zip';
                        break;
                    default:
                        imagemFormato = fileTypeFim;
                        break;
                }
                break;
            default:
                imagemFormato = fileTypeFim;
                break;
        }
        if (!CATALOGO_ARQUIVOS[imagemFormato]) {
            imagemFormato = fallback;
        }
        preview.src = new URL(imagemFormato + '.svg', baseUrl).toString();
        preview.onerror = () => {
            preview.onerror = null;
            preview.src = new URL(fallback + '.svg', baseUrl).toString();
        };

        return podeVisualizar;
    }

    /**
     * Monta a prévisualização do arquivo.
     * @param {File} arquivo
     * @returns {Promise<HTMLElement>}
     */
    async montaPreviewArquivo(arquivo) {
        const [fileTypeIni, fileTypeFim] = arquivo.type.split('/');
        /**
         * @param {boolean} asDataUrl
         * @returns {Promise<string>}
         */
        const readConteudo = (asDataUrl) => {
            const reader = new FileReader();
            return new Promise((resolve, reject) => {
                reader.onload = (event) => {
                    resolve(event.target.result.toString());
                };
                reader.onerror = (ev) => {
                    GOVBR.logs.error(ev);
                    reject('Erro ao ler o arquivo ' + arquivo.name);
                };
                if (asDataUrl) {
                    reader.readAsDataURL(arquivo);
                } else {
                    reader.readAsText(arquivo);
                }
            });
        };
        if (fileTypeIni === 'image') {
            const img = document.createElement('img');
            img.src = URL.createObjectURL(arquivo);
            img.className = 'govbr-file-drag-drop-visualizar-imagem';
            return img;
        }
        if (fileTypeIni == 'application' && fileTypeFim === 'pdf') {
            const iframe = document.createElement('iframe');
            iframe.src = URL.createObjectURL(arquivo);
            iframe.className = 'govbr-file-drag-drop-visualizar-application';
            return iframe;
        }
        const container = document.createElement('div');
        container.className = 'govbr-file-drag-drop-visualizar-texto';

        const containerInput = document.createElement('div');
        containerInput.className = 'govbr-file-drag-drop-visualizar-texto-input-container';
        const input = document.createElement('input');
        input.id = 'preview_quebra_linha';
        input.name = 'preview_quebra_linha';
        input.type = 'checkbox';
        input.checked = true;
        input.addEventListener('change', () => {
            preview.classList.toggle('govbr-file-drag-drop-visualizar-texto-wrap', input.checked);
        });
        input.className = 'govbr-file-drag-drop-visualizar-texto-input';
        const label = document.createElement('label');
        label.innerText = 'Quebrar Linha';
        label.htmlFor = input.id;
        label.className = 'govbr-file-drag-drop-visualizar-texto-label';
        containerInput.append(input);
        containerInput.append(label);
        container.append(containerInput);

        const preview = document.createElement('pre');
        preview.className = 'govbr-file-drag-drop-visualizar-texto-wrap';
        let conteudo = await readConteudo(false);
        if (conteudo.length > 524288) {
            preview.classList.add('govbr-file-drag-drop-visualizar-texto-gigante');
            conteudo = conteudo.slice(0, 524288) + '\n\n...';
        }
        preview.innerText = conteudo;
        container.append(preview);
        return container;
    }

    /**
     * Monta a thumbnail para um arquivo.
     * @param {File} arquivo
     * @returns {Promise<HTMLImageElement>}
     */
    async montaThumbnail(arquivo) {
        const img = document.createElement('img');
        img.classList.add('govbr-file-drag-drop-preview-img', 'govbr-file-drag-drop-imagem-preview-temp');
        const reader = new FileReader();
        img.src = await new Promise((resolve, reject) => {
            reader.onload = (event) => {
                img.onload = (_) => {
                    img.onload = null;
                    img.remove();
                    img.classList.remove('govbr-file-drag-drop-imagem-preview-temp');
                    const canvas = document.createElement('canvas');
                    const ctx = canvas.getContext('2d');
                    if (img.width < img.height) {
                        const tam = (img.height / img.width) * 96;
                        canvas.width = 96;
                        canvas.height = tam;
                        ctx.drawImage(img, 0, 0, 96, tam);
                    } else {
                        const tam = (img.width / img.height) * 96;
                        canvas.width = tam;
                        canvas.height = 96;
                        ctx.drawImage(img, 0, 0, tam, 96);
                    }
                    try {
                        resolve(canvas.toDataURL());
                    } catch (ex) {
                        GOVBR.logs.error(ex);
                        reject('Erro ao ler o arquivo ' + arquivo.name);
                    }
                };
                img.onerror = (ev) => {
                    img.remove();
                    GOVBR.logs.error(ev);
                    reject('Erro ao ler o arquivo ' + arquivo.name);
                };
                img.src = event.target.result.toString();
                document.body.append(img);
            };
            reader.onerror = (ev) => {
                GOVBR.logs.error(ev);
                reject('Erro ao ler o arquivo ' + arquivo.name);
            };
            reader.readAsDataURL(arquivo);
        });
        return img;
    }

    /**
     * Envia os arquivos para o servidor.
     * @param {boolean} confirma
     */
    async salvaValores(confirma) {
        /**
         * @type {import('./GOVBRFileDragDropUserControl.mjs').GOVBRFileDragDropValor[]}
         */
        const valoresFinais = [];
        /**
         * @type {Record<string, FileDragDropInput>}
         */
        const categorias = {};
        if (this.containerPrincipal) {
            categorias[''] = this.containerPrincipal.querySelector('.govbr-file-drag-drop-input');
        }
        Object.entries(this.containersCategorias).forEach(([id, container]) => {
            categorias[id] = container.querySelector('.govbr-file-drag-drop-input');
        });
        try {
            if (confirma) {
                Object.values(categorias).forEach((input) => {
                    if (!input.checkValidity()) {
                        throw new Error('Selecione pelo menos um arquivo na categoria ' + input.title);
                    }
                });
            }
            await Promise.all(
                Object.entries(categorias).map(
                    async ([
                        idCategoria,
                        input,
                    ]) => {
                        if (confirma) {
                            const valores = await this.salvaValoresCategoria(input);
                            valores.forEach(({ IdUpload, MimeType, Titulo }) => {
                                valoresFinais.push({
                                    IdCategoria: idCategoria,
                                    IdUpload,
                                    Titulo,
                                    MimeType,
                                });
                            });
                        } else if (input.files?.length) {
                            Array.from(input.files).forEach((arquivo) => {
                                valoresFinais.push({
                                    IdCategoria: idCategoria,
                                    IdUpload: '',
                                    Titulo: arquivo.name,
                                    MimeType: arquivo.type,
                                });
                            });
                        }
                    }
                )
            );
        } catch (ex) {
            GOVBR.logs.error(ex);
            if (ex instanceof Error) {
                GOVBR.mensagem(ex.message, 'alerta');
            } else {
                throw ex;
            }
        }
        this.userControl.VALORES = valoresFinais;
    }

    /**
     * Salva os arquivos da categoria
     * @param {FileDragDropInput} input
     * @returns {Promise<{IdUpload: string, Titulo: string, MimeType: string}[]>}
     */
    async salvaValoresCategoria(input) {
        const arquivos = input.files;
        if (!arquivos.length) {
            return [];
        }
        const formData = new FormData();
        Array.from(arquivos).forEach((arquivo) => {
            formData.append(arquivo.name, arquivo);
        });
        let response;
        try {
            response = await this.enviaArquivos(formData);
        } catch (ex) {
            if (!ex) {
                return [];
            }
            throw new Error('Falha ao enviar os arquivos para o servidor');
        }
        if (response.status !== 200) {
            throw new Error('Resposta inválida do servidor: ' + response.status);
        }
        const resposta = response.response;
        try {
            /**
             * @type {{Nome: string, Tamanho: number, Tipo: string, GUID: string}[]}
             */
            const retorno = await JSON.parse(resposta);
            return retorno.map((e) => ({
                IdUpload: e.GUID,
                Titulo: e.Nome,
                MimeType: e.Tipo,
            }));
        } catch (ex) {
            if (resposta) {
                let retorno;
                try {
                    retorno = await JSON.parse(resposta);
                } catch (_) {}
                if (retorno?.message || retorno?.mensagem) {
                    GOVBR.mensagem(retorno?.message, 'erro');
                    throw new Error();
                }
                throw new Error('Resposta inválida do servidor: ' + resposta);
            } else {
                throw new Error('O servidor respondeu ao envio do arquivo.');
            }
        }
    }

    /**
     * Envia os arquivos para o servidor
     * @param {FormData} formData
     * @return {Promise<{status: number, response: string}>}
     */
    async enviaArquivos(formData) {
        return new Promise((resolve, reject) => {
            var request = new XMLHttpRequest();

            /**
             * @type {{nome: string, tamanho: number}[]}
             */
            const arquivos = [];
            formData.forEach((arquivo) => {
                if (arquivo instanceof File) {
                    arquivos.push({ nome: arquivo.name, tamanho: arquivo.size });
                }
            });
            GOVBR.bloqueiaElemento();
            const progresso = GOVBR.ComponentUtils.montaBarraProgresso();
            progresso.setTitulo(arquivos[0].nome);
            progresso.setTotal(arquivos[0].tamanho);
            progresso.setValor(0);
            /**
             * @type {{ nome: string, tamanho: number }}
             */
            let arquivoAtual;
            request.upload.addEventListener('progress', function (progress) {
                let idx = 0;
                let novoArquivoAtual = arquivos[idx];
                let progressoAtual = progress.loaded;
                while (novoArquivoAtual && progressoAtual - novoArquivoAtual.tamanho > 0) {
                    progressoAtual -= novoArquivoAtual.tamanho;
                    novoArquivoAtual = arquivos[++idx];
                }
                if (novoArquivoAtual) {
                    arquivoAtual = novoArquivoAtual;
                    progresso.setTitulo(arquivoAtual.nome);
                    progresso.setTotal(arquivoAtual.tamanho);
                    progresso.setValor(progressoAtual);
                    progresso.setProgressoTexto(
                        GOVBRPlataformaJS.ComponentUtils.converteUMTamanhoBytes(progressoAtual) +
                            ' de ' +
                            GOVBRPlataformaJS.ComponentUtils.converteUMTamanhoBytes(arquivoAtual.tamanho)
                    );
                } else if (arquivoAtual) {
                    progresso.setTotal(arquivoAtual.tamanho);
                    progresso.setValor(arquivoAtual.tamanho);
                    progresso.setProgressoTexto(
                        GOVBRPlataformaJS.ComponentUtils.converteUMTamanhoBytes(arquivoAtual.tamanho) +
                            ' de ' +
                            GOVBRPlataformaJS.ComponentUtils.converteUMTamanhoBytes(arquivoAtual.tamanho)
                    );
                }
            });

            request.onload = () => {
                progresso.remove();
                GOVBR.desbloqueiaElemento();
                GOVBR.logs.info('Resposta de ' + path + ': ' + request.status);
                resolve({
                    status: request.status,
                    response: request.response,
                });
            };
            request.onerror = () => {
                progresso.remove();
                GOVBR.desbloqueiaElemento();
                reject(true);
            };
            request.onabort = () => {
                progresso.remove();
                GOVBR.desbloqueiaElemento();
                reject(false);
            };

            const path = '/' + (window.location.pathname.match(/^\/(\w+\/)?/)[1] || '') + 'govbrlibrary.upload.arquivoscarregar.aspx';
            GOVBR.logs.info('Realizando upload de um arquivo para endpoint ' + path + '.');
            request.open('post', path);
            request.responseType = 'text';
            request.send(formData);
        });
    }
}

// Caso esteja no browser, disponibiliza o widget no objeto de window.
if (typeof window !== 'undefined') {
    // @ts-ignore
    window['GOVBRFileDragDropUC'] = GOVBRFileDragDropUC;
}
export {
    GOVBRFileDragDropUC,
    FORMATOS_PADROES_DOCUMENTOS,
    FORMATOS_PADROES_DOCUMENTOS_APRESENTACOES,
    FORMATOS_PADROES_DOCUMENTOS_IMAGENS,
    FORMATOS_PADROES_DOCUMENTOS_PLANILHAS,
    FORMATOS_PADROES_DOCUMENTOS_TEXTUAIS,
};
