// Table management functionality

(function() {
    'use strict';


    // FileBlobCache, processedLinks, preview state, and utilities
    const fileBlobCache = new Map();
    const processedLinks = new WeakSet();
    let fileLinkHandlersAttached = false;
    let filePreviewWindow = null;
    // All extensions previewable in the popup. Image / tiff / zip / text routed
    // through #previewContent below; pdf gets a direct iframe; docx/xlsx use
    // dedicated lazy-loaded renderers.
    const PREVIEW_EXTENSIONS = [
        'pdf',
        'docx', 'xlsx', 'xls',
        ...zddc.preview.IMAGE_EXTENSIONS,
        ...zddc.preview.TIFF_EXTENSIONS,
        ...zddc.preview.TEXT_EXTENSIONS,
        'zip'
    ];
    const loadedLibraries = new Map();
    let resizing = null;

    // Currently-previewing file (visual highlight in the file table). Survives
    // re-renders via applyPreviewHighlight, which is called at the tail of
    // updateFileTable. Cleared when the preview popup is closed.
    let currentPreviewFileId = null;
    let previewWindowWatcher = null;

    function setCurrentPreviewFile(fileId) {
        currentPreviewFileId = fileId;
        applyPreviewHighlight();
    }

    function applyPreviewHighlight() {
        const tbody = document.getElementById('filesTableBody');
        if (!tbody) return;
        // Clear any prior highlight first.
        tbody.querySelectorAll('tr.is-previewing').forEach(el => el.classList.remove('is-previewing'));
        tbody.querySelectorAll('.revision-file.is-previewing').forEach(el => el.classList.remove('is-previewing'));
        if (!currentPreviewFileId) return;
        const checkbox = tbody.querySelector(`input[type="checkbox"][data-file-id="${cssEscape(currentPreviewFileId)}"]`);
        if (!checkbox) return;
        const wrapper = checkbox.closest('.revision-file');
        if (wrapper) wrapper.classList.add('is-previewing');
        const row = checkbox.closest('tr');
        if (row) row.classList.add('is-previewing');
    }

    function cssEscape(s) {
        if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(s);
        return String(s).replace(/[^a-zA-Z0-9_-]/g, c => '\\' + c);
    }

    // Watch the preview popup; clear the highlight when the user closes it so
    // the table doesn't keep advertising a preview that's no longer on screen.
    function watchPreviewWindow() {
        if (previewWindowWatcher) {
            clearInterval(previewWindowWatcher);
            previewWindowWatcher = null;
        }
        if (!filePreviewWindow) return;
        previewWindowWatcher = setInterval(() => {
            if (!filePreviewWindow || filePreviewWindow.closed) {
                clearInterval(previewWindowWatcher);
                previewWindowWatcher = null;
                if (currentPreviewFileId) setCurrentPreviewFile(null);
            }
        }, 500);
    }


    /**
     * Get or create a blob URL for a file.
     * - Local files: reads via File System Access API, caches the blob URL.
     * - HTTP files: fetches the remote URL, caches the blob URL.
     * Returns a Promise<string> resolving to a blob: URL.
     */
    async function getFileBlobUrl(file) {
        if (fileBlobCache.has(file.id)) {
            return fileBlobCache.get(file.id);
        }
        let blob;
        if (file.handle) {
            // Local file via File System Access API
            const f = await file.handle.getFile();
            blob = f;
        } else if (file.url) {
            // HTTP file — fetch and convert to blob
            const resp = await fetch(file.url);
            if (!resp.ok) throw new Error('HTTP ' + resp.status + ' fetching ' + file.url);
            blob = await resp.blob();
        } else {
            throw new Error('File has neither a handle nor a URL');
        }
        const url = URL.createObjectURL(blob);
        fileBlobCache.set(file.id, url);
        return url;
    }

    /**
     * Clean up blob URLs for files no longer displayed
     */
    function cleanupUnusedBlobUrls() {
        const displayedFileIds = new Set(window.app.filteredFiles.map(f => f.id));
        for (const [fileId, url] of fileBlobCache.entries()) {
            if (!displayedFileIds.has(fileId)) {
                URL.revokeObjectURL(url);
                fileBlobCache.delete(fileId);
            }
        }
    }

    /**
     * Revoke all blob URLs and clear cache
     */
    function cleanupAllBlobUrls() {
        for (const url of fileBlobCache.values()) {
            URL.revokeObjectURL(url);
        }
        fileBlobCache.clear();
    }

    // Update file table
    function updateFileTable() {
        const tbody = document.getElementById('filesTableBody');
        
        if (window.app.filteredFiles.length === 0) {
            tbody.innerHTML = `
                <tr>
                    <td colspan="3" class="empty-table">
                        No files found matching the current filters.
                    </td>
                </tr>
            `;
            cleanupUnusedBlobUrls(); // Clean up all blob URLs
            return;
        }
        
        // Group and sort files
        const grouped = window.app.modules.parser.groupFilesByTrackingNumber(window.app.filteredFiles);
        const sorted = window.app.modules.parser.sortGroupedFiles(grouped);
        
        // Build table rows
        const rows = [];
        sorted.forEach(group => {
            rows.push(createFileGroupRow(group));
        });
        
        tbody.innerHTML = rows.join('');

        // Re-apply the preview highlight after every re-render so a file that
        // was being previewed when filters changed still shows as previewing if
        // it's still in the visible set.
        applyPreviewHighlight();

        // Clean up blob URLs for files no longer visible
        cleanupUnusedBlobUrls();
    }

    // Create row for a file group
    function createFileGroupRow(group) {
        // Generate one <tr> per revision; last row gets class group-last for border
        const lastIndex = group.sortedRevisions.length - 1;
        return group.sortedRevisions.map((revision, i) => {
            const titleClass = revision.hasModifier ? 'revision-title-modifier' : 'revision-title-base';
            const titleHtml = `<div class="${titleClass}">${window.app.modules.app.escapeHtml(revision.title)}</div>`;
            const revisionHtml = createRevisionHtml(group.trackingNumber, revision);
            const lastClass = i === lastIndex ? ' group-last' : '';

            // First row includes trackingNumber cell with rowspan
            if (i === 0) {
                return `
                    <tr class="group-row${lastClass}">
                        <td data-field="trackingNumber" rowspan="${group.sortedRevisions.length}">${window.app.modules.app.escapeHtml(group.trackingNumber)}</td>
                        <td data-field="title">${titleHtml}</td>
                        <td data-field="revisions">${revisionHtml}</td>
                    </tr>
                `;
            }
            // Subsequent rows omit trackingNumber cell
            return `
                <tr class="group-row${lastClass}">
                    <td data-field="title">${titleHtml}</td>
                    <td data-field="revisions">${revisionHtml}</td>
                </tr>
            `;
        }).join('');
    }

    // Create HTML for a revision
    function createRevisionHtml(trackingNumber, revision) {
        const filesHtml = revision.files.map(file => 
            createFileHtml(file)
        ).join(' ');
        
        return `
            <div class="revision-group">
                <div class="revision-item">
                    <span class="revision-info">
                        <span class="revision-id">${window.app.modules.app.escapeHtml(revision.revision)}</span>
                        <span class="revision-status">(${window.app.modules.app.escapeHtml(revision.status)})</span>
                    </span>
                    ${filesHtml}
                </div>
            </div>
        `;
    }

    // Create HTML for a file
    function createFileHtml(file) {
        const checked = window.app.selectedFiles.has(file.id) ? 'checked' : '';
        const fullPath = file.path || file.folderPath + '/' + file.name;
        
        // Handle files with path errors (Windows 260-char limit)
        if (file.hasPathError) {
            const errorTitle = `⚠️ Cannot access: Microsoft Windows path length limit (260 chars)\n\nPath: ${fullPath}\n\nUse 'subst' to map archive to a drive letter, or shorten folder names.`;
            return `
                <span class="revision-file">
                    <input type="checkbox" 
                           data-file-id="${file.id}"
                           ${checked}
                           onchange="toggleFileSelection('${file.id}')">
                    <span class="path-error-indicator" title="${window.app.modules.app.escapeHtml(errorTitle)}">⚠️</span>
                    <span class="file-link-disabled"
                          title="${window.app.modules.app.escapeHtml(errorTitle)}">
                        <span class="file-ext">${window.app.modules.app.escapeHtml(file.extension.toUpperCase())}</span>
                        ${file.size != null ? `<span class="file-size">${window.app.modules.export.formatFileSize(file.size)}</span>` : ''}
                    </span>
                </span>
            `;
        }
        
        return `
            <span class="revision-file">
                <input type="checkbox" 
                       data-file-id="${file.id}"
                       ${checked}
                       onchange="toggleFileSelection('${file.id}')">
                <a href="#" 
                   class="file-link"
                   data-file-id="${file.id}"
                   data-file-name="${window.app.modules.app.escapeHtml(file.name)}"
                   title="${window.app.modules.app.escapeHtml(fullPath)}">
                    <span class="file-ext">${window.app.modules.app.escapeHtml(file.extension.toUpperCase())}</span>
                    ${file.size != null ? `<span class="file-size">${window.app.modules.export.formatFileSize(file.size)}</span>` : ''}
                </a>
            </span>
        `;
    }

    // Toggle file selection
    function toggleFileSelection(fileId) {
        if (window.app.selectedFiles.has(fileId)) {
            window.app.selectedFiles.delete(fileId);
        } else {
            window.app.selectedFiles.add(fileId);
        }
        window.app.modules.app.updateStatusBar();
        updateSelectAllVisibleCheckbox();
    }

    // Toggle selection of all visible files based on checkbox state
    function toggleSelectAllVisible(selectAll) {
        window.app.filteredFiles.forEach(file => {
            if (selectAll) {
                window.app.selectedFiles.add(file.id);
            } else {
                window.app.selectedFiles.delete(file.id);
            }
        });
        
        updateFileTable();
        window.app.modules.app.updateStatusBar();
        updateSelectAllVisibleCheckbox();
    }

    // Update the select all visible checkbox to reflect current state
    function updateSelectAllVisibleCheckbox() {
        const checkbox = document.getElementById('selectAllVisibleCheckbox');
        if (!checkbox) return;
        
        const visibleCount = window.app.filteredFiles.length;
        if (visibleCount === 0) {
            checkbox.checked = false;
            checkbox.indeterminate = false;
            return;
        }
        
        const selectedVisibleCount = window.app.filteredFiles.filter(f => 
            window.app.selectedFiles.has(f.id)
        ).length;
        
        if (selectedVisibleCount === 0) {
            checkbox.checked = false;
            checkbox.indeterminate = false;
        } else if (selectedVisibleCount === visibleCount) {
            checkbox.checked = true;
            checkbox.indeterminate = false;
        } else {
            checkbox.checked = false;
            checkbox.indeterminate = true;
        }
    }

    /**
     * Memory-efficient blob URL management
     * 
     * fileBlobCache: Maps file IDs to blob URLs for reuse
     * processedLinks: WeakSet tracks DOM elements that already have blob URLs
     *   - Automatically garbage collected when DOM elements are removed
     *   - Prevents redundant async operations on mouseover
     */

    /**
     * Lazily load a script from CDN. Returns a promise that resolves when loaded.
     * Caches the promise so subsequent calls return immediately.
     */
    function loadLibrary(url) {
        if (loadedLibraries.has(url)) return loadedLibraries.get(url);
        const promise = new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = url;
            script.onload = resolve;
            script.onerror = () => reject(new Error(`Failed to load library: ${url}`));
            document.head.appendChild(script);
        });
        loadedLibraries.set(url, promise);
        return promise;
    }

    /**
     * Check if file preview mode is enabled
     */
    function isFilePreviewEnabled() {
        const toggle = document.getElementById('filePreviewToggle');
        return toggle && toggle.checked;
    }

    /**
     * Show file preview in a separate popup window
     * Supports PDF (iframe), DOCX (docx-preview), XLSX/XLS (SheetJS)
     */
    async function showFilePreview(file) {
        const ext = file.extension.toLowerCase();

        try {
            // For HTML preview, prefer the file's real server URL over a
            // blob URL when available (zddc-server-backed archives have
            // file.url set; local FileSystemAccessAPI mode doesn't).
            //
            // Why it matters: HTML files in an archive often link to
            // sibling/parent paths via relative URLs — e.g.
            // ../.archive/<tracking>.html — which zddc-server intercepts
            // and resolves. From a blob: URL the relative resolution
            // produces blob:.../.archive/X.html, which never reaches the
            // server. Loading the iframe from the actual https://zddc.../
            // URL means relative links resolve back to the server and the
            // .archive interception fires as designed.
            //
            // Other types (pdf, images rendered via canvas / iframe etc.)
            // are content-only — they don't depend on relative URLs — so
            // a blob URL is fine.
            const isHtml = ext === 'html' || ext === 'htm';
            const url = (isHtml && file.url)
                ? file.url
                : await getFileBlobUrl(file);
            
            // Mirror the parent window's theme in the popup
            const parentTheme = document.documentElement.getAttribute('data-theme') || '';
            const themeAttr = parentTheme ? ` data-theme="${parentTheme}"` : '';

            // Base HTML shell for the preview window
            const previewHtml = `
<!DOCTYPE html>
<html${themeAttr}>
<head>
    <title>${window.app.modules.app.escapeHtml(file.name)} - Preview</title>
    <style>
        :root {
            --bg: #ffffff;
            --bg-secondary: #f5f5f5;
            --bg-hover: #e8e8e8;
            --text: #212529;
            --text-muted: #666666;
            --border: #dddddd;
            --primary: #2a5a8a;
        }
        @media (prefers-color-scheme: dark) {
            :root:not([data-theme="light"]) {
                --bg: #1e1e1e;
                --bg-secondary: #252526;
                --bg-hover: #2d2d30;
                --text: #d4d4d4;
                --text-muted: #9d9d9d;
                --border: #3e3e42;
                --primary: #4a90c4;
            }
        }
        [data-theme="dark"] {
            --bg: #1e1e1e;
            --bg-secondary: #252526;
            --bg-hover: #2d2d30;
            --text: #d4d4d4;
            --text-muted: #9d9d9d;
            --border: #3e3e42;
            --primary: #4a90c4;
        }
        [data-theme="light"] {
            --bg: #ffffff;
            --bg-secondary: #f5f5f5;
            --bg-hover: #e8e8e8;
            --text: #212529;
            --text-muted: #666666;
            --border: #dddddd;
            --primary: #2a5a8a;
        }
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            display: flex;
            flex-direction: column;
            height: 100vh;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: var(--bg);
            color: var(--text);
        }
        .toolbar {
            display: flex;
            align-items: center;
            gap: 0.5rem;
            padding: 0.5rem 1rem;
            background: var(--bg-secondary);
            border-bottom: 1px solid var(--border);
            flex-shrink: 0;
        }
        .toolbar h1 {
            flex: 1;
            font-size: 0.95rem;
            font-weight: 500;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            color: var(--text);
        }
        .btn {
            padding: 0.4rem 0.8rem;
            font-size: 0.85rem;
            border: 1px solid var(--border);
            border-radius: 4px;
            background: var(--bg);
            color: var(--text);
            cursor: pointer;
        }
        .btn:hover { background: var(--bg-hover); }
        iframe {
            flex: 1;
            width: 100%;
            border: none;
        }
        #previewContent {
            flex: 1;
            overflow: auto;
            background: var(--bg);
        }
        .loading {
            display: flex;
            align-items: center;
            justify-content: center;
            height: 100%;
            color: var(--text-muted);
            font-size: 1.1rem;
        }
        /* docx-preview container */
        .docx-wrapper { padding: 1rem; }
        /* Image preview */
        img.preview-image {
            max-width: 100%;
            max-height: 100%;
            display: block;
            margin: auto;
            object-fit: contain;
        }
        /* Text preview */
        pre.preview-text {
            padding: 1rem;
            font-family: 'Consolas', 'Monaco', monospace;
            font-size: 0.85rem;
            white-space: pre-wrap;
            word-wrap: break-word;
            color: var(--text);
            background: var(--bg);
        }
        /* xlsx table styling */
        .xlsx-table { border-collapse: collapse; width: 100%; font-size: 0.85rem; }
        .xlsx-table th, .xlsx-table td {
            border: 1px solid var(--border);
            padding: 0.35rem 0.5rem;
            text-align: left;
            white-space: nowrap;
            color: var(--text);
        }
        .xlsx-table th { background: var(--bg-secondary); font-weight: 600; position: sticky; top: 0; }
        .xlsx-table tr:nth-child(even) { background: var(--bg-secondary); }
        .xlsx-table tr:hover { background: var(--bg-hover); }
        .sheet-tabs { display: flex; gap: 0; border-bottom: 1px solid var(--border); background: var(--bg-secondary); }
        .sheet-tab {
            padding: 0.4rem 1rem;
            cursor: pointer;
            border: 1px solid transparent;
            border-bottom: none;
            font-size: 0.85rem;
            background: transparent;
            color: var(--text);
        }
        .sheet-tab:hover { background: var(--bg-hover); }
        .sheet-tab.active {
            background: var(--bg);
            border-color: var(--border);
            border-bottom-color: var(--bg);
            margin-bottom: -1px;
            font-weight: 500;
        }
    </style>
</head>
<body>
    <div class="toolbar">
        <h1>${window.app.modules.app.escapeHtml(file.name)}</h1>
        <button class="btn" onclick="downloadFile()">Download</button>
    </div>
    ${(ext === 'pdf' || ext === 'html' || ext === 'htm')
        ? '<iframe src="' + url + '"' + (ext === 'pdf' ? '' : ' sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"') + '></iframe>'
        : '<div id="previewContent"><div class="loading">Loading preview...</div></div>'}
    <script>
        var blobUrl = "${url}";
        var fileName = "${window.app.modules.app.escapeHtml(file.name).replace(/"/g, '\\"')}";
        
        function downloadFile() {
            const a = document.createElement('a');
            a.href = blobUrl;
            a.download = fileName;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
        }
    <\/script>
</body>
</html>`;
            
            // Open or reuse the preview window
            if (filePreviewWindow && !filePreviewWindow.closed) {
                filePreviewWindow.document.open();
                filePreviewWindow.document.write(previewHtml);
                filePreviewWindow.document.close();
                filePreviewWindow.focus();
            } else {
                const width = Math.round(screen.width * 0.6);
                const height = Math.round(screen.height * 0.8);
                const left = Math.round((screen.width - width) / 2);
                const top = Math.round((screen.height - height) / 2);
                
                filePreviewWindow = window.open('', 'filePreview', 
                    `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`);
                
                if (!filePreviewWindow) {
                    window.open(url, '_blank');
                    return;
                }
                
                filePreviewWindow.document.write(previewHtml);
                filePreviewWindow.document.close();
                filePreviewWindow.focus();
            }
            
            // For non-PDF / non-HTML types, render content into the
            // preview window. PDF and HTML are already wired up via the
            // <iframe> in the popup's body (see buildPreviewHtml above);
            // for HTML this means the page is RENDERED, not shown as
            // literal source text. The previewBlobUrl carries the right
            // MIME type ('text/html') so the iframe loads natively.
            if (ext === 'pdf' || ext === 'html' || ext === 'htm') {
                // iframe already wired in popup HTML; nothing to do
            } else if (ext === 'docx') {
                await renderDocxInWindow(file);
            } else if (ext === 'xlsx' || ext === 'xls') {
                await renderXlsxInWindow(file);
            } else if (zddc.preview.isTiff(ext)) {
                await renderTiffInWindow(file);
            } else if (zddc.preview.isZip(ext)) {
                await renderZipInWindow(file);
            } else if (zddc.preview.isImage(ext)) {
                renderImageInWindow(file, url);
            } else if (zddc.preview.isText(ext)) {
                await renderTextInWindow(file);
            }

        } catch (err) {
            console.error('Error loading file preview:', err);
            alert(`Error loading preview: ${err.message}`);
        }
    }

    /**
     * Render a DOCX file in the preview window using docx-preview library
     */
    async function renderDocxInWindow(file) {
        const container = filePreviewWindow.document.getElementById('previewContent');
        if (!container) return;
        
        try {
            // jszip + docx-preview are vendored (concatenated by build.sh
            // ahead of every tool module), so window.JSZip and window.docx
            // are already defined here.
            const arrayBuffer = await (file.handle
                ? file.handle.getFile().then(f => f.arrayBuffer())
                : fetch(file.url).then(r => r.arrayBuffer()));

            container.innerHTML = '';
            await window.docx.renderAsync(arrayBuffer, container);
        } catch (err) {
            console.error('Error rendering DOCX:', err);
            container.innerHTML = `<div class="loading">Error rendering DOCX: ${err.message}<br>Click Download to view in Word.</div>`;
        }
    }

    /**
     * Render an XLSX/XLS file in the preview window using SheetJS
     */
    async function renderXlsxInWindow(file) {
        const container = filePreviewWindow.document.getElementById('previewContent');
        if (!container) return;
        
        try {
            // XLSX is bundled into the dist HTML (shared/vendor/xlsx.full.min.js),
            // so window.XLSX is available synchronously — no runtime load.
            const arrayBuffer = await (file.handle
                ? file.handle.getFile().then(f => f.arrayBuffer())
                : fetch(file.url).then(r => r.arrayBuffer()));
            const workbook = XLSX.read(arrayBuffer, { type: 'array' });
            
            container.innerHTML = '';
            
            // Build sheet tabs if multiple sheets
            if (workbook.SheetNames.length > 1) {
                const tabs = filePreviewWindow.document.createElement('div');
                tabs.className = 'sheet-tabs';
                workbook.SheetNames.forEach((name, i) => {
                    const tab = filePreviewWindow.document.createElement('button');
                    tab.className = 'sheet-tab' + (i === 0 ? ' active' : '');
                    tab.textContent = name;
                    tab.onclick = () => {
                        tabs.querySelectorAll('.sheet-tab').forEach(t => t.classList.remove('active'));
                        tab.classList.add('active');
                        renderSheet(workbook, name, tableContainer);
                    };
                    tabs.appendChild(tab);
                });
                container.appendChild(tabs);
            }
            
            const tableContainer = filePreviewWindow.document.createElement('div');
            tableContainer.style.flex = '1';
            tableContainer.style.overflow = 'auto';
            container.appendChild(tableContainer);
            
            renderSheet(workbook, workbook.SheetNames[0], tableContainer);
        } catch (err) {
            console.error('Error rendering XLSX:', err);
            container.innerHTML = `<div class="loading">Error rendering spreadsheet: ${err.message}<br>Click Download to view in Excel.</div>`;
        }
    }

    /**
     * Render a single sheet as an HTML table
     */
    function renderSheet(workbook, sheetName, container) {
        const sheet = workbook.Sheets[sheetName];
        const html = XLSX.utils.sheet_to_html(sheet, { editable: false });
        container.innerHTML = html;
        // Apply styling to the generated table
        const table = container.querySelector('table');
        if (table) table.className = 'xlsx-table';
    }

    async function _getFileArrayBuffer(file) {
        if (file.handle) {
            const f = await file.handle.getFile();
            return f.arrayBuffer();
        }
        if (file.url) {
            const r = await fetch(file.url);
            if (!r.ok) throw new Error('HTTP ' + r.status);
            return r.arrayBuffer();
        }
        throw new Error('No file source available');
    }

    /**
     * Render an image (non-tiff) directly using the popup's <img> element.
     * The browser handles decoding for jpg/jpeg/png/gif/webp/bmp/svg/ico natively.
     */
    function renderImageInWindow(file, url) {
        const container = filePreviewWindow.document.getElementById('previewContent');
        if (!container) return;
        container.innerHTML = '';
        const img = filePreviewWindow.document.createElement('img');
        img.className = 'preview-image';
        img.src = url;
        img.alt = file.name || '';
        container.appendChild(img);
    }

    /**
     * Render a TIFF using the shared zddc.preview.renderTiff helper (UTIF.js).
     */
    async function renderTiffInWindow(file) {
        const container = filePreviewWindow.document.getElementById('previewContent');
        if (!container) return;
        try {
            const arrayBuffer = await _getFileArrayBuffer(file);
            await zddc.preview.renderTiff(filePreviewWindow.document, container, arrayBuffer, {
                fileName: file.name
            });
        } catch (err) {
            console.error('Error rendering TIFF:', err);
            container.innerHTML = `<div class="loading">Error rendering TIFF: ${err.message}<br>Click Download to view in another application.</div>`;
        }
    }

    /**
     * Render a ZIP listing using the shared zddc.preview.renderZipListing helper.
     */
    async function renderZipInWindow(file) {
        const container = filePreviewWindow.document.getElementById('previewContent');
        if (!container) return;
        try {
            const arrayBuffer = await _getFileArrayBuffer(file);
            await zddc.preview.renderZipListing(filePreviewWindow.document, container, arrayBuffer, {
                fileName: file.name
            });
        } catch (err) {
            console.error('Error rendering ZIP listing:', err);
            container.innerHTML = `<div class="loading">Error reading ZIP: ${err.message}</div>`;
        }
    }

    /**
     * Render a text file as preformatted text. Truncates very large files to
     * keep the popup responsive — users can Download to see the full file.
     */
    async function renderTextInWindow(file) {
        const container = filePreviewWindow.document.getElementById('previewContent');
        if (!container) return;
        try {
            const fileObj = file.handle ? await file.handle.getFile() : await fetch(file.url).then(r => r.blob());
            let text = await fileObj.text();
            const MAX = 200000;
            if (text.length > MAX) text = text.substring(0, MAX) + '\n\n... (truncated, ' + (text.length - MAX) + ' more chars — Download for full file)';
            container.innerHTML = '';
            const pre = filePreviewWindow.document.createElement('pre');
            pre.className = 'preview-text';
            pre.textContent = text;
            container.appendChild(pre);
        } catch (err) {
            console.error('Error rendering text file:', err);
            container.innerHTML = `<div class="loading">Error reading file: ${err.message}</div>`;
        }
    }

    /**
     * Setup event delegation for file links
     * Left-click: Download file (or preview if PDF and preview mode enabled)
     * Right-click: Allow "Open in new tab" with blob URL
     */
    function setupFileLinkHandlers() {
        if (fileLinkHandlersAttached) return;
        
        const table = document.getElementById('filesTable');
        if (!table) {
            console.warn('Files table not found');
            return;
        }
        
        // Handle clicks - download file or show preview
        table.addEventListener('click', async (e) => {
            const link = e.target.closest('.file-link');
            if (!link) return;
            
            e.preventDefault();
            e.stopPropagation();
            
            const fileId = link.getAttribute('data-file-id');
            const fileName = link.getAttribute('data-file-name');
            
            if (!fileId || !fileName) {
                console.error('Invalid link data');
                return;
            }
            
            const file = window.app.files.find(f => f.id === fileId);
            
            if (!file) {
                console.error(`File not found: ${fileId}`);
                alert('File not found. Please refresh and try again.');
                return;
            }
            
            // Check if file preview is enabled and file type is previewable
            if (isFilePreviewEnabled() && PREVIEW_EXTENSIONS.includes(file.extension.toLowerCase())) {
                await showFilePreview(file);
                setCurrentPreviewFile(file.id);
                watchPreviewWindow();
                return;
            }
            
            try {
                if (!file.handle && file.url) {
                    // HTTP mode: open the file URL directly in a new tab
                    window.open(file.url, '_blank');
                } else {
                    // Local mode: create blob URL and trigger download
                    const url = await getFileBlobUrl(file);
                    const downloadLink = document.createElement('a');
                    downloadLink.href = url;
                    downloadLink.download = fileName;
                    document.body.appendChild(downloadLink);
                    downloadLink.click();
                    document.body.removeChild(downloadLink);
                }
            } catch (err) {
                console.error('Error opening file:', err);
                alert(`Error opening file: ${err.message}`);
            }
        }, true); // Use capture phase
        
        // Handle mouseover - pre-load URL for fast right-click / middle-click
        table.addEventListener('mouseover', async (e) => {
            const link = e.target.closest('.file-link');
            if (!link) return;
            
            // Skip if already processed (prevents redundant operations)
            if (processedLinks.has(link)) return;
            
            const fileId = link.getAttribute('data-file-id');
            if (!fileId) return;
            
            const file = window.app.files.find(f => f.id === fileId);
            if (!file) {
                console.warn(`File not found for pre-load: ${fileId}`);
                return;
            }
            
            try {
                if (!file.handle && file.url) {
                    // HTTP mode: set href directly — no async needed
                    link.href = file.url;
                    link.target = '_blank';
                    processedLinks.add(link);
                } else {
                    // Local mode: pre-load blob URL asynchronously
                    const url = await getFileBlobUrl(file);
                    link.href = url;
                    link.target = '_blank';
                    processedLinks.add(link);
                }
            } catch (err) {
                console.error('Error pre-loading file link:', err);
                // Don't mark as processed so it can retry
            }
        }, true); // Use capture phase
        
        // Handle context menu - ensure blob URL is set (fallback if mouseover didn't fire)
        table.addEventListener('contextmenu', async (e) => {
            const link = e.target.closest('.file-link');
            if (!link) return;
            
            // If already processed, blob URL is set - allow context menu to work
            if (processedLinks.has(link)) return;
            
            const fileId = link.getAttribute('data-file-id');
            if (!fileId) return;
            
            const file = window.app.files.find(f => f.id === fileId);
            if (!file) {
                console.warn(`File not found for context menu: ${fileId}`);
                return;
            }
            
            try {
                // Get blob URL and set it as href synchronously as possible
                const url = await getFileBlobUrl(file);
                link.href = url;
                link.target = '_blank';
                
                // Mark as processed
                processedLinks.add(link);
            } catch (err) {
                console.error('Error preparing file for context menu:', err);
                // Don't mark as processed so it can retry
            }
        }, true); // Use capture phase
        
        fileLinkHandlersAttached = true;
    }

    // Get MIME type from extension
    function getMimeType(extension) {
        const ext = extension.toLowerCase();
        const mimeTypes = {
            // Documents
            'pdf': 'application/pdf',
            'doc': 'application/msword',
            'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'xls': 'application/vnd.ms-excel',
            'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'ppt': 'application/vnd.ms-powerpoint',
            'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            
            // Text
            'txt': 'text/plain',
            'csv': 'text/csv',
            'html': 'text/html',
            'htm': 'text/html',
            'xml': 'text/xml',
            'json': 'application/json',
            
            // Code
            'js': 'text/javascript',
            'css': 'text/css',
            'py': 'text/plain',
            'java': 'text/plain',
            'cpp': 'text/plain',
            'c': 'text/plain',
            'h': 'text/plain',
            
            // Images
            'jpg': 'image/jpeg',
            'jpeg': 'image/jpeg',
            'png': 'image/png',
            'gif': 'image/gif',
            'bmp': 'image/bmp',
            'svg': 'image/svg+xml',
            'webp': 'image/webp',
            'ico': 'image/x-icon',
            
            // Archives
            'zip': 'application/zip',
            'rar': 'application/x-rar-compressed',
            '7z': 'application/x-7z-compressed',
            'tar': 'application/x-tar',
            'gz': 'application/gzip',
            
            // CAD
            'dwg': 'application/acad',
            'dxf': 'application/dxf',
            'dwf': 'model/vnd.dwf',
            'dgn': 'application/x-dgn',
            
            // Other
            'mp4': 'video/mp4',
            'mp3': 'audio/mpeg',
            'wav': 'audio/wav',
            'avi': 'video/x-msvideo',
            'mov': 'video/quicktime',
            'md': 'text/markdown',
            'log': 'text/plain',
            'ini': 'text/plain',
            'cfg': 'text/plain',
            'conf': 'text/plain',
            'yaml': 'text/yaml',
            'yml': 'text/yaml'
        };
        
        return mimeTypes[ext] || 'application/octet-stream';
    }

    // Sort table
    function sortTable(field) {
        if (window.app.sortField === field) {
            // Toggle direction
            window.app.sortDirection = window.app.sortDirection === 'asc' ? 'desc' : 'asc';
        } else {
            // New field, default to ascending
            window.app.sortField = field;
            window.app.sortDirection = 'asc';
        }
        
        updateSortIndicators();
        window.app.modules.filtering.applyFilters(); // Re-apply filters which will trigger table update
        window.app.modules.urlState.push();
    }

    // Update sort indicators
    function updateSortIndicators() {
        // Remove all sort indicators
        document.querySelectorAll('th[data-sort]').forEach(th => {
            th.removeAttribute('data-sort');
        });
        
        // Add current sort indicator
        const th = document.querySelector(`th[data-field="${window.app.sortField}"]`);
        if (th) {
            th.setAttribute('data-sort', window.app.sortDirection);
        }
    }

    // Column resize functionality
    function initializeColumnResize() {
        const handles = document.querySelectorAll('.resize-handle');
        handles.forEach(handle => {
            handle.addEventListener('mousedown', startResize);
        });
        
        document.addEventListener('mousemove', doResize);
        document.addEventListener('mouseup', stopResize);
    }

    function startResize(e) {
        const th = e.target.parentElement;
        resizing = {
            th: th,
            startX: e.clientX,
            startWidth: th.offsetWidth
        };
        
        document.body.style.cursor = 'col-resize';
        document.body.style.userSelect = 'none';
    }

    function doResize(e) {
        if (! resizing) return;
        
        const diff = e.clientX - resizing.startX;
        const newWidth = Math.max(100, resizing.startWidth + diff);
        resizing.th.style.width = newWidth + 'px';
        
        // Update corresponding column
        const field = resizing.th.getAttribute('data-field');
        const cells = document.querySelectorAll(`td[data-field="${field}"]`);
        cells.forEach(cell => {
            cell.style.width = newWidth + 'px';
        });
    }

    function stopResize() {
        if (resizing) {
            document.body.style.cursor = '';
            document.body.style.userSelect = '';
            resizing = null;
        }
    }

    // Toggle all files (Ctrl+A shortcut handler)
    // wrapper around toggleSelectAllVisible for keyboard shortcuts
    function toggleSelectAll() {
        toggleSelectAllVisible(true);
    }

    /**
     * Clean up resources when page unloads
     */
    window.addEventListener('beforeunload', () => {
        cleanupAllBlobUrls();
    });

    window.app.modules.table = {
        updateFileTable,
        toggleFileSelection,
        toggleSelectAllVisible,
        updateSelectAllVisibleCheckbox,
        setupFileLinkHandlers,
        updateSortIndicators,
        sortTable,
        initializeColumnResize
    };
})();
