#!/usr/bin/env node
/**
 * Document Library UI Tests
 *
 * Tests for the library page including filters, views, PDF/text viewers,
 * and document management.
 *
 * Run: node test_library_documents_ci.js
 */

const { setupTest, teardownTest, TestResults, log, delay, navigateTo, withTimeout } = require('./test_lib');

// ============================================================================
// Library Page Structure Tests
// ============================================================================
const LibraryPageTests = {
    async libraryPageLoads(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        const result = await page.evaluate(() => {
            const hasContent = document.body.textContent.length > 100;
            const title = document.title.toLowerCase();
            const hasLibraryContent = title.includes('library') || title.includes('document') ||
                                     !!document.querySelector('.library, #library, [class*="library"]');

            return {
                hasContent,
                hasLibraryContent,
                title,
                url: window.location.href
            };
        });

        return {
            passed: result.hasContent,
            message: `Library page loads (title: "${result.title}", has content: ${result.hasContent})`
        };
    },

    async libraryHeaderStats(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        const result = await page.evaluate(() => {
            // Look for statistics in header
            const statsArea = document.querySelector('.stats, .ldr-library-stats, .header-stats, [class*="statistics"]');
            const pdfCount = document.querySelector('#total-pdfs');
            const storageSize = document.querySelector('#total-size');

            // Look for any numbers that might be stats
            const pageText = document.body.textContent;
            const hasNumbers = /\d+\s*(?:PDFs?|documents?|MB|GB|files?)/i.test(pageText);

            return {
                hasStatsArea: !!statsArea,
                hasPdfCount: !!pdfCount,
                hasStorageSize: !!storageSize,
                hasNumbers,
                pdfCountText: pdfCount?.textContent?.trim(),
                storageSizeText: storageSize?.textContent?.trim()
            };
        });

        const hasStats = result.hasStatsArea || result.hasPdfCount || result.hasNumbers;

        return {
            passed: hasStats,
            message: `Library stats: area=${result.hasStatsArea}, count="${result.pdfCountText}", size="${result.storageSizeText}"`
        };
    },

    async storageModeDisplay(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        const result = await page.evaluate(() => {
            const pageText = document.body.textContent.toLowerCase();

            // Look for storage mode indicators
            const hasEncrypted = pageText.includes('encrypted') || pageText.includes('database');
            const hasFilesystem = pageText.includes('filesystem') || pageText.includes('unencrypted');
            const hasTextOnly = pageText.includes('text only');

            // Look for storage mode badge/indicator
            const modeBadge = document.querySelector(
                '.storage-mode, ' +
                '[class*="storage-badge"], ' +
                '.mode-indicator, ' +
                '.badge'
            );

            return {
                hasEncrypted,
                hasFilesystem,
                hasTextOnly,
                hasModeBadge: !!modeBadge,
                badgeText: modeBadge?.textContent?.trim()
            };
        });

        const hasModeInfo = result.hasEncrypted || result.hasFilesystem || result.hasTextOnly || result.hasModeBadge;

        if (!hasModeInfo) {
            return { passed: null, skipped: true, message: 'No storage mode display found' };
        }

        return {
            passed: true,
            message: `Storage mode: encrypted=${result.hasEncrypted}, filesystem=${result.hasFilesystem}, badge="${result.badgeText}"`
        };
    }
};

// ============================================================================
// Filter Controls Tests
// ============================================================================
const FilterControlsTests = {
    async filterControlsExist(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);
        await delay(500);

        const result = await page.evaluate(() => {
            // Broader search for search inputs
            const searchInput = document.querySelector(
                'input[type="search"], ' +
                'input[type="text"][placeholder*="search" i], ' +
                'input[type="text"][placeholder*="filter" i], ' +
                '#search, ' +
                '.search-input, ' +
                '[class*="search-input"], ' +
                '[class*="filter-input"]'
            );

            // Look for any filter controls
            const collectionFilter = document.querySelector(
                'select[name*="collection"], ' +
                '#collection-filter, ' +
                '[class*="collection-filter"], ' +
                '[class*="collection-select"]'
            );

            const domainFilter = document.querySelector(
                'select[name*="domain"], ' +
                '#domain-filter, ' +
                '[class*="domain-filter"]'
            );

            const dateFilter = document.querySelector(
                'select[name*="date"], ' +
                '#date-filter, ' +
                '.date-filter, ' +
                '[class*="date-range"], ' +
                '[class*="date-filter"]'
            );

            // Also check if library page has any content at all
            const hasLibraryContent = document.querySelector('.library, #library, [class*="library"], .documents, [class*="document"]');
            const pageTitle = document.title?.toLowerCase() || '';
            const isLibraryPage = pageTitle.includes('library') || !!hasLibraryContent;

            return {
                hasSearch: !!searchInput,
                hasCollectionFilter: !!collectionFilter,
                hasDomainFilter: !!domainFilter,
                hasDateFilter: !!dateFilter,
                searchPlaceholder: searchInput?.placeholder,
                isLibraryPage,
                hasAnyContent: !!hasLibraryContent
            };
        });

        const hasFilters = result.hasSearch || result.hasCollectionFilter ||
                          result.hasDomainFilter || result.hasDateFilter;

        // If no filters but we're on library page, skip instead of fail
        if (!hasFilters) {
            if (result.isLibraryPage || result.hasAnyContent) {
                return { passed: null, skipped: true, message: 'Library page found but filter controls not visible (may be dynamic or require documents)' };
            }
            return { passed: null, skipped: true, message: 'Library page or filter controls not found' };
        }

        return {
            passed: true,
            message: `Filters: search=${result.hasSearch} ("${result.searchPlaceholder}"), collection=${result.hasCollectionFilter}, domain=${result.hasDomainFilter}, date=${result.hasDateFilter}`
        };
    },

    async searchFilterFunctionality(page, baseUrl) {
        // #search-documents (library.html) is the real document search input,
        // wired to filter .ldr-document-card client-side on the 'input' event
        // (library_search_ui.js:142). The old test typed a query but returned
        // passed:true regardless. Assert the real input exists and reflects
        // typed input.
        await navigateTo(page, `${baseUrl}/library`);
        await page.waitForSelector('#search-documents', { timeout: 10000 });

        const info = await page.evaluate(() => {
            const el = document.getElementById('search-documents');
            return { exists: !!el, type: el?.type, placeholder: el?.placeholder || '' };
        });
        if (!info.exists) {
            return { passed: false, message: '#search-documents input not found on /library' };
        }

        // Clear any pre-filled value first so the assertion is deterministic
        // (Puppeteer's page.type appends — there is no page.fill).
        await page.$eval('#search-documents', el => { el.value = ''; });
        await page.type('#search-documents', 'fixture');
        const value = await page.$eval('#search-documents', el => el.value);
        const passed = value === 'fixture';
        return {
            passed,
            message: passed
                ? `Document search input accepts input (placeholder="${info.placeholder}")`
                : `Search input did not reflect typed value (got "${value}")`
        };
    },

    async dateFilterButtons(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        const result = await page.evaluate(() => {
            const buttons = Array.from(document.querySelectorAll('button, .btn, [class*="filter-btn"]'));

            const dateButtons = {
                today: buttons.find(b => b.textContent?.toLowerCase().includes('today')),
                week: buttons.find(b => b.textContent?.toLowerCase().includes('week')),
                month: buttons.find(b => b.textContent?.toLowerCase().includes('month')),
                all: buttons.find(b => b.textContent?.toLowerCase().includes('all'))
            };

            return {
                hasToday: !!dateButtons.today,
                hasWeek: !!dateButtons.week,
                hasMonth: !!dateButtons.month,
                hasAll: !!dateButtons.all,
                foundButtons: Object.entries(dateButtons).filter(([_k, v]) => v).map(([k]) => k)
            };
        });

        const hasDateButtons = result.foundButtons.length > 0;

        if (!hasDateButtons) {
            return { passed: null, skipped: true, message: 'No date filter buttons found' };
        }

        return {
            passed: true,
            message: `Date filter buttons: ${result.foundButtons.join(', ')}`
        };
    }
};

// ============================================================================
// View Toggle Tests
// ============================================================================
const ViewToggleTests = {
    async viewToggleExists(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        const result = await page.evaluate(() => {
            const gridBtn = document.querySelector(
                '[data-view="grid"], ' +
                '.grid-view, ' +
                '#grid-view, ' +
                'button[title*="grid"], ' +
                '[class*="grid-toggle"]'
            );

            const listBtn = document.querySelector(
                '[data-view="list"], ' +
                '.list-view, ' +
                '#list-view, ' +
                'button[title*="list"], ' +
                '[class*="list-toggle"]'
            );

            const viewToggle = document.querySelector(
                '.ldr-view-toggle, ' +
                '[class*="view-switch"], ' +
                '.view-options'
            );

            return {
                hasGridBtn: !!gridBtn,
                hasListBtn: !!listBtn,
                hasViewToggle: !!viewToggle,
                gridIcon: gridBtn?.innerHTML?.includes('grid') || gridBtn?.className?.includes('grid'),
                listIcon: listBtn?.innerHTML?.includes('list') || listBtn?.className?.includes('list')
            };
        });

        const hasToggle = result.hasGridBtn || result.hasListBtn || result.hasViewToggle;

        if (!hasToggle) {
            return { passed: null, skipped: true, message: 'No view toggle found' };
        }

        return {
            passed: true,
            message: `View toggle: grid=${result.hasGridBtn}, list=${result.hasListBtn}`
        };
    },

    async viewToggleFunctionality(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        const result = await page.evaluate(() => {
            const gridBtn = document.querySelector('[data-view="grid"], .grid-view, #grid-view');
            const listBtn = document.querySelector('[data-view="list"], .list-view, #list-view');

            // Get initial view state
            const container = document.querySelector('.library-content, .documents, .doc-list, main');
            const initialClass = container?.className;

            // Click list view if grid exists (or vice versa)
            if (listBtn) {
                listBtn.click();
            } else if (gridBtn) {
                gridBtn.click();
            }

            const afterClass = container?.className;

            return {
                hasButtons: !!(gridBtn || listBtn),
                classChanged: initialClass !== afterClass,
                initialClass,
                afterClass
            };
        });

        if (!result.hasButtons) {
            return { passed: null, skipped: true, message: 'No view toggle buttons to test' };
        }

        return {
            passed: result.hasButtons,
            message: `View toggle: class changed=${result.classChanged}`
        };
    }
};

// ============================================================================
// Document Card Tests
// ============================================================================
const DocumentCardTests = {
    async documentCardsDisplay(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        const result = await page.evaluate(() => {
            const cards = document.querySelectorAll(
                '.ldr-document-card, ' +
                '.doc-card, ' +
                '.library-item, ' +
                '[class*="document-item"], ' +
                '.card'
            );

            if (cards.length === 0) {
                // Check for empty state
                const emptyState = document.querySelector('.ldr-empty-state, .no-documents, [class*="empty"]');
                return {
                    hasCards: false,
                    cardCount: 0,
                    hasEmptyState: !!emptyState,
                    emptyText: emptyState?.textContent?.trim()?.substring(0, 50)
                };
            }

            const firstCard = cards[0];
            const hasTitle = !!firstCard.querySelector('.title, .ldr-doc-title, h3, h4, [class*="title"]');
            const hasAuthor = !!firstCard.querySelector('.author, [class*="author"]');
            const hasDate = !!firstCard.querySelector('.date, time, [class*="date"]');
            const hasDomain = !!firstCard.querySelector('.domain, .badge, [class*="domain"]');

            return {
                hasCards: true,
                cardCount: cards.length,
                hasTitle,
                hasAuthor,
                hasDate,
                hasDomain
            };
        });

        if (!result.hasCards && result.hasEmptyState) {
            return {
                passed: true,
                message: `Empty state shown: "${result.emptyText}"`
            };
        }

        return {
            passed: result.hasCards,
            message: result.hasCards
                ? `${result.cardCount} document cards (title=${result.hasTitle}, author=${result.hasAuthor}, date=${result.hasDate})`
                : 'No document cards found'
        };
    },

    async documentCardActions(page, baseUrl) {
        // NOTE: deferred from the dead-field audit's PR 3. The real assertion
        // (seed collection + upload a doc, then assert .ldr-action-btn-txt +
        // .ldr-btn-delete-doc on the card) works, but the uploaded doc leaks
        // into this file's later PDF/Text viewer tests (collection deletion
        // doesn't cascade-delete the doc), turning their skips into failures.
        // Needs document-level cleanup or test reordering — tracked as a
        // follow-up so it doesn't destabilise the library shard here.
        await navigateTo(page, `${baseUrl}/library`);

        const result = await page.evaluate(() => {
            const card = document.querySelector('.ldr-document-card, .doc-card, .library-item, .card');
            if (!card) return { hasCard: false };

            const viewPdfBtn = card.querySelector('[class*="view-pdf"], button[title*="PDF"], .pdf-btn');
            const viewTextBtn = card.querySelector('[class*="view-text"], button[title*="text"], .text-btn');
            const deleteBtn = card.querySelector('[class*="delete"], .delete-btn, button[title*="delete"]');
            const downloadBtn = card.querySelector('[class*="download"], .download-btn, a[download]');

            return {
                hasCard: true,
                hasViewPdf: !!viewPdfBtn,
                hasViewText: !!viewTextBtn,
                hasDelete: !!deleteBtn,
                hasDownload: !!downloadBtn
            };
        });

        if (!result.hasCard) {
            return { passed: null, skipped: true, message: 'No document cards to check actions' };
        }

        const hasActions = result.hasViewPdf || result.hasViewText || result.hasDelete || result.hasDownload;

        return {
            passed: hasActions,
            message: `Card actions: viewPdf=${result.hasViewPdf}, viewText=${result.hasViewText}, delete=${result.hasDelete}, download=${result.hasDownload}`
        };
    }
};

// ============================================================================
// PDF Viewer Tests
// ============================================================================
const PdfViewerTests = {
    async pdfViewerModalExists(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        // Only test PDF viewer if there are document cards
        const hasCards = await page.evaluate(() => {
            return !!document.querySelector('.ldr-document-card, .doc-card, .library-item');
        });
        if (!hasCards) {
            return { passed: null, skipped: true, message: 'No documents to test PDF viewer' };
        }

        // Try to click view PDF button on a card
        const clicked = await page.evaluate(() => {
            const card = document.querySelector('.ldr-document-card, .doc-card, .library-item');
            if (!card) return false;
            const viewBtn = card.querySelector(
                '[class*="view-pdf"], ' +
                'button[title*="PDF"], ' +
                '.pdf-btn, ' +
                '[data-action="view-pdf"]'
            );
            if (viewBtn) {
                viewBtn.click();
                return true;
            }
            return false;
        });

        if (!clicked) {
            return { passed: null, skipped: true, message: 'No PDF view button on document card' };
        }

        await delay(500);

        const result = await page.evaluate(() => {
            const modal = document.querySelector(
                '.modal, ' +
                '.pdf-viewer, ' +
                '[role="dialog"], ' +
                '.viewer-modal, ' +
                '#pdf-modal'
            );

            const pdfEmbed = document.querySelector('embed[type*="pdf"], iframe[src*="pdf"], object[type*="pdf"]');
            const pdfCanvas = document.querySelector('canvas.pdf-page, .pdf-canvas');

            return {
                hasModal: !!modal && window.getComputedStyle(modal).display !== 'none',
                hasPdfEmbed: !!pdfEmbed,
                hasPdfCanvas: !!pdfCanvas,
                modalClass: modal?.className
            };
        });

        const hasPdfViewer = result.hasModal || result.hasPdfEmbed || result.hasPdfCanvas;

        return {
            passed: hasPdfViewer,
            message: hasPdfViewer
                ? `PDF viewer: modal=${result.hasModal}, embed=${result.hasPdfEmbed}, canvas=${result.hasPdfCanvas}`
                : 'PDF viewer modal did not appear'
        };
    },

    async pdfViewerCloseButton(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        // Only test if there are document cards
        const hasCards = await page.evaluate(() => {
            return !!document.querySelector('.ldr-document-card, .doc-card, .library-item');
        });
        if (!hasCards) {
            return { passed: null, skipped: true, message: 'No documents to test PDF close button' };
        }

        // Open PDF viewer from a card
        await page.evaluate(() => {
            const card = document.querySelector('.ldr-document-card, .doc-card, .library-item');
            if (!card) return;
            const viewBtn = card.querySelector('[class*="view-pdf"], .pdf-btn, [data-action="view-pdf"]');
            if (viewBtn) viewBtn.click();
        });

        await delay(500);

        const result = await page.evaluate(() => {
            const modal = document.querySelector('.modal, .pdf-viewer, [role="dialog"]');
            if (!modal) return { hasModal: false };

            const closeBtn = modal.querySelector(
                '.close, ' +
                '.close-btn, ' +
                '[class*="close"], ' +
                'button[aria-label="Close"], ' +
                '.modal-close'
            );

            return {
                hasModal: true,
                hasCloseBtn: !!closeBtn,
                closeBtnText: closeBtn?.textContent?.trim() || closeBtn?.getAttribute('aria-label')
            };
        });

        if (!result.hasModal) {
            return { passed: null, skipped: true, message: 'PDF modal not open for close button test' };
        }

        return {
            passed: result.hasCloseBtn,
            message: result.hasCloseBtn
                ? `Close button found: "${result.closeBtnText}"`
                : 'No close button in PDF viewer'
        };
    }
};

// ============================================================================
// Text Viewer Tests
// ============================================================================
const TextViewerTests = {
    async textViewerModalExists(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        // Only test if there are document cards
        const hasCards = await page.evaluate(() => {
            return !!document.querySelector('.ldr-document-card, .doc-card, .library-item');
        });
        if (!hasCards) {
            return { passed: null, skipped: true, message: 'No documents to test text viewer' };
        }

        // Try to click view text button on a card
        const clicked = await page.evaluate(() => {
            const card = document.querySelector('.ldr-document-card, .doc-card, .library-item');
            if (!card) return false;
            const viewBtn = card.querySelector(
                '[class*="view-text"], ' +
                'button[title*="text"], ' +
                '.text-btn, ' +
                '[data-action="view-text"]'
            );
            if (viewBtn) {
                viewBtn.click();
                return true;
            }
            return false;
        });

        if (!clicked) {
            return { passed: null, skipped: true, message: 'No text view button on document card' };
        }

        await delay(500);

        const result = await page.evaluate(() => {
            const modal = document.querySelector(
                '.modal, ' +
                '.text-viewer, ' +
                '[role="dialog"], ' +
                '.viewer-modal, ' +
                '#text-modal'
            );

            const textContent = modal?.querySelector('pre, .text-content, .document-text, textarea[readonly]');

            return {
                hasModal: !!modal && window.getComputedStyle(modal).display !== 'none',
                hasTextContent: !!textContent,
                textLength: textContent?.textContent?.length || 0
            };
        });

        const hasTextViewer = result.hasModal || result.hasTextContent;

        return {
            passed: hasTextViewer,
            message: hasTextViewer
                ? `Text viewer: modal=${result.hasModal}, content length=${result.textLength}`
                : 'Text viewer modal did not appear'
        };
    },

    async textViewerCopyButton(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        // Open text viewer
        await page.evaluate(() => {
            const viewBtn = document.querySelector('[class*="view-text"], .text-btn, [data-action="view-text"]');
            if (viewBtn) viewBtn.click();
        });

        await delay(500);

        const result = await page.evaluate(() => {
            const modal = document.querySelector('.modal, .text-viewer, [role="dialog"]');
            if (!modal) return { hasModal: false };

            const copyBtn = modal.querySelector(
                '[class*="copy"], ' +
                'button[title*="copy"], ' +
                '.copy-btn, ' +
                '[data-action="copy"]'
            );

            return {
                hasModal: true,
                hasCopyBtn: !!copyBtn,
                copyBtnText: copyBtn?.textContent?.trim() || copyBtn?.getAttribute('title')
            };
        });

        if (!result.hasModal) {
            return { passed: null, skipped: true, message: 'Text modal not open for copy button test' };
        }

        if (!result.hasCopyBtn) {
            return { passed: null, skipped: true, message: 'No copy button in text viewer' };
        }

        return {
            passed: true,
            message: `Copy button found: "${result.copyBtnText}"`
        };
    }
};

// ============================================================================
// Bulk Actions Tests
// ============================================================================
const BulkActionsTests = {
    async getAllPdfsButton(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        const result = await page.evaluate(() => {
            const buttons = Array.from(document.querySelectorAll('button, .btn, a.btn'));
            const getAllBtn = buttons.find(btn => {
                const text = btn.textContent?.toLowerCase() || '';
                return text.includes('get all') || text.includes('download all') ||
                       text.includes('fetch all') || text.includes('bulk download');
            });

            return {
                hasButton: !!getAllBtn,
                buttonText: getAllBtn?.textContent?.trim()
            };
        });

        if (!result.hasButton) {
            return { passed: null, skipped: true, message: 'No get all PDFs button found' };
        }

        return {
            passed: true,
            message: `Get all PDFs button: "${result.buttonText}"`
        };
    },

    async syncLibraryButton(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/library`);

        const result = await page.evaluate(() => {
            const buttons = Array.from(document.querySelectorAll('button, .btn, a.btn'));
            const syncBtn = buttons.find(btn => {
                const text = btn.textContent?.toLowerCase() || '';
                return text.includes('sync') || text.includes('refresh') || text.includes('update library');
            });

            return {
                hasButton: !!syncBtn,
                buttonText: syncBtn?.textContent?.trim()
            };
        });

        if (!result.hasButton) {
            return { passed: null, skipped: true, message: 'No sync library button found' };
        }

        return {
            passed: true,
            message: `Sync button: "${result.buttonText}"`
        };
    }
};

// ============================================================================
// Main Test Runner
// ============================================================================
async function main() {
    log.section('Document Library UI Tests');

    const ctx = await setupTest({ authenticate: true });
    const results = new TestResults('Library Documents Tests');
    const { page } = ctx;
    const { baseUrl } = ctx.config;

    // Helper: run a sub-test with a 30s timeout so a single hang
    // doesn't block the entire 300s process-level timeout.
    const subTestTimeout = ctx.config.isCI ? 60000 : 30000;
    async function run(category, name, testFn) {
        try {
            const result = await withTimeout(
                testFn(page, baseUrl),
                subTestTimeout,
                `${category}/${name}`
            );
            if (result.skipped) {
                results.skip(category, name, result.message);
            } else {
                results.add(category, name, result.passed, result.message);
            }
        } catch (error) {
            results.add(category, name, false, `Error: ${error.message}`);
        }
    }

    try {
        // Library Page Structure
        log.section('Library Page Structure');
        await run('Page', 'Loads', (p, u) => LibraryPageTests.libraryPageLoads(p, u));
        await run('Page', 'Header Stats', (p, u) => LibraryPageTests.libraryHeaderStats(p, u));
        await run('Page', 'Storage Mode', (p, u) => LibraryPageTests.storageModeDisplay(p, u));

        // Filter Controls
        log.section('Filter Controls');
        await run('Filters', 'Controls Exist', (p, u) => FilterControlsTests.filterControlsExist(p, u));
        await run('Filters', 'Search', (p, u) => FilterControlsTests.searchFilterFunctionality(p, u));
        await run('Filters', 'Date Buttons', (p, u) => FilterControlsTests.dateFilterButtons(p, u));

        // View Toggle
        log.section('View Toggle');
        await run('View', 'Toggle Exists', (p, u) => ViewToggleTests.viewToggleExists(p, u));
        await run('View', 'Toggle Works', (p, u) => ViewToggleTests.viewToggleFunctionality(p, u));

        // Document Cards
        log.section('Document Cards');
        await run('Cards', 'Display', (p, u) => DocumentCardTests.documentCardsDisplay(p, u));
        await run('Cards', 'Actions', (p, u) => DocumentCardTests.documentCardActions(p, u));

        // PDF Viewer
        log.section('PDF Viewer');
        await run('PDF', 'Viewer Modal', (p, u) => PdfViewerTests.pdfViewerModalExists(p, u));
        await run('PDF', 'Close Button', (p, u) => PdfViewerTests.pdfViewerCloseButton(p, u));

        // Text Viewer
        log.section('Text Viewer');
        await run('Text', 'Viewer Modal', (p, u) => TextViewerTests.textViewerModalExists(p, u));
        await run('Text', 'Copy Button', (p, u) => TextViewerTests.textViewerCopyButton(p, u));

        // Bulk Actions
        log.section('Bulk Actions');
        await run('Bulk', 'Get All PDFs', (p, u) => BulkActionsTests.getAllPdfsButton(p, u));
        await run('Bulk', 'Sync Library', (p, u) => BulkActionsTests.syncLibraryButton(p, u));

    } catch (error) {
        log.error(`Fatal error: ${error.message}`);
        console.error(error.stack);
    } finally {
        results.print();
        results.save();
        await teardownTest(ctx);
        process.exit(results.exitCode());
    }
}

// Run if executed directly
if (require.main === module) {
    main().catch(error => {
        console.error('Test runner failed:', error);
        process.exit(1);
    });
}

module.exports = { LibraryPageTests, FilterControlsTests, ViewToggleTests, DocumentCardTests, PdfViewerTests, TextViewerTests, BulkActionsTests };
