const PDF_CONVERSION_MAX_DIMENSION = 2200;
const PDF_CONVERSION_JPEG_QUALITY = 0.82;

document.addEventListener('DOMContentLoaded', () => {
    initConditionalFields();
    initDependents();
    initMasks();
    initDataTable();
    initDocumentPdfAutoConversion();
    initFilePreviews();
    initUpdateModal();
    initFormWizard();
    initUploadGuard();
    initFormTimeout();
    initSmartSubmit();
    scrollToFirstError();
});



function initFormWizard() {
    const form = document.querySelector('form[data-mobile-wizard="true"]');
    if (!form) return;

    const sections = Array.from(form.querySelectorAll('.form-section'));
    if (sections.length < 2) return;

    const finalActions = form.querySelector('.wizard-final-actions');
    const mobileQuery = window.matchMedia('(max-width: 991.98px)');
    let wizard = null;
    let currentStep = 0;

    const getSectionTitle = (section, index) => {
        const heading = section.querySelector('.section-header h2');
        if (!heading) return `Etapa ${index + 1}`;
        return heading.textContent.replace(/^\s*\d+[\.-]?\s*/, '').trim();
    };

    const sectionHasError = (section) => Boolean(section.querySelector('.is-invalid, .field-error'));

    const getInitialStep = () => {
        const invalidIndex = sections.findIndex(sectionHasError);
        return invalidIndex >= 0 ? invalidIndex : 0;
    };

    const focusActiveStep = () => {
        const activeSection = sections[currentStep];
        if (!activeSection) return;

        window.requestAnimationFrame(() => {
            activeSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
        });
    };

    const updateWizardUi = (shouldScroll = false) => {
        if (!wizard) return;

        sections.forEach((section, index) => {
            section.classList.add('wizard-step');
            section.classList.toggle('is-active', index === currentStep);
            if (index === currentStep) {
                section.removeAttribute('aria-hidden');
            } else {
                section.setAttribute('aria-hidden', 'true');
            }
        });

        const title = getSectionTitle(sections[currentStep], currentStep);
        const progress = ((currentStep + 1) / sections.length) * 100;
        const isLastStep = currentStep === sections.length - 1;

        wizard.stepCounter.textContent = `Etapa ${currentStep + 1} de ${sections.length}`;
        wizard.stepTitle.textContent = title;
        wizard.progressFill.style.width = `${progress}%`;
        wizard.prevButton.disabled = currentStep === 0;
        wizard.nav.hidden = isLastStep;

        if (finalActions) {
            finalActions.hidden = !isLastStep;
            finalActions.classList.toggle('is-active', isLastStep);
        }

        if (shouldScroll) {
            focusActiveStep();
        }
    };

    const setStep = (index, shouldScroll = true) => {
        currentStep = Math.max(0, Math.min(index, sections.length - 1));
        updateWizardUi(shouldScroll);
    };

    const enableWizard = () => {
        if (wizard) {
            updateWizardUi(false);
            return;
        }

        form.classList.add('mobile-wizard-enabled');

        const header = document.createElement('div');
        header.className = 'mobile-wizard-header';
        header.innerHTML = `
            <div class="mobile-wizard-header__meta">
                <span class="mobile-wizard-counter"></span>
                <strong class="mobile-wizard-title"></strong>
            </div>
            <div class="mobile-wizard-progress" aria-hidden="true">
                <span class="mobile-wizard-progress__fill"></span>
            </div>
        `;

        const nav = document.createElement('div');
        nav.className = 'mobile-wizard-nav';
        nav.innerHTML = `
            <button type="button" class="btn btn-outline-secondary mobile-wizard-prev">Voltar</button>
            <button type="button" class="btn btn-primary mobile-wizard-next">Próxima etapa</button>
        `;

        form.insertBefore(header, sections[0]);
        if (finalActions && finalActions.parentNode === form) {
            form.insertBefore(nav, finalActions);
        } else {
            form.appendChild(nav);
        }

        wizard = {
            header,
            nav,
            stepCounter: header.querySelector('.mobile-wizard-counter'),
            stepTitle: header.querySelector('.mobile-wizard-title'),
            progressFill: header.querySelector('.mobile-wizard-progress__fill'),
            prevButton: nav.querySelector('.mobile-wizard-prev'),
            nextButton: nav.querySelector('.mobile-wizard-next')
        };

        wizard.prevButton.addEventListener('click', () => setStep(currentStep - 1));
        wizard.nextButton.addEventListener('click', () => setStep(currentStep + 1));

        currentStep = getInitialStep();
        updateWizardUi(false);
    };

    const disableWizard = () => {
        form.classList.remove('mobile-wizard-enabled');
        sections.forEach((section) => {
            section.classList.remove('wizard-step', 'is-active');
            section.removeAttribute('aria-hidden');
        });

        if (finalActions) {
            finalActions.hidden = false;
            finalActions.classList.remove('is-active');
        }

        if (wizard) {
            wizard.header.remove();
            wizard.nav.remove();
            wizard = null;
        }
    };

    const syncWizardMode = () => {
        if (mobileQuery.matches) {
            enableWizard();
        } else {
            disableWizard();
        }
    };

    syncWizardMode();

    if (typeof mobileQuery.addEventListener === 'function') {
        mobileQuery.addEventListener('change', syncWizardMode);
    } else if (typeof mobileQuery.addListener === 'function') {
        mobileQuery.addListener(syncWizardMode);
    }
}



function renderFormAlert(form, type, storageClass, message, hint = '', shouldScroll = true) {
    let alert = form.querySelector(`.${storageClass}`);
    if (!alert) {
        alert = document.createElement('div');
        form.insertBefore(alert, form.firstChild);
    }

    alert.className = `alert alert-${type} shadow-sm form-alert ${storageClass}`;
    alert.innerHTML = `
        <strong>${escapeHtml(message)}</strong>
        ${hint ? `<div class="small mt-2">${escapeHtml(hint)}</div>` : ''}
    `;

    if (shouldScroll) {
        window.requestAnimationFrame(() => {
            alert.scrollIntoView({ behavior: 'smooth', block: 'start' });
        });
    }

    return alert;
}


function resetSubmitState(form) {
    if (!form) return;

    delete form.dataset.submitting;

    form.querySelectorAll('button[type="submit"], input[type="submit"]').forEach((button) => {
        button.disabled = false;
        button.classList.remove('is-submitting');

        if (button.tagName === 'BUTTON' && button.dataset.originalText) {
            button.innerHTML = button.dataset.originalText;
            delete button.dataset.originalText;
        }
    });
}

function initSmartSubmit() {
    document.querySelectorAll('form').forEach((form) => {
        form.addEventListener('submit', (event) => {
            if (event.defaultPrevented || form.dataset.submitting === '1' || form.dataset.formExpired === '1') {
                return;
            }

            window.setTimeout(() => {
                if (event.defaultPrevented || form.dataset.formExpired === '1') {
                    resetSubmitState(form);
                    return;
                }

                form.dataset.submitting = '1';

                form.querySelectorAll('button[type="submit"], input[type="submit"]').forEach((button) => {
                    button.disabled = true;
                    button.classList.add('is-submitting');
                    if (button.tagName === 'BUTTON' && !button.dataset.originalText) {
                        button.dataset.originalText = button.innerHTML;
                        button.innerHTML = 'Enviando...';
                    }
                });
            }, 0);
        });
    });
}

function initUploadGuard() {
    document.querySelectorAll('form[data-upload-guard="true"]').forEach((form) => {
        const maxFileSizeBytes = parseInt(form.dataset.maxFileSizeBytes || '0', 10);
        const maxFileSizeLabel = form.dataset.maxFileSizeLabel || '0 B';
        const postMaxBytes = parseInt(form.dataset.postMaxBytes || '0', 10);
        const postMaxLabel = form.dataset.postMaxLabel || '0 B';

        const renderError = (message) => renderFormAlert(
            form,
            'danger',
            'js-upload-error',
            message,
            'Revise o tipo e o tamanho do arquivo antes de tentar novamente.'
        );

        form.addEventListener('submit', (event) => {
            const previousError = form.querySelector('.js-upload-error');
            if (previousError) previousError.remove();

            const pendingConversions = parseInt(form.dataset.pendingConversions || '0', 10);
            if (pendingConversions > 0) {
                event.preventDefault();
                renderError('Aguarde a conversão automática das imagens para PDF terminar antes de enviar a ficha.');
                resetSubmitState(form);
                return;
            }

            const fileInputs = Array.from(form.querySelectorAll('input[type="file"]'));
            let totalSize = 0;
            let oversizedFile = null;

            fileInputs.forEach((input) => {
                Array.from(input.files || []).forEach((file) => {
                    totalSize += file.size || 0;
                    if (!oversizedFile && maxFileSizeBytes > 0 && (file.size || 0) > maxFileSizeBytes) {
                        oversizedFile = file;
                    }
                });
            });

            if (oversizedFile) {
                event.preventDefault();
                renderError(`O arquivo "${oversizedFile.name}" excede ${maxFileSizeLabel}. Reduza o tamanho e tente novamente.`);
                resetSubmitState(form);
                return;
            }

            if (postMaxBytes > 0 && totalSize > postMaxBytes) {
                event.preventDefault();
                renderError(`A soma dos anexos passou do limite total de ${postMaxLabel}. Reduza o tamanho dos arquivos e tente novamente.`);
                resetSubmitState(form);
            }
        });
    });
}


function initFormTimeout() {
    const timeoutForms = Array.from(document.querySelectorAll('form[data-form-time-limit-seconds]'));
    if (timeoutForms.length === 0) return;

    const groups = new Map();

    timeoutForms.forEach((form, index) => {
        const limitSeconds = parseInt(form.dataset.formTimeLimitSeconds || '0', 10);
        if (!Number.isFinite(limitSeconds) || limitSeconds <= 0) return;

        const scope = String(form.dataset.timeoutScope || form.getAttribute('action') || `timeout-form-${index}`);
        if (!groups.has(scope)) {
            groups.set(scope, {
                forms: [],
                limitSeconds,
                alertHost: form,
                storageKey: `admission-form-timeout-message:${scope}`,
                deadline: Date.now() + (limitSeconds * 1000)
            });
        }

        const group = groups.get(scope);
        group.forms.push(form);
        if (!group.alertHost) {
            group.alertHost = form;
        }
    });

    groups.forEach((group) => {
        const { forms, limitSeconds, alertHost, storageKey, deadline } = group;
        if (!alertHost || forms.length === 0) return;

        const limitLabel = `${Math.ceil(limitSeconds / 60)} min`;
        let timerId = null;
        let expired = false;

        const resetGroupSubmitState = () => {
            forms.forEach((form) => resetSubmitState(form));
        };

        const reloadCleanForm = () => {
            try {
                sessionStorage.setItem(storageKey, `O tempo máximo de ${limitLabel} foi atingido. A ficha foi reiniciada.`);
            } catch (error) {
                // ignore storage issues
            }

            forms.forEach((form) => {
                try {
                    form.reset();
                    form.querySelectorAll('input[type="file"]').forEach((input) => {
                        input.value = '';
                    });
                } catch (error) {
                    // ignore reset issues
                }
            });

            const targetUrl = `${window.location.pathname}${window.location.search}`;
            window.setTimeout(() => {
                window.location.replace(targetUrl);
            }, 900);
        };

        const renderStoredMessage = () => {
            try {
                const message = sessionStorage.getItem(storageKey);
                if (!message) return;
                sessionStorage.removeItem(storageKey);
                renderFormAlert(alertHost, 'warning', 'js-form-timeout-message', message, 'Preencha e envie novamente dentro do tempo limite.', false);
            } catch (error) {
                // ignore storage issues
            }
        };

        const renderTimer = () => {
            const remainingSeconds = Math.max(0, Math.ceil((deadline - Date.now()) / 1000));
            const minutes = String(Math.floor(remainingSeconds / 60)).padStart(2, '0');
            const seconds = String(remainingSeconds % 60).padStart(2, '0');
            const alert = renderFormAlert(
                alertHost,
                remainingSeconds <= 60 ? 'warning' : 'info',
                'js-form-deadline-alert',
                `Tempo restante para envio: ${minutes}:${seconds}`,
                `Após ${limitLabel}, a página será recarregada e a ficha será limpa automaticamente.`,
                false
            );

            alert.classList.toggle('alert-warning', remainingSeconds <= 60);
            alert.classList.toggle('alert-info', remainingSeconds > 60);
        };

        const expireForm = () => {
            const anySubmitting = forms.some((form) => form.dataset.submitting === '1');
            if (expired || anySubmitting) return;

            expired = true;
            forms.forEach((form) => {
                form.dataset.formExpired = '1';
            });

            if (timerId) {
                window.clearInterval(timerId);
            }

            renderFormAlert(
                alertHost,
                'warning',
                'js-form-deadline-alert',
                `Tempo máximo de ${limitLabel} atingido.`,
                'A página será recarregada para reiniciar a ficha.'
            );
            reloadCleanForm();
        };

        forms.forEach((form) => {
            form.addEventListener('submit', (event) => {
                if (expired) {
                    event.preventDefault();
                    resetGroupSubmitState();
                    return;
                }

                if (event.defaultPrevented) {
                    resetSubmitState(form);
                    return;
                }

                form.dataset.submitting = '1';
                if (timerId) {
                    window.clearInterval(timerId);
                }
            });
        });

        renderStoredMessage();
        renderTimer();

        timerId = window.setInterval(() => {
            const anySubmitting = forms.some((form) => form.dataset.submitting === '1');
            if (anySubmitting) {
                window.clearInterval(timerId);
                return;
            }

            if (Date.now() >= deadline) {
                expireForm();
                return;
            }

            renderTimer();
        }, 1000);
    });
}

function initUpdateModal() {
    const modalElement = document.getElementById('updateNoteModal');
    if (!modalElement || typeof window.bootstrap === 'undefined') return;

    const version = modalElement.dataset.updateVersion || '0';
    const storageKey = `update-note-dismissed-${version}`;
    const modal = new window.bootstrap.Modal(modalElement);
    const dismissButton = modalElement.querySelector('[data-update-dismiss]');

    const markDismissed = () => {
        try {
            localStorage.setItem(storageKey, '1');
        } catch (e) {
            // ignore storage issues
        }
        modal.hide();
    };

    if (dismissButton) {
        dismissButton.addEventListener('click', markDismissed);
    }

    modalElement.addEventListener('hidden.bs.modal', () => {
        try {
            localStorage.setItem(storageKey, '1');
        } catch (e) {
            // ignore storage issues
        }
    });

    let dismissed = false;
    try {
        dismissed = localStorage.getItem(storageKey) === '1';
    } catch (e) {
        dismissed = false;
    }

    if (!dismissed) {
        modal.show();
    }
}

function initConditionalFields() {
    document.querySelectorAll('.toggle-field').forEach((field) => {
        const updateState = () => {
            const target = document.querySelector(field.dataset.target);
            if (!target) return;
            target.classList.toggle('is-hidden', String(field.value) !== '1');
        };

        field.addEventListener('change', updateState);
        updateState();
    });

    const jobTitle = document.getElementById('job_title');
    const studentField = document.querySelector('[name="is_student"]');
    const maritalStatusField = document.getElementById('marital_status');
    const dependentsField = document.getElementById('has_dependents');
    const academicSection = document.getElementById('academic-section');
    const crefSection = document.getElementById('cref-section');
    const crefDocumentSection = document.getElementById('cref-document-section');
    const meiCertificateSection = document.getElementById('mei-certificate-section');
    const marriageCertificateSection = document.getElementById('marriage-certificate-section');
    const divorceAnnotationSection = document.getElementById('divorce-annotation-section');
    const enrollmentDeclarationSection = document.getElementById('enrollment-declaration-section');

    const normalize = (value) => {
        return String(value || '')
            .normalize('NFD')
            .replace(/[̀-ͯ]/g, '')
            .trim()
            .toLowerCase();
    };

    const syncJobSections = () => {
        const selectedJob = normalize(jobTitle ? jobTitle.value : '');
        const isStudent = studentField ? String(studentField.value || '0') === '1' : false;
        const isInternEdu = selectedJob === normalize('Estagiário Ed. Física');
        const isProfessorEdu =
            selectedJob === normalize('Professor de Musculação') ||
            selectedJob === normalize('Professor de natação') ||
            selectedJob.includes('professor');

        if (academicSection) {
            academicSection.classList.toggle('is-hidden', !(isStudent || isInternEdu));
        }

        if (crefSection) {
            crefSection.classList.toggle('is-hidden', !isProfessorEdu);
        }

        if (crefDocumentSection) {
            crefDocumentSection.classList.toggle('is-hidden', !isProfessorEdu);
        }

        if (meiCertificateSection) {
            meiCertificateSection.classList.toggle('is-hidden', !isProfessorEdu);
        }

        const maritalStatus = String(maritalStatusField ? maritalStatusField.value : '').trim().toLowerCase();
        const hasDependents = String(dependentsField ? dependentsField.value : '0') === '1';

        if (marriageCertificateSection) {
            marriageCertificateSection.classList.toggle('is-hidden', maritalStatus !== 'casado');
        }

        if (divorceAnnotationSection) {
            divorceAnnotationSection.classList.toggle('is-hidden', maritalStatus !== 'divorciado');
        }

        if (enrollmentDeclarationSection) {
            enrollmentDeclarationSection.classList.toggle('is-hidden', !(isStudent || isInternEdu));
        }

    };

    if (jobTitle) {
        jobTitle.addEventListener('change', syncJobSections);
        jobTitle.addEventListener('input', syncJobSections);
    }

    if (studentField) {
        studentField.addEventListener('change', syncJobSections);
    }

    if (maritalStatusField) {
        maritalStatusField.addEventListener('change', syncJobSections);
    }

    if (dependentsField) {
        dependentsField.addEventListener('change', syncJobSections);
    }

    const bankPaymentMethodField = document.getElementById('bank_payment_method');
    const bankDetailsSection = document.getElementById('bank-details-section');
    const pixSection = document.getElementById('pix-section');

    const syncPaymentMethod = () => {
        const method = String(bankPaymentMethodField ? bankPaymentMethodField.value : 'bank');

        if (bankDetailsSection) {
            bankDetailsSection.classList.toggle('is-hidden', method !== 'bank');
        }

        if (pixSection) {
            pixSection.classList.toggle('is-hidden', method !== 'pix');
        }
    };

    if (bankPaymentMethodField) {
        bankPaymentMethodField.addEventListener('change', syncPaymentMethod);
        syncPaymentMethod();
    }

    syncJobSections();
}

function initDependents() {
    const wrapper = document.getElementById('dependents-wrapper');
    const addButton = document.getElementById('add-dependent');

    if (!wrapper || !addButton) return;

    const attachmentUploadMode = String(wrapper.dataset.attachmentUploadMode || 'inline');

    const reindex = () => {
        [...wrapper.querySelectorAll('.dependent-card')].forEach((card, index) => {
            const title = card.querySelector('h3');
            if (title) title.textContent = `Dependente ${index + 1}`;

            card.querySelectorAll('input, select').forEach((input) => {
                const name = input.getAttribute('name');
                if (!name) return;

                let updatedName = name.replace(/dependents\[\d+\]/, `dependents[${index}]`);
                updatedName = updatedName.replace(/dependent_birth_certificates\[\d+\]/, `dependent_birth_certificates[${index}]`);
                input.setAttribute('name', updatedName);
            });

            const deferredLabel = card.querySelector('[data-dependent-certificate-label]');
            if (deferredLabel) {
                deferredLabel.textContent = `Certidão de nascimento do dependente ${index + 1}`;
            }
        });
    };

    addButton.addEventListener('click', () => {
        const newIndex = wrapper.querySelectorAll('.dependent-card').length;
        const card = document.createElement('div');
        const dependentCertificateMarkup = attachmentUploadMode === 'deferred'
            ? `
                <div class="col-md-6">
                    <label class="form-label" data-dependent-certificate-label>Certidão de nascimento do dependente ${newIndex + 1}</label>
                    <div class="alert alert-light border mb-0">A certidão deste dependente será enviada na próxima etapa, em upload separado.</div>
                </div>
            `
            : `
                <div class="col-md-6">
                    <label class="form-label">Certidão de nascimento do dependente ${newIndex + 1}</label>
                    <input type="file" class="form-control" name="dependent_birth_certificates[${newIndex}]" accept=".pdf,.jpg,.jpeg,.png" data-auto-convert-doc-to-pdf="true">
                    <div class="form-text">Envie PDF, JPG ou PNG. Imagens serão convertidas automaticamente para PDF.</div>
                </div>
            `;

        card.className = 'dependent-card border rounded-4 p-3';
        card.innerHTML = `
            <div class="d-flex justify-content-between align-items-center mb-3">
                <h3 class="h6 mb-0">Dependente ${newIndex + 1}</h3>
                <button type="button" class="btn btn-sm btn-outline-danger remove-dependent">Remover</button>
            </div>
            <div class="row g-3">
                <div class="col-md-4">
                    <label class="form-label">Nome completo</label>
                    <input type="text" class="form-control" name="dependents[${newIndex}][full_name]">
                </div>
                <div class="col-md-3">
                    <label class="form-label">Data de nascimento</label>
                    <input type="date" class="form-control" name="dependents[${newIndex}][birth_date]">
                </div>
                <div class="col-md-3">
                    <label class="form-label">CPF</label>
                    <input type="text" class="form-control js-cpf" name="dependents[${newIndex}][cpf]">
                </div>
                <div class="col-md-2">
                    <label class="form-label">Deficiência?</label>
                    <select class="form-select" name="dependents[${newIndex}][has_disability]">
                        <option value="0">Não</option>
                        <option value="1">Sim</option>
                    </select>
                </div>
                <div class="col-md-6">
                    <label class="form-label">Local/cidade de nascimento</label>
                    <input type="text" class="form-control" name="dependents[${newIndex}][birth_city]">
                </div>
                ${dependentCertificateMarkup}
            </div>
        `;

        wrapper.appendChild(card);
        initMasks();
        initDocumentPdfAutoConversion();
        reindex();
    });

    wrapper.addEventListener('click', (event) => {
        const button = event.target.closest('.remove-dependent');
        if (!button) return;

        const card = button.closest('.dependent-card');
        if (card && wrapper.querySelectorAll('.dependent-card').length > 1) {
            card.remove();
            reindex();
        }
    });
}

function initMasks() {
    applyMask('.js-cpf', cpfMask, 'numeric');
    applyMask('.js-phone', phoneMask, 'tel');
    applyMask('.js-cep', cepMask, 'numeric');
    applyMask('.js-cnpj', cnpjMask, 'numeric');
    applyMoneyBehavior('.js-money');
}

function applyMask(selector, formatter, inputMode = null) {
    document.querySelectorAll(selector).forEach((input) => {
        input.removeEventListener('input', input._maskHandler || (() => { }));

        if (inputMode) {
            input.setAttribute('inputmode', inputMode);
        }

        const handler = () => {
            input.value = formatter(input.value);
        };

        input._maskHandler = handler;
        input.addEventListener('input', handler);
    });
}

function applyMoneyBehavior(selector) {
    document.querySelectorAll(selector).forEach((input) => {
        input.removeEventListener('input', input._moneyInputHandler || (() => { }));
        input.removeEventListener('blur', input._moneyBlurHandler || (() => { }));

        input.setAttribute('inputmode', 'decimal');
        if (!input.getAttribute('placeholder')) {
            input.setAttribute('placeholder', 'Ex.: 1500,00');
        }

        const inputHandler = () => {
            input.value = moneyTypingMask(input.value);
        };

        const blurHandler = () => {
            input.value = formatMoneyValue(input.value);
        };

        input._moneyInputHandler = inputHandler;
        input._moneyBlurHandler = blurHandler;

        input.addEventListener('input', inputHandler);
        input.addEventListener('blur', blurHandler);
    });
}

function initDocumentPdfAutoConversion() {
    document.querySelectorAll('input[type="file"][data-auto-convert-doc-to-pdf="true"]').forEach((input) => {
        input.removeEventListener('change', input._autoConvertHandler || (() => {}));

        const handler = async () => {
            const file = input.files && input.files[0] ? input.files[0] : null;
            if (!file) {
                delete input.dataset.convertedToPdf;
                delete input.dataset.convertedFromName;
                delete input.dataset.convertedOriginalSize;
                delete input.dataset.convertedPdfSize;
                return;
            }

            const fileName = String(file.name || '');
            const lowerName = fileName.toLowerCase();
            const mimeType = String(file.type || '').toLowerCase();
            const isPdf = mimeType === 'application/pdf' || lowerName.endsWith('.pdf');
            const isImage = mimeType.startsWith('image/') || lowerName.endsWith('.jpg') || lowerName.endsWith('.jpeg') || lowerName.endsWith('.png');

            if (isPdf) {
                if (input.dataset.convertedToPdf !== '1') {
                    clearConvertedFileMetadata(input);
                }
                return;
            }

            if (!isImage) {
                return;
            }

            const form = input.form;
            if (form) {
                form.dataset.pendingConversions = String(parseInt(form.dataset.pendingConversions || '0', 10) + 1);
            }

            try {
                setConversionBusyState(input, true);
                const pdfFile = await convertImageFileToPdf(file);
                const transfer = new DataTransfer();
                transfer.items.add(pdfFile);
                input.files = transfer.files;
                input.dataset.convertedToPdf = '1';
                input.dataset.convertedFromName = file.name;
                input.dataset.convertedOriginalSize = String(file.size || 0);
                input.dataset.convertedPdfSize = String(pdfFile.size || 0);
                input.dispatchEvent(new Event('change', { bubbles: true }));
            } catch (error) {
                input.value = '';
                clearConvertedFileMetadata(input);
                input.dispatchEvent(new Event('change', { bubbles: true }));

                if (form) {
                    renderFormAlert(
                        form,
                        'danger',
                        'js-upload-error',
                        'Não foi possível converter a imagem para PDF automaticamente.',
                        'Tente enviar um PDF pronto ou uma imagem JPG/PNG menor. Imagens muito grandes também são reduzidas automaticamente antes da conversão.'
                    );
                    resetSubmitState(form);
                }
            } finally {
                if (form) {
                    const currentPending = parseInt(form.dataset.pendingConversions || '0', 10);
                    form.dataset.pendingConversions = String(Math.max(0, currentPending - 1));
                }
                setConversionBusyState(input, false);
            }
        };

        input._autoConvertHandler = handler;
        input.addEventListener('change', handler);
    });
}

function clearConvertedFileMetadata(input) {
    delete input.dataset.convertedToPdf;
    delete input.dataset.convertedFromName;
    delete input.dataset.convertedOriginalSize;
    delete input.dataset.convertedPdfSize;
}

function renderEmptyFilePreview(input, wrapper, image, meta) {
    const media = wrapper.querySelector('.upload-preview__media');
    const accept = (input.getAttribute('accept') || '').toLowerCase();
    const autoConvertDocToPdf = input.dataset.autoConvertDocToPdf === 'true';
    const acceptsImage = accept.includes('.jpg') || accept.includes('.jpeg') || accept.includes('.png');

    wrapper.classList.add('is-empty');
    if (media) media.classList.add('d-none');
    image.removeAttribute('src');
    meta.innerHTML = `
        <div class="upload-preview__filename">Nenhum arquivo selecionado</div>
        <div class="upload-preview__hint">${autoConvertDocToPdf ? 'Selecione um PDF, JPG ou PNG. Imagens serão reduzidas e convertidas automaticamente para PDF.' : (acceptsImage ? 'A miniatura aparece aqui quando você escolher uma imagem.' : 'Selecione um arquivo PDF.')}</div>
    `;
}

function setConversionBusyState(input, isBusy) {
    const wrapper = document.querySelector(input.dataset.previewTarget || '');
    const meta = document.querySelector(input.dataset.previewMeta || '');

    input.disabled = isBusy;

    if (!wrapper || !meta) return;

    wrapper.classList.toggle('is-empty', false);

    if (isBusy) {
        const media = wrapper.querySelector('.upload-preview__media');
        const image = document.querySelector(input.dataset.previewImage || '');
        if (media) media.classList.add('d-none');
        if (image) image.removeAttribute('src');

        meta.innerHTML = `
            <div class="upload-preview__filename">Convertendo imagem para PDF...</div>
            <div class="upload-preview__hint">Aguarde alguns segundos. O envio será liberado quando a conversão terminar.</div>
        `;
    }
}

async function convertImageFileToPdf(file) {
    const imageBitmap = await loadImageBitmap(file);
    const { width, height } = fitImageDimensions(imageBitmap.width, imageBitmap.height, PDF_CONVERSION_MAX_DIMENSION);
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d', { alpha: false });
    if (!context) {
        throw new Error('Canvas 2D indisponível');
    }

    canvas.width = width;
    canvas.height = height;
    context.fillStyle = '#ffffff';
    context.fillRect(0, 0, canvas.width, canvas.height);
    context.drawImage(imageBitmap, 0, 0, canvas.width, canvas.height);

    const jpegBlob = await canvasToBlob(canvas, 'image/jpeg', PDF_CONVERSION_JPEG_QUALITY);
    const jpegBytes = new Uint8Array(await jpegBlob.arrayBuffer());
    const pdfBytes = buildPdfFromJpegBytes(jpegBytes, canvas.width, canvas.height);
    const outputName = replaceFileExtension(file.name, '.pdf');

    return new File([pdfBytes], outputName, {
        type: 'application/pdf',
        lastModified: file.lastModified || Date.now(),
    });
}


function fitImageDimensions(width, height, maxDimension) {
    const safeWidth = Math.max(1, Number(width) || 1);
    const safeHeight = Math.max(1, Number(height) || 1);
    const safeMaxDimension = Math.max(1, Number(maxDimension) || 1);
    const largestSide = Math.max(safeWidth, safeHeight);

    if (largestSide <= safeMaxDimension) {
        return { width: Math.round(safeWidth), height: Math.round(safeHeight) };
    }

    const scale = safeMaxDimension / largestSide;

    return {
        width: Math.max(1, Math.round(safeWidth * scale)),
        height: Math.max(1, Math.round(safeHeight * scale)),
    };
}

function loadImageBitmap(file) {
    return new Promise((resolve, reject) => {
        const objectUrl = URL.createObjectURL(file);
        const image = new Image();

        image.onload = () => {
            URL.revokeObjectURL(objectUrl);
            resolve(image);
        };

        image.onerror = () => {
            URL.revokeObjectURL(objectUrl);
            reject(new Error('Falha ao carregar imagem'));
        };

        image.src = objectUrl;
    });
}

function canvasToBlob(canvas, mimeType, quality) {
    return new Promise((resolve, reject) => {
        canvas.toBlob((blob) => {
            if (!blob) {
                reject(new Error('Falha ao gerar blob do canvas'));
                return;
            }

            resolve(blob);
        }, mimeType, quality);
    });
}

function replaceFileExtension(name, extension) {
    const baseName = String(name || 'arquivo').replace(/\.[^.]+$/, '');
    return `${baseName}${extension}`;
}

function buildPdfFromJpegBytes(jpegBytes, imageWidth, imageHeight) {
    const pageWidth = 595.28;
    const pageHeight = 841.89;
    const margin = 28;
    const drawableWidth = pageWidth - (margin * 2);
    const drawableHeight = pageHeight - (margin * 2);
    const imageRatio = imageWidth / imageHeight;
    const pageRatio = drawableWidth / drawableHeight;

    let drawWidth = drawableWidth;
    let drawHeight = drawableHeight;

    if (imageRatio > pageRatio) {
        drawHeight = drawableWidth / imageRatio;
    } else {
        drawWidth = drawableHeight * imageRatio;
    }

    const offsetX = (pageWidth - drawWidth) / 2;
    const offsetY = (pageHeight - drawHeight) / 2;
    const contentStream = `q
${formatPdfNumber(drawWidth)} 0 0 ${formatPdfNumber(drawHeight)} ${formatPdfNumber(offsetX)} ${formatPdfNumber(offsetY)} cm
/Im0 Do
Q`;

    const objects = [
        '<< /Type /Catalog /Pages 2 0 R >>',
        '<< /Type /Pages /Kids [3 0 R] /Count 1 >>',
        `<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${formatPdfNumber(pageWidth)} ${formatPdfNumber(pageHeight)}] /Resources << /XObject << /Im0 4 0 R >> >> /Contents 5 0 R >>`,
        { header: `<< /Type /XObject /Subtype /Image /Width ${Math.max(1, Math.round(imageWidth))} /Height ${Math.max(1, Math.round(imageHeight))} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${jpegBytes.length} >>`, stream: jpegBytes },
        { header: `<< /Length ${new TextEncoder().encode(contentStream).length} >>`, stream: new TextEncoder().encode(contentStream) },
    ];

    return assemblePdf(objects);
}

function formatPdfNumber(value) {
    return Number(value).toFixed(2).replace(/\.00$/, '');
}

function assemblePdf(objects) {
    const encoder = new TextEncoder();
    const chunks = [encoder.encode('%PDF-1.4\n%\xC7\xEC\x8F\xA2\n')];
    const offsets = [0];
    let currentOffset = chunks[0].length;

    objects.forEach((object, index) => {
        offsets.push(currentOffset);

        if (typeof object === 'string') {
            const bytes = encoder.encode(`${index + 1} 0 obj\n${object}\nendobj\n`);
            chunks.push(bytes);
            currentOffset += bytes.length;
            return;
        }

        const headerBytes = encoder.encode(`${index + 1} 0 obj\n${object.header}\nstream\n`);
        const footerBytes = encoder.encode('\nendstream\nendobj\n');
        chunks.push(headerBytes, object.stream, footerBytes);
        currentOffset += headerBytes.length + object.stream.length + footerBytes.length;
    });

    const xrefOffset = currentOffset;
    const xrefLines = ['xref', `0 ${objects.length + 1}`, '0000000000 65535 f '];
    for (let i = 1; i < offsets.length; i += 1) {
        xrefLines.push(`${String(offsets[i]).padStart(10, '0')} 00000 n `);
    }

    const trailer = `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF`;
    chunks.push(encoder.encode(`${xrefLines.join('\n')}\n${trailer}`));

    const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
    const result = new Uint8Array(totalLength);
    let offset = 0;

    chunks.forEach((chunk) => {
        result.set(chunk, offset);
        offset += chunk.length;
    });

    return result;
}

function formatFileSize(bytes) {
    const size = Number(bytes || 0);

    if (!Number.isFinite(size) || size <= 0) {
        return '0 B';
    }

    const units = ['B', 'KB', 'MB', 'GB', 'TB'];
    let value = size;
    let unitIndex = 0;

    while (value >= 1024 && unitIndex < units.length - 1) {
        value /= 1024;
        unitIndex += 1;
    }

    const decimals = value >= 100 || unitIndex === 0 ? 0 : (value >= 10 ? 1 : 2);
    return `${value.toLocaleString('pt-BR', {
        minimumFractionDigits: decimals,
        maximumFractionDigits: decimals,
    })} ${units[unitIndex]}`;
}

function initFilePreviews() {
    document.querySelectorAll('.js-file-preview').forEach((input) => {
        const wrapper = document.querySelector(input.dataset.previewTarget);
        const image = document.querySelector(input.dataset.previewImage);
        const meta = document.querySelector(input.dataset.previewMeta);

        if (!wrapper || !image || !meta) return;

        input.removeEventListener('change', input._filePreviewHandler || (() => {}));

        const handler = () => {
            const file = input.files && input.files[0] ? input.files[0] : null;
            const media = wrapper.querySelector('.upload-preview__media');
            const accept = (input.getAttribute('accept') || '').toLowerCase();
            const autoConvertDocToPdf = input.dataset.autoConvertDocToPdf === 'true';
            const acceptsImage = accept.includes('.jpg') || accept.includes('.jpeg') || accept.includes('.png');
            const acceptsPdf = accept.includes('.pdf');
            const fileName = file ? String(file.name || '').toLowerCase() : '';
            const isPdf = file && (file.type === 'application/pdf' || fileName.endsWith('.pdf'));
            const isImage = file && (
                String(file.type || '').startsWith('image/') ||
                fileName.endsWith('.jpg') ||
                fileName.endsWith('.jpeg') ||
                fileName.endsWith('.png')
            );

            if (!file) {
                renderEmptyFilePreview(input, wrapper, image, meta);
                return;
            }

            if ((acceptsPdf && !acceptsImage && !isPdf) || (acceptsImage && !acceptsPdf && !isImage) || (acceptsPdf && acceptsImage && !isPdf && !isImage)) {
                input.value = '';
                clearConvertedFileMetadata(input);
                wrapper.classList.add('is-empty');
                if (media) media.classList.add('d-none');
                image.removeAttribute('src');
                meta.innerHTML = `
                    <div class="upload-preview__filename">Arquivo não aceito</div>
                    <div class="upload-preview__hint">${autoConvertDocToPdf ? 'Envie um PDF, JPG ou PNG.' : (acceptsImage ? 'Envie uma imagem JPG ou PNG.' : 'Envie um arquivo PDF.')}</div>
                `;
                return;
            }

            if (autoConvertDocToPdf && isImage && input.disabled) {
                wrapper.classList.remove('is-empty');
                return;
            }

            wrapper.classList.remove('is-empty');

            if (isImage) {
                const objectUrl = URL.createObjectURL(file);

                image.src = objectUrl;
                if (media) media.classList.remove('d-none');

                meta.innerHTML = `
                    <div class="upload-preview__filename">${escapeHtml(file.name)}</div>
                    <div class="upload-preview__size">Tamanho: ${escapeHtml(formatFileSize(file.size))}</div>
                    <div class="upload-preview__hint">Pré-visualização da imagem selecionada</div>
                `;

                image.onload = () => URL.revokeObjectURL(objectUrl);
                return;
            }

            if (media) media.classList.add('d-none');
            image.removeAttribute('src');

            const convertedHint = input.dataset.convertedToPdf === '1'
                ? `<div class="upload-preview__hint">Imagem convertida automaticamente para PDF. Original: ${escapeHtml(input.dataset.convertedFromName || file.name)}</div><div class="upload-preview__size">Original: ${escapeHtml(formatFileSize(input.dataset.convertedOriginalSize || 0))}</div>`
                : '<div class="upload-preview__hint">Arquivo selecionado. Prévia visual disponível apenas para imagens.</div>';

            meta.innerHTML = `
                <div class="upload-preview__filename">${escapeHtml(file.name)}</div>
                <div class="upload-preview__size">Tamanho: ${escapeHtml(formatFileSize(file.size))}</div>
                ${convertedHint}
            `;
        };

        input._filePreviewHandler = handler;
        input.addEventListener('change', handler);
    });
}

function onlyDigits(v) {
    return v.replace(/\D/g, '');
}

function cpfMask(v) {
    v = onlyDigits(v).slice(0, 11);
    v = v.replace(/(\d{3})(\d)/, '$1.$2');
    v = v.replace(/(\d{3})(\d)/, '$1.$2');
    return v.replace(/(\d{3})(\d{1,2})$/, '$1-$2');
}

function phoneMask(v) {
    v = onlyDigits(v).slice(0, 11);

    if (v.length <= 10) {
        v = v.replace(/(\d{2})(\d)/, '($1) $2');
        return v.replace(/(\d{4})(\d)/, '$1-$2');
    }

    v = v.replace(/(\d{2})(\d)/, '($1) $2');
    return v.replace(/(\d{5})(\d)/, '$1-$2');
}

function cepMask(v) {
    v = onlyDigits(v).slice(0, 8);
    return v.replace(/(\d{5})(\d)/, '$1-$2');
}

function cnpjMask(v) {
    v = onlyDigits(v).slice(0, 14);
    v = v.replace(/^(\d{2})(\d)/, '$1.$2');
    v = v.replace(/^(\d{2})\.(\d{3})(\d)/, '$1.$2.$3');
    v = v.replace(/\.(\d{3})(\d)/, '.$1/$2');
    return v.replace(/(\d{4})(\d)/, '$1-$2');
}

function moneyTypingMask(v) {
    v = String(v || '').replace(/[^\d,\.]/g, '');

    const lastComma = v.lastIndexOf(',');
    const lastDot = v.lastIndexOf('.');
    const separatorIndex = Math.max(lastComma, lastDot);

    if (separatorIndex === -1) {
        return v.replace(/[^\d]/g, '');
    }

    const integerPart = v.slice(0, separatorIndex).replace(/[^\d]/g, '');
    const decimalPart = v.slice(separatorIndex + 1).replace(/[^\d]/g, '').slice(0, 2);

    return `${integerPart},${decimalPart}`;
}

function formatMoneyValue(v) {
    const typedValue = moneyTypingMask(v);
    if (!typedValue) return '';

    const normalized = typedValue.replace(',', '.');
    const number = parseFloat(normalized);
    if (Number.isNaN(number)) return '';

    return number.toFixed(2).replace('.', ',');
}

function initDataTable() {
    const table = document.getElementById('admissions-table');
    if (!table || typeof window.DataTable === 'undefined') return;

    const tbody = table.querySelector('tbody');
    const headerCells = table.querySelectorAll('thead th').length;
    const rows = tbody ? Array.from(tbody.querySelectorAll('tr')) : [];

    if (!rows.length) return;

    const invalidRow = rows.some((row) => row.children.length !== headerCells);
    if (invalidRow) return;

    new DataTable(table, {
        language: {
            search: 'Filtrar resultados:',
            lengthMenu: 'Mostrar _MENU_ registros',
            info: 'Mostrando _START_ a _END_ de _TOTAL_ registros',
            paginate: {
                first: 'Primeira',
                last: 'Última',
                next: 'Próxima',
                previous: 'Anterior'
            },
            zeroRecords: 'Nenhum resultado encontrado',
            infoEmpty: 'Nenhum registro disponível',
            infoFiltered: '(filtrado de _MAX_ registros)'
        },
        order: [[0, 'desc']],
        pageLength: 10,
        deferRender: true,
        stateSave: true
    });
}

function escapeHtml(value) {
    const map = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#039;'
    };

    return String(value).replace(/[&<>"']/g, (m) => map[m]);
}

function scrollToFirstError() {
    const firstInvalid = document.querySelector('.is-invalid, .field-error');
    if (!firstInvalid) return;

    const target = firstInvalid.classList.contains('field-error')
        ? firstInvalid.previousElementSibling || firstInvalid
        : firstInvalid;

    window.requestAnimationFrame(() => {
        target.scrollIntoView({ behavior: 'smooth', block: 'center' });
        if (typeof target.focus === 'function') {
            target.focus({ preventScroll: true });
        }
    });
}
