/**
 * Collection Upload Page JavaScript
 * Handles file uploads for a specific collection
 */

// Use existing URLS configuration from config/urls.js
// Collection upload endpoint is now available as URLS.LIBRARY_API.COLLECTION_UPLOAD
// safeFetch (with URLValidator) is now provided by utils/safe-fetch.js loaded in base.html
// escapeHtml is the canonical window.escapeHtml from security/xss-protection.js
// (loaded first via base.html). Do NOT reintroduce a local fallback — it would
// be weaker (e.g. not escape "/") and risk a duplicate-const SyntaxError (#3706).

// Store selected files globally to avoid losing them
let selectedFiles = [];

// Store supported formats (fetched from backend)
let supportedFormats = {
    extensions: [],
    accept_string: ''
};

/**
 * Fetch supported file formats from backend
 * Updates the file input accept attribute and help text
 */
async function fetchSupportedFormats() {
    try {
        const response = await safeFetch(URLS.LIBRARY_API.SUPPORTED_FORMATS);
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        const data = await response.json();

        supportedFormats = data;

        // Update file input accept attribute
        const fileInput = document.getElementById('files-input');
        if (fileInput && data.accept_string) {
            fileInput.accept = data.accept_string;
        }

        // Update help text with format details
        const formatDetails = document.getElementById('format-details');
        if (formatDetails && data.count) {
            formatDetails.textContent = `(${data.count} formats supported)`;
        }

        // Update the full format list in the instructions
        const fullFormatList = document.getElementById('full-format-list');
        if (fullFormatList && data.extensions) {
            // Format as comma-separated uppercase list
            const formatList = data.extensions
                .map(ext => ext.replace('.', '').toUpperCase())
                .join(', ');
            fullFormatList.textContent = `All supported: ${formatList}`;
        }

        SafeLogger.log(`📄 Loaded ${data.count} supported formats`);
    } catch (error) {
        SafeLogger.warn('Could not fetch supported formats, file picker will accept all files:', error);
        // Update UI to indicate formats could not be loaded
        const formatDetails = document.getElementById('format-details');
        if (formatDetails) {
            formatDetails.textContent = '';
        }
        const fullFormatList = document.getElementById('full-format-list');
        if (fullFormatList) {
            fullFormatList.textContent = 'Could not load format list';
        }
    }
}

/**
 * Initialize the upload page
 */
document.addEventListener('DOMContentLoaded', function() {
    // Fetch supported formats from backend
    fetchSupportedFormats();

    // Setup form submission
    document.getElementById('upload-files-form').addEventListener('submit', function(e) {
        SafeLogger.log('📋 Form submit event triggered');
        SafeLogger.log('📋 File input state before submit:');
        const fileInput = document.getElementById('files-input');
        SafeLogger.log('📋 - files:', fileInput.files);
        SafeLogger.log('📋 - value:', fileInput.value);
        SafeLogger.log('📋 - length:', fileInput.files.length);
        handleUploadFiles(e);
    });

    // Setup file input change handler
    document.getElementById('files-input').addEventListener('change', handleFileSelect);

    // Setup drag and drop
    const dropZone = document.getElementById('drop-zone');
    const fileInput = document.getElementById('files-input');

    if (dropZone && fileInput) {
        dropZone.addEventListener('click', (e) => {
            if (e.target.tagName !== 'BUTTON') {
                fileInput.click();
            }
        });

        dropZone.addEventListener('dragover', handleDragOver);
        dropZone.addEventListener('dragleave', handleDragLeave);
        dropZone.addEventListener('drop', handleDrop);
    }

    // IMMEDIATE document-level drag detection
    // Fires instantly when ANY file drag enters the window
    let dragCounter = 0;

    document.addEventListener('dragenter', (e) => {
        e.preventDefault();
        dragCounter++;
        if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
            if (dropZone) {
                dropZone.classList.add('ldr-drag-over');
                // Scroll drop zone into view if not visible
                if (!isElementInViewport(dropZone)) {
                    dropZone.scrollIntoView({ behavior: 'smooth', block: 'center' });
                }
            }
        }
    }, { capture: true });

    document.addEventListener('dragleave', (e) => {
        e.preventDefault();
        dragCounter--;
        // Only remove class when truly leaving the document
        if (dragCounter <= 0) {
            dragCounter = 0;
            if (dropZone) {
                dropZone.classList.remove('ldr-drag-over');
            }
        }
    }, { capture: true });

    document.addEventListener('drop', () => {
        dragCounter = 0;
        if (dropZone) {
            dropZone.classList.remove('ldr-drag-over');
        }
    }, { capture: true });

    // Prevent default for dragover on document to allow drop
    document.addEventListener('dragover', (e) => {
        e.preventDefault();
    }, { capture: true });
});

/**
 * Check if element is in viewport
 */
function isElementInViewport(el) {
    const rect = el.getBoundingClientRect();
    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
}

// Throttle drag events for better performance
let dragOverTimeout = null;

/**
 * Handle drag over event (throttled)
 */
function handleDragOver(e) {
    e.preventDefault();
    e.stopPropagation();

    // Only update class if not already set (avoid repeated DOM updates)
    if (!e.currentTarget.classList.contains('ldr-drag-over')) {
        e.currentTarget.classList.add('ldr-drag-over');
    }

    // Clear any pending drag leave
    if (dragOverTimeout) {
        clearTimeout(dragOverTimeout);
        dragOverTimeout = null;
    }
}

/**
 * Handle drag leave event (debounced)
 */
function handleDragLeave(e) {
    e.preventDefault();
    e.stopPropagation();

    const target = e.currentTarget;

    // Debounce the class removal to avoid flickering
    dragOverTimeout = setTimeout(() => {
        target.classList.remove('ldr-drag-over');
    }, 50);
}

/**
 * Handle drop event
 */
function handleDrop(e) {
    e.preventDefault();
    e.stopPropagation();

    const dropZone = e.currentTarget;
    dropZone.classList.remove('ldr-drag-over');

    const files = e.dataTransfer.files;
    handleFiles(files);
}

/**
 * Handle file selection
 */
function handleFileSelect(e) {
    SafeLogger.log('📂 File selection triggered');
    SafeLogger.log('📂 Event target:', e.target);
    SafeLogger.log('📂 Files from event:', e.target.files);
    const files = e.target.files;
    SafeLogger.log('📂 Calling handleFiles with:', files.length, 'files');
    handleFiles(files);
}

/**
 * Handle selected files
 */
function handleFiles(files) {
    SafeLogger.log('📦 handleFiles called with:', files.length, 'files');

    // Store files globally
    selectedFiles = Array.from(files);
    SafeLogger.log('📦 Stored files globally:', selectedFiles.length, 'files');

    if (files.length === 0) {
        hideSelectedFiles();
        return;
    }

    showSelectedFiles(files);
}

/**
 * Show selected files preview
 */
function showSelectedFiles(files) {
    SafeLogger.log(`👀 Showing preview for ${files.length} files`);
    const selectedFilesDiv = document.getElementById('selected-files');
    const fileList = document.getElementById('file-list');

    // Clear existing file list
    fileList.innerHTML = '';

    // Add each file to list (batch DOM updates)
    const fragment = document.createDocumentFragment();
    Array.from(files).forEach((file) => {
        const li = document.createElement('li');
        const fileSize = (file.size / 1024 / 1024).toFixed(2) + ' MB';
        // bearer:disable javascript_lang_dangerous_insert_html
        // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: variable built from escaped/numeric values above
        li.innerHTML = `
            <i class="fas fa-file"></i>
            <span>${escapeHtml(file.name)} (${fileSize})</span>
        `;
        fragment.appendChild(li);
    });
    fileList.appendChild(fragment);

    selectedFilesDiv.style.display = 'block';
}

/**
 * Hide selected files preview
 */
function hideSelectedFiles() {
    const selectedFilesDiv = document.getElementById('selected-files');
    selectedFilesDiv.style.display = 'none';
}

// Configuration for batched uploads
const BATCH_SIZE = 15; // Files per batch - balance between efficiency and memory

/**
 * Handle file upload - uses batched uploads for large file sets
 */
async function handleUploadFiles(e) {
    e.preventDefault();

    if (selectedFiles.length === 0) {
        showError('Please select at least one file to upload.');
        return;
    }

    const pdfStorageMode = document.querySelector('input[name="pdf_storage"]:checked')?.value || 'none';
    const submitBtn = e.target.querySelector('button[type="submit"]');
    const csrfToken = window.api ? window.api.getCsrfToken() : '';
    const uploadUrl = URLBuilder.build(URLS.LIBRARY_API.COLLECTION_UPLOAD, COLLECTION_ID);

    // Decide: batch upload for large sets, single upload for small sets
    const useBatchedUpload = selectedFiles.length > BATCH_SIZE;

    try {
        submitBtn.disabled = true;
        submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Uploading...';

        if (useBatchedUpload) {
            // Batched upload for large file sets
            await handleBatchedUpload(selectedFiles, pdfStorageMode, csrfToken, uploadUrl);
        } else {
            // Single upload for small file sets
            await handleSingleUpload(selectedFiles, pdfStorageMode, csrfToken, uploadUrl);
        }
    } catch (error) {
        SafeLogger.error('Error uploading files:', error);
        showError('Upload failed: ' + error.message);
    } finally {
        submitBtn.disabled = false;
        submitBtn.innerHTML = '<i class="fas fa-upload"></i> 🚀 Upload Files';
    }
}

/**
 * Handle batched upload - uploads files in smaller chunks
 */
async function handleBatchedUpload(files, pdfStorageMode, csrfToken, uploadUrl) {
    const totalFiles = files.length;
    const totalSize = files.reduce((sum, f) => sum + f.size, 0);
    const batches = [];

    // Split files into batches
    for (let i = 0; i < files.length; i += BATCH_SIZE) {
        batches.push(files.slice(i, i + BATCH_SIZE));
    }

    SafeLogger.log(`📦 Uploading ${totalFiles} files in ${batches.length} batches`);

    // Show batched progress UI
    showBatchedProgress(files, batches.length);

    // Track results across all batches
    const allUploaded = [];
    const allErrors = [];
    let uploadedBytes = 0;

    for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
        const batch = batches[batchIndex];
        const batchSize = batch.reduce((sum, f) => sum + f.size, 0);
        // Capture into a const so the progress closure references a stable
        // per-iteration baseline (avoids closure-over-loop-var hazard).
        const startBytes = uploadedBytes;

        updateBatchProgress(batchIndex + 1, batches.length, uploadedBytes, totalSize);

        try {
            const result = await uploadBatch(batch, pdfStorageMode, csrfToken, uploadUrl, (loaded) => {
                // Progress within this batch
                const currentProgress = startBytes + loaded;
                const percent = Math.round((currentProgress / totalSize) * 100);
                updateBatchProgressBytes(percent, currentProgress, totalSize);
            });

            if (result.uploaded) allUploaded.push(...result.uploaded);
            if (result.errors) allErrors.push(...result.errors);

            uploadedBytes += batchSize;

        } catch (error) {
            SafeLogger.error(`Batch ${batchIndex + 1} failed:`, error);
            // Mark all files in failed batch as errors
            batch.forEach(file => {
                allErrors.push({ filename: file.name, error: error.message });
            });
        }
    }

    // Show combined results
    const combinedResult = {
        success: true,
        uploaded: allUploaded,
        errors: allErrors,
        summary: { successful: allUploaded.length, failed: allErrors.length }
    };

    updateProgressComplete(combinedResult);
    setTimeout(() => {
        showUploadResults(combinedResult);
        document.getElementById('upload-files-form').reset();
        selectedFiles = [];
        hideSelectedFiles();
    }, 500);
}

/**
 * Upload a single batch of files
 */
function uploadBatch(files, pdfStorageMode, csrfToken, uploadUrl, onProgress) {
    return new Promise((resolve, reject) => {
        const formData = new FormData();
        for (const file of files) {
            formData.append('files', file);
        }
        formData.append('pdf_storage', pdfStorageMode);

        const xhr = new XMLHttpRequest();

        xhr.upload.onprogress = function(e) {
            if (e.lengthComputable && onProgress) {
                onProgress(e.loaded);
            }
        };

        xhr.onload = function() {
            if (xhr.status >= 200 && xhr.status < 300) {
                try {
                    resolve(JSON.parse(xhr.responseText));
                } catch {
                    reject(new Error('Invalid JSON response'));
                }
            } else {
                reject(new Error(`Server returned ${xhr.status}`));
            }
        };

        xhr.onerror = function() {
            reject(new Error('Network error'));
        };

        xhr.ontimeout = function() {
            reject(new Error('Request timeout'));
        };

        xhr.open('POST', uploadUrl);
        xhr.setRequestHeader('X-CSRFToken', csrfToken);
        xhr.timeout = 300000; // 5 min per batch
        xhr.send(formData);
    });
}

/**
 * Handle single upload (original behavior for small file sets)
 */
async function handleSingleUpload(files, pdfStorageMode, csrfToken, uploadUrl) {
    const formData = new FormData();
    for (const file of files) {
        formData.append('files', file);
    }
    formData.append('pdf_storage', pdfStorageMode);

    showCleanProgress(files);

    const data = await new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();

        xhr.upload.onprogress = function(e) {
            if (e.lengthComputable) {
                const percentComplete = Math.round((e.loaded / e.total) * 100);
                const loadedMB = (e.loaded / (1024 * 1024)).toFixed(1);
                const totalMB = (e.total / (1024 * 1024)).toFixed(1);
                updateUploadProgress(percentComplete, loadedMB, totalMB);
            }
        };

        xhr.onload = function() {
            if (xhr.status >= 200 && xhr.status < 300) {
                try {
                    resolve(JSON.parse(xhr.responseText));
                } catch {
                    reject(new Error('Invalid JSON response'));
                }
            } else {
                reject(new Error(`Server returned ${xhr.status}: ${xhr.responseText.substring(0, 100)}`));
            }
        };

        xhr.onerror = () => reject(new Error('NetworkError: Upload failed'));
        xhr.ontimeout = () => reject(new Error('Request timeout'));

        xhr.open('POST', uploadUrl);
        xhr.setRequestHeader('X-CSRFToken', csrfToken);
        xhr.timeout = 600000;
        xhr.send(formData);
    });

    if (data.success) {
        updateProgressComplete(data);
        setTimeout(() => {
            showUploadResults(data);
            document.getElementById('upload-files-form').reset();
            selectedFiles = [];
            hideSelectedFiles();
        }, 500);
    } else {
        showError(data.error || 'Upload failed');
    }
}

/**
 * Show batched upload progress UI
 */
function showBatchedProgress(files, totalBatches) {
    const progressDiv = document.getElementById('upload-progress');
    const totalFiles = files.length;
    const totalSize = files.reduce((sum, f) => sum + f.size, 0);
    const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(1);

    // bearer:disable javascript_lang_dangerous_insert_html
    // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: variable built from escaped/numeric values above
    progressDiv.innerHTML = `
        <div style="padding: 1.5rem; background: var(--bg-tertiary); border-radius: 8px;">
            <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem;">
                <h3 style="margin: 0; font-size: 1.1rem; color: var(--text-primary);">
                    <i class="fas fa-cloud-upload-alt" style="color: var(--accent-primary);"></i>
                    Uploading ${totalFiles} Files
                </h3>
                <span id="progress-summary" style="font-weight: 600; color: var(--accent-primary);">
                    0% (0 / ${totalSizeMB} MB)
                </span>
            </div>

            <div style="background: var(--bg-secondary); border-radius: 8px; height: 12px; overflow: hidden; margin-bottom: 1rem;">
                <div id="progress-bar-fill" style="background: linear-gradient(90deg, var(--accent-primary) 0%, var(--accent-secondary) 100%); height: 100%; width: 0%; transition: width 0.2s ease;"></div>
            </div>

            <div id="progress-details" style="font-size: 0.9rem; color: var(--text-muted); margin-bottom: 0.5rem; text-align: center;">
                Preparing batched upload (${totalBatches} batches)...
            </div>

            <div id="batch-info" style="font-size: 0.85rem; color: var(--accent-primary); text-align: center;">
                Batch 1 of ${totalBatches}
            </div>
        </div>
    `;
    progressDiv.style.display = 'block';
}

/**
 * Update batched upload progress
 */
function updateBatchProgress(currentBatch, totalBatches) {
    const batchInfo = document.getElementById('batch-info');
    const progressDetails = document.getElementById('progress-details');

    if (batchInfo) {
        batchInfo.textContent = `Batch ${currentBatch} of ${totalBatches}`;
    }
    if (progressDetails) {
        progressDetails.textContent = `Uploading batch ${currentBatch}...`;
    }
}

/**
 * Update progress bar with byte-level accuracy
 */
function updateBatchProgressBytes(percent, uploadedBytes, totalBytes) {
    const progressBarFill = document.getElementById('progress-bar-fill');
    const progressSummary = document.getElementById('progress-summary');

    const uploadedMB = (uploadedBytes / (1024 * 1024)).toFixed(1);
    const totalMB = (totalBytes / (1024 * 1024)).toFixed(1);

    if (progressBarFill) {
        progressBarFill.style.width = percent + '%';
    }
    if (progressSummary) {
        progressSummary.textContent = `${percent}% (${uploadedMB} / ${totalMB} MB)`;
    }
}

/**
 * Show clean progress display with file list
 */
function showCleanProgress(files) {
    const progressDiv = document.getElementById('upload-progress');
    const totalFiles = files.length;
    const totalSize = files.reduce((sum, f) => sum + f.size, 0);
    const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(1);

    let html = `
        <div style="padding: 1.5rem; background: var(--bg-tertiary); border-radius: 8px;">
            <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem;">
                <h3 style="margin: 0; font-size: 1.1rem; color: var(--text-primary);">
                    <i class="fas fa-cloud-upload-alt" style="color: var(--accent-primary);"></i>
                    Uploading ${totalFiles} Files
                </h3>
                <span id="progress-summary" style="font-weight: 600; color: var(--accent-primary);">
                    0% (0 / ${totalSizeMB} MB)
                </span>
            </div>

            <div style="background: var(--bg-secondary); border-radius: 8px; height: 12px; overflow: hidden; margin-bottom: 1rem;">
                <div id="progress-bar-fill" style="background: linear-gradient(90deg, var(--accent-primary) 0%, var(--accent-secondary) 100%); height: 100%; width: 0%; transition: width 0.2s ease;"></div>
            </div>

            <div id="progress-details" style="font-size: 0.9rem; color: var(--text-muted); margin-bottom: 1rem; text-align: center;">
                Starting upload...
            </div>

            <details style="margin-top: 1rem;">
                <summary style="cursor: pointer; color: var(--accent-primary); font-weight: 500;">Show file list (${totalFiles} files)</summary>
                <div id="file-progress-list" style="max-height: 300px; overflow-y: auto; margin-top: 0.5rem;">
    `;

    files.forEach((file, index) => {
        const fileSize = (file.size / 1024 / 1024).toFixed(2);
        html += `
            <div id="file-${index}" style="display: flex; align-items: center; padding: 0.5rem; background: var(--bg-secondary); border-radius: 4px; margin-bottom: 0.25rem; border: 1px solid var(--border-color); font-size: 0.85rem;">
                <i class="fas fa-file-pdf" style="color: var(--error-color); margin-right: 0.5rem; width: 14px;"></i>
                <span style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${escapeHtml(file.name)}</span>
                <span style="color: var(--text-muted); margin-left: 0.5rem;">${fileSize} MB</span>
            </div>
        `;
    });

    html += `
                </div>
            </details>
        </div>
    `;

    // bearer:disable javascript_lang_dangerous_insert_html
    // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: variable built from escaped/numeric values above
    progressDiv.innerHTML = html;
    progressDiv.style.display = 'block';
}

/**
 * Update upload progress display
 */
function updateUploadProgress(percent, loadedMB, totalMB) {
    const progressBarFill = document.getElementById('progress-bar-fill');
    const progressSummary = document.getElementById('progress-summary');
    const progressDetails = document.getElementById('progress-details');

    if (progressBarFill) {
        progressBarFill.style.width = percent + '%';
    }

    if (progressSummary) {
        progressSummary.textContent = `${percent}% (${loadedMB} / ${totalMB} MB)`;
    }

    if (progressDetails) {
        if (percent < 100) {
            progressDetails.textContent = `Uploading... ${loadedMB} MB of ${totalMB} MB transferred`;
        } else {
            progressDetails.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing files on server...';
        }
    }
}

/**
 * Update progress to show completion
 */
function updateProgressComplete(data) {
    const progressBarFill = document.getElementById('progress-bar-fill');
    const progressSummary = document.getElementById('progress-summary');
    const progressDetails = document.getElementById('progress-details');

    if (progressBarFill) {
        progressBarFill.style.width = '100%';
        progressBarFill.style.background = 'var(--success-color)';
    }

    // Server returns data.uploaded and data.errors (not uploaded_files/failed_files)
    const successCount = data.uploaded ? data.uploaded.length : (data.summary?.successful || 0);
    const failedCount = data.errors ? data.errors.length : (data.summary?.failed || 0);

    if (progressSummary) {
        progressSummary.textContent = `✓ ${successCount} uploaded${failedCount > 0 ? `, ${failedCount} failed` : ''}`;
        progressSummary.style.color = failedCount > 0 ? 'var(--warning-color)' : 'var(--success-color)';
    }

    if (progressDetails) {
        progressDetails.innerHTML = `<i class="fas fa-check-circle" style="color: var(--success-color);"></i> Upload complete!`;
    }
}

/**
 * Show upload results with clear breakdown
 */
function showUploadResults(data) {
    const progressDiv = document.getElementById('upload-progress');
    const resultsDiv = document.getElementById('upload-results');

    // Hide progress display
    progressDiv.style.display = 'none';

    // Server returns data.uploaded and data.errors
    const uploadedFiles = data.uploaded || [];
    const failedFiles = data.errors || [];

    // Categorize by status
    const newUploads = uploadedFiles.filter(f => f.status === 'uploaded');
    const addedToCollection = uploadedFiles.filter(f => f.status === 'added_to_collection' || f.status === 'added_to_collection_pdf_upgraded');
    const alreadyInCollection = uploadedFiles.filter(f => f.status === 'already_in_collection' || f.status === 'pdf_upgraded');

    const actualNewCount = newUploads.length + addedToCollection.length;
    const skippedCount = alreadyInCollection.length;

    let html = '<div style="padding: 1.5rem; background: var(--bg-tertiary); border-radius: 8px;">';

    // Summary header
    html += '<h3 style="margin: 0 0 1rem 0; color: var(--text-primary);"><i class="fas fa-check-circle" style="color: var(--success-color);"></i> Upload Complete!</h3>';

    // Stats grid
    html += '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1rem; margin-bottom: 1.5rem;">';

    if (actualNewCount > 0) {
        html += `<div class="ldr-alert ldr-alert-success" style="padding: 1rem; border-radius: 8px; text-align: center;">
            <div style="font-size: 1.5rem; font-weight: 700;">${actualNewCount}</div>
            <div style="font-size: 0.85rem;">New Files Added</div>
        </div>`;
    }

    if (skippedCount > 0) {
        html += `<div class="ldr-alert ldr-alert-warning" style="padding: 1rem; border-radius: 8px; text-align: center;">
            <div style="font-size: 1.5rem; font-weight: 700;">${skippedCount}</div>
            <div style="font-size: 0.85rem;">Already Existed</div>
        </div>`;
    }

    if (failedFiles.length > 0) {
        html += `<div class="ldr-alert ldr-alert-danger" style="padding: 1rem; border-radius: 8px; text-align: center;">
            <div style="font-size: 1.5rem; font-weight: 700;">${failedFiles.length}</div>
            <div style="font-size: 0.85rem;">Failed</div>
        </div>`;
    }

    html += '</div>';

    // Details sections
    if (newUploads.length > 0) {
        html += `<details style="margin-bottom: 0.5rem;">
            <summary style="cursor: pointer; color: var(--success-color); font-weight: 500;">
                ✅ New uploads (${newUploads.length})
            </summary>
            <ul style="margin: 0.5rem 0 0 1rem; padding: 0; list-style: none;">
                ${newUploads.map(f => `<li style="padding: 0.25rem 0;">📄 ${escapeHtml(f.filename)}</li>`).join('')}
            </ul>
        </details>`;
    }

    if (addedToCollection.length > 0) {
        html += `<details style="margin-bottom: 0.5rem;">
            <summary style="cursor: pointer; color: var(--accent-tertiary); font-weight: 500;">
                📁 Added to collection (already in library) (${addedToCollection.length})
            </summary>
            <ul style="margin: 0.5rem 0 0 1rem; padding: 0; list-style: none;">
                ${addedToCollection.map(f => `<li style="padding: 0.25rem 0;">📄 ${escapeHtml(f.filename)}</li>`).join('')}
            </ul>
        </details>`;
    }

    if (alreadyInCollection.length > 0) {
        html += `<details style="margin-bottom: 0.5rem;">
            <summary style="cursor: pointer; color: var(--warning-color); font-weight: 500;">
                ⏭️ Skipped - already in collection (${alreadyInCollection.length})
            </summary>
            <ul style="margin: 0.5rem 0 0 1rem; padding: 0; list-style: none;">
                ${alreadyInCollection.map(f => `<li style="padding: 0.25rem 0;">📄 ${escapeHtml(f.filename)}</li>`).join('')}
            </ul>
        </details>`;
    }

    if (failedFiles.length > 0) {
        html += `<details open style="margin-bottom: 0.5rem;">
            <summary style="cursor: pointer; color: var(--error-color); font-weight: 500;">
                ❌ Failed (${failedFiles.length})
            </summary>
            <ul style="margin: 0.5rem 0 0 1rem; padding: 0; list-style: none;">
                ${failedFiles.map(f => `<li style="padding: 0.25rem 0; color: var(--error-color);">📄 ${escapeHtml(f.filename)}: ${escapeHtml(f.error)}</li>`).join('')}
            </ul>
        </details>`;
    }

    // Action buttons
    html += '<div style="margin-top: 1.5rem; display: flex; gap: 1rem; flex-wrap: wrap;">';
    html += `<a href="/library/collections/${COLLECTION_ID}" class="ldr-btn-collections ldr-btn-collections-primary">
        <i class="fas fa-folder-open"></i> View Collection
    </a>`;
    html += `<button onclick="location.reload()" class="ldr-btn-collections ldr-btn-collections-secondary">
        <i class="fas fa-plus"></i> Upload More Files
    </button>`;
    html += '</div>';

    html += '</div>';

    // bearer:disable javascript_lang_dangerous_insert_html
    // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: variable built from escaped/numeric values above
    resultsDiv.innerHTML = html;
    resultsDiv.style.display = 'block';
}

/**
 * Show error message
 *
 * SECURITY: message receives user-controlled data (error.message at line 337,
 * data.error at line 512) — escapeHtml() is required to prevent XSS.
 */
function showError(message) {
    const progressDiv = document.getElementById('upload-progress');
    const resultsDiv = document.getElementById('upload-results');

    // Hide progress display
    if (progressDiv) {
        progressDiv.style.display = 'none';
    }

    // Escape message before including in HTML template
    const escapedMessage = escapeHtml(message);
    const html = `
        <div class="ldr-alert ldr-alert-danger" style="padding: 1rem; border-radius: 8px; margin-bottom: 1rem;">
            <h4 style="margin: 0 0 0.5rem 0;"><i class="fas fa-exclamation-triangle"></i> Upload Error</h4>
            <p>${escapedMessage}</p>
        </div>
    `;

    // bearer:disable javascript_lang_dangerous_insert_html
    resultsDiv.innerHTML = html;
    resultsDiv.style.display = 'block';
}
