/**
 * PDF Service
 * Handles generation of PDFs from research results
 */

/**
 * Replace KaTeX-rendered math elements with their raw LaTeX source.
 *
 * The PDF generator extracts element.textContent for pdf.text(), which would
 * garble KaTeX's dual HTML+MathML output (rendered glyphs concatenated with
 * the duplicated LaTeX source from <annotation>). We swap each rendered math
 * element for its original LaTeX so PDFs show readable formulas.
 *
 * Display math (`$$\n...\n$$`) is emitted by marked-katex-extension as a
 * top-level <span class="katex-display"> with NO <p> wrapper. The PDF walker
 * iterates contentDiv.children (Elements only — text nodes skipped), so a
 * bare text-node replacement would silently drop top-level display math.
 * Wrap display math in <p> so the walker still finds it. Inline .katex
 * always sits inside a <p>/<li>/etc., so a bare text node is safe there.
 *
 * @param {Element} container - The DOM element containing rendered KaTeX
 */
function replaceKatexWithLatex(container) {
    container.querySelectorAll('.katex-display').forEach((el) => {
        const ann = el.querySelector('annotation[encoding="application/x-tex"]');
        if (!ann) return;
        const p = document.createElement('p');
        p.textContent = `$$${ann.textContent}$$`;
        // replaceWith receives a Node built via textContent, not an HTML
        // string, so no HTML parsing occurs -- Bearer false positive.
        // (Directive must be the bare rule id on its own line; trailing
        // prose after the rule id makes Bearer ignore it.)
        // bearer:disable javascript_lang_dangerous_insert_html
        el.replaceWith(p);
    });
    container.querySelectorAll('.katex').forEach((el) => {
        const ann = el.querySelector('annotation[encoding="application/x-tex"]');
        // replaceWith receives a TextNode via createTextNode, not an HTML
        // string, so no HTML parsing occurs -- Bearer false positive.
        // bearer:disable javascript_lang_dangerous_insert_html
        if (ann) el.replaceWith(document.createTextNode(`$${ann.textContent}$`));
    });
}

/**
 * Generate a PDF from the research content
 * @param {string|any} title - The research title/query
 * @param {string|any} content - The markdown content of the research
 * @param {Object} metadata - Additional metadata for the PDF
 * @returns {Promise<Blob>} A promise resolving to the generated PDF blob
 */
async function generatePdf(title, content, _metadata = {}) {
    // Check if necessary libraries are loaded (check window object for Vite compatibility)
    if (typeof jsPDF === 'undefined' && typeof window.jsPDF === 'undefined') {
        throw new Error('PDF generation libraries not loaded (jsPDF missing)');
    }

    if (typeof html2canvas === 'undefined' && typeof window.html2canvas === 'undefined') {
        throw new Error('PDF generation libraries not loaded (html2canvas missing)');
    }

    // Use the global versions if local ones aren't available
    const jsPDFLib = typeof jsPDF !== 'undefined' ? jsPDF : window.jsPDF;
    const html2canvasLib = typeof html2canvas !== 'undefined' ? html2canvas : window.html2canvas;

    // Ensure content is a string
    content = String(content || '');

    // Create a temporary container to render the markdown
    const tempContainer = document.createElement('div');
    tempContainer.style.position = 'absolute';
    tempContainer.style.left = '-9999px';
    tempContainer.style.top = '-9999px';
    tempContainer.style.width = '8.5in'; // US Letter width
    tempContainer.className = 'ldr-pdf-content';

    // Add PDF-specific styles
    tempContainer.innerHTML = `
        <style>
            .ldr-pdf-content {
                font-family: Arial, sans-serif;
                color: #333;
                line-height: 1.5;
                padding: 20px;
                background-color: #ffffff;
            }
            .ldr-pdf-content h1 {
                font-size: 24px;
                color: #000;
                margin-bottom: 12px;
            }
            .ldr-pdf-content h2 {
                font-size: 20px;
                color: #000;
                margin-top: 20px;
                margin-bottom: 10px;
            }
            .ldr-pdf-content h3 {
                font-size: 16px;
                color: #000;
                margin-top: 16px;
                margin-bottom: 8px;
            }
            .ldr-pdf-content p {
                margin-bottom: 10px;
            }
            .ldr-pdf-content ul, .ldr-pdf-content ol {
                margin-left: 20px;
                margin-bottom: 10px;
            }
            .ldr-pdf-content li {
                margin-bottom: 5px;
            }
            .ldr-pdf-content pre {
                background-color: #f5f5f5;
                padding: 10px;
                border-radius: 4px;
                overflow-x: auto;
                font-family: monospace;
                font-size: 12px;
                margin-bottom: 10px;
            }
            .ldr-pdf-content code {
                font-family: monospace;
                background-color: #f5f5f5;
                padding: 2px 4px;
                border-radius: 2px;
                font-size: 12px;
            }
            .ldr-pdf-content blockquote {
                border-left: 4px solid #ddd;
                padding-left: 15px;
                margin-left: 0;
                color: #666;
            }
            .ldr-pdf-content table {
                border-collapse: collapse;
                width: 100%;
                margin-bottom: 15px;
            }
            .ldr-pdf-content table, .ldr-pdf-content th, .ldr-pdf-content td {
                border: 1px solid #ddd;
            }
            .ldr-pdf-content th, .ldr-pdf-content td {
                padding: 8px;
                text-align: left;
            }
            .ldr-pdf-content th {
                background-color: #f5f5f5;
            }
            .ldr-pdf-metadata {
                color: #666;
                font-size: 12px;
                margin-bottom: 20px;
                border-bottom: 1px solid #ddd;
                padding-bottom: 10px;
            }
            .ldr-pdf-header {
                text-align: center;
                border-bottom: 2px solid #333;
                padding-bottom: 10px;
                margin-bottom: 20px;
            }
            .ldr-pdf-footer {
                border-top: 1px solid #ddd;
                padding-top: 10px;
                margin-top: 20px;
                font-size: 12px;
                color: #666;
            }
            .ldr-pdf-content a {
                color: #0066cc;
                text-decoration: underline;
            }
        </style>
        <div class="ldr-pdf-body" id="pdf-body-content">
            <!-- Content will be added after marked parsing -->
        </div>
    `;

    // Add to the document body temporarily
    document.body.appendChild(tempContainer);

    // Now parse and add the content
    const pdfBodyDiv = tempContainer.querySelector('#pdf-body-content');
    if (pdfBodyDiv) {
        if (window.marked && typeof window.marked.parse === 'function') {
            const rawHtml = window.marked.parse(content);
            if (!window.DOMPurify) {
                document.body.removeChild(tempContainer);
                throw new Error('DOMPurify not loaded. Cannot generate PDF safely.');
            }
            pdfBodyDiv.innerHTML = window.DOMPurify.sanitize(rawHtml, {
                    ADD_TAGS: ['semantics', 'annotation'],
                  });
            replaceKatexWithLatex(pdfBodyDiv);
        } else {
            // marked.js not available - this is a critical error for PDF generation
            document.body.removeChild(tempContainer);
            throw new Error('Markdown parser (marked.js) not loaded. Cannot generate PDF.');
        }
    } else {
        document.body.removeChild(tempContainer);
        throw new Error('PDF body container not found in template');
    }

    try {
        // Create a new PDF document
        const pdf = new jsPDFLib('p', 'pt', 'letter');
        const pdfWidth = pdf.internal.pageSize.getWidth();
        const pdfHeight = pdf.internal.pageSize.getHeight();
        const margin = 40;
        const contentWidth = pdfWidth - 2 * margin;

        // Add footer to page
        const addFooterToCurrentPage = (pageNum) => {
            pdf.setFontSize(8);
            pdf.setTextColor(100, 100, 100);
            pdf.text(`Page ${pageNum}`, margin, pdfHeight - 20);
        };

        // Process each element with a more optimized approach
        const contentDiv = tempContainer.querySelector('#pdf-body-content');
        if (!contentDiv) {
            throw new Error('PDF body container not found');
        }

        // Create a more efficient PDF generation approach that keeps text selectable
        const elements = Array.from(contentDiv.children);
        let currentY = margin; // Start position at margin
        let pageNum = 1;

        // Start with the first page
        // Note: jsPDF starts with one page already, no need to add

        // Add footer to first page
        addFooterToCurrentPage(pageNum);

        // If no elements found, try to add raw content
        if (elements.length === 0) {
            const rawContent = contentDiv.textContent || '';
            if (rawContent) {
                pdf.setFont('helvetica', 'normal');
                pdf.setFontSize(11);
                const lines = pdf.splitTextToSize(rawContent, contentWidth);
                pdf.text(lines, margin, currentY);
            }
        }

        // Process each element
        for (const element of elements) {
            // Skip style element
            if (element.tagName === 'STYLE') continue;

            try {
                // Simple text content - handled directly by jsPDF for better text selection
                if ((element.tagName === 'P' || element.tagName === 'DIV' || element.tagName === 'H1' ||
                     element.tagName === 'H2' || element.tagName === 'H3') &&
                    !element.querySelector('img, canvas, svg, table')) {

                    // Special styling for headers
                    if (element.tagName.startsWith('H')) {
                        pdf.setFont('helvetica', 'bold');

                        // Add background for H1 and H2
                        if (element.tagName === 'H1' || element.tagName === 'H2') {
                            // Check if we need a new page for header
                            if (currentY + 30 > pdfHeight - margin) {
                                pageNum++;
                                pdf.addPage();
                                currentY = margin;
                            }

                            // Draw background rectangle
                            if (element.tagName === 'H1') {
                                pdf.setFillColor(0, 51, 102); // Dark blue for H1
                                pdf.rect(margin - 10, currentY, contentWidth + 20, 28, 'F');
                                pdf.setTextColor(255, 255, 255); // White text
                                pdf.setFontSize(18);
                            } else {
                                pdf.setFillColor(240, 240, 240); // Light gray for H2
                                pdf.rect(margin - 10, currentY + 2, contentWidth + 20, 22, 'F');
                                pdf.setTextColor(0, 51, 102); // Dark blue text
                                pdf.setFontSize(16);
                            }
                        } else {
                            pdf.setFontSize(14);
                            pdf.setTextColor(0, 51, 102); // Dark blue for H3
                        }
                    } else {
                        pdf.setFont('helvetica', 'normal');
                        pdf.setFontSize(11);
                        pdf.setTextColor(0, 0, 0);
                    }

                    // Check if element contains links
                    const links = element.querySelectorAll('a');
                    if (links.length > 0) {
                        // Process element with links
                        let currentX = margin;
                        let currentLine = currentY + 12;

                        // Process each child node
                        for (const node of element.childNodes) {
                            if (node.nodeType === Node.TEXT_NODE) {
                                // Regular text
                                const text = node.textContent;
                                if (text.trim()) {
                                    const words = text.split(/\s+/);
                                    for (const word of words) {
                                        const wordWidth = pdf.getTextWidth(word + ' ');
                                        if (currentX + wordWidth > pdfWidth - margin && currentX > margin) {
                                            currentLine += 14;
                                            currentX = margin;
                                            if (currentLine > pdfHeight - margin) {
                                                pageNum++;
                                                pdf.addPage();
                                                currentLine = margin;
                                            }
                                        }
                                        pdf.setTextColor(0, 0, 0);
                                        pdf.text(word + ' ', currentX, currentLine);
                                        currentX += wordWidth;
                                    }
                                }
                            } else if (node.tagName === 'A') {
                                // Link element
                                const linkText = node.textContent;
                                const linkUrl = node.href;
                                const linkWidth = pdf.getTextWidth(linkText + ' ');

                                if (currentX + linkWidth > pdfWidth - margin && currentX > margin) {
                                    currentLine += 14;
                                    currentX = margin;
                                    if (currentLine > pdfHeight - margin) {
                                        pageNum++;
                                        pdf.addPage();
                                        currentLine = margin;
                                    }
                                }

                                // Add link with styled blue color
                                pdf.setTextColor(0, 102, 204);
                                pdf.textWithLink(linkText + ' ', currentX, currentLine, { url: linkUrl });

                                // Underline
                                pdf.setDrawColor(0, 102, 204);
                                pdf.setLineWidth(0.3);
                                pdf.line(currentX, currentLine + 2, currentX + linkWidth - 5, currentLine + 2);

                                currentX += linkWidth;
                            }
                        }

                        currentY = currentLine + 2;
                        if (element.tagName === 'P') currentY += 6; // Extra space after paragraphs
                        continue; // Skip the rest of the processing for this element
                    }

                    // No links, process as plain text
                    const text = element.textContent.trim();
                    if (!text) continue; // Skip empty text

                    // Special handling for text with citation links like [[ref]](url) or [ref](url) in plain text
                    if ((text.includes('[[') && text.includes('](')) || (text.includes('[') && text.includes(']('))) {
                        // Process text with citation links - handle both [[ref]](url) and [ref](url) formats
                        const citationRegex = /\[?\[([^\]]+)\]\]?\(([^)]+)\)/g;
                        let lastIndex = 0;
                        let currentX = margin;
                        let lineStarted = false;

                        // PDF generator state (currentY, pageNum) is mutated by both this
                        // closure and the surrounding loop; the closure runs synchronously
                        // each loop iteration so the no-loop-func hazard doesn't apply here.
                        // eslint-disable-next-line no-loop-func
                        const processTextSegment = (segment) => {
                            if (!segment) return;

                            const words = segment.split(' ');
                            for (const word of words) {
                                const wordWidth = pdf.getTextWidth(word + ' ');

                                // Check if word fits on current line
                                if (currentX + wordWidth > pdfWidth - margin && lineStarted) {
                                    currentY += 14;
                                    currentX = margin;
                                    lineStarted = false;

                                    // Check for new page
                                    if (currentY + 14 > pdfHeight - margin) {
                                        pageNum++;
                                        pdf.addPage();
                                        currentY = margin;
                                    }
                                }

                                pdf.setTextColor(0, 0, 0);
                                pdf.text(word + ' ', currentX, currentY + 12);
                                currentX += wordWidth;
                                lineStarted = true;
                            }
                        };

                        let match;
                        while ((match = citationRegex.exec(text)) !== null) {
                            // Process text before citation
                            if (match.index > lastIndex) {
                                processTextSegment(text.substring(lastIndex, match.index));
                            }

                            // Process citation link
                            const citationText = `[${match[1]}]`;
                            const citationUrl = match[2];
                            const citationWidth = pdf.getTextWidth(citationText);

                            // Check if citation fits on current line
                            if (currentX + citationWidth > pdfWidth - margin && lineStarted) {
                                currentY += 14;
                                currentX = margin;
                                lineStarted = false;

                                if (currentY + 14 > pdfHeight - margin) {
                                    pageNum++;
                                    pdf.addPage();
                                    currentY = margin;
                                }
                            }

                            // Add citation as clickable link
                            pdf.setTextColor(0, 102, 204);
                            pdf.textWithLink(citationText, currentX, currentY + 12, { url: citationUrl });

                            // Underline
                            pdf.setDrawColor(0, 102, 204);
                            pdf.setLineWidth(0.3);
                            pdf.line(currentX, currentY + 14, currentX + citationWidth, currentY + 14);

                            currentX += citationWidth;
                            lineStarted = true;
                            lastIndex = match.index + match[0].length;
                        }

                        // Process remaining text
                        if (lastIndex < text.length) {
                            processTextSegment(text.substring(lastIndex));
                        }

                        currentY += 14;
                        if (element.tagName === 'P') currentY += 6; // Extra space after paragraphs
                    } else {
                        // No citation links, process normally
                        const textLines = pdf.splitTextToSize(text, contentWidth);

                        // Check if we need a new page
                        if (currentY + (textLines.length * 14) > pdfHeight - margin) {
                            pageNum++;
                            pdf.addPage();
                            currentY = margin;
                        }

                        // Adjust text position for headers with background
                        let textY = currentY + 12;
                        if (element.tagName === 'H1') {
                            textY = currentY + 18; // Center text in H1 background
                        } else if (element.tagName === 'H2') {
                            textY = currentY + 16; // Center text in H2 background
                        }

                        pdf.text(textLines, margin, textY);

                        // Adjust currentY based on element type
                        if (element.tagName === 'H1') {
                            currentY += 36; // Account for background height + spacing
                        } else if (element.tagName === 'H2') {
                            currentY += 32; // Account for background height + spacing
                        } else if (element.tagName === 'H3') {
                            currentY += (textLines.length * 14) + 8; // Extra space after H3
                        } else {
                            currentY += (textLines.length * 14);
                            if (element.tagName === 'P') currentY += 6; // Extra space after paragraphs
                        }
                    }
                }
                // List elements - handle bullets and numbering
                else if (element.tagName === 'UL' || element.tagName === 'OL') {
                    const listItems = Array.from(element.querySelectorAll('li'));
                    for (let i = 0; i < listItems.length; i++) {
                        const item = listItems[i];

                        pdf.setFont('helvetica', 'normal');
                        pdf.setFontSize(11);
                        pdf.setTextColor(0, 0, 0);

                        const bulletPoint = element.tagName === 'UL' ? '•' : `${i + 1}.`;
                        const indentX = margin + 15; // Indent for list items

                        // Check if we need a new page
                        if (currentY + 14 > pdfHeight - margin) {
                            pageNum++;
                            pdf.addPage();
                            currentY = margin;
                        }

                        // Draw bullet/number
                        pdf.text(bulletPoint, margin, currentY + 12);

                        // Process list item content (may contain links)
                        const links = item.querySelectorAll('a');
                        if (links.length > 0) {
                            // List item with links
                            let currentX = indentX;
                            let lineY = currentY + 12;

                            for (const node of item.childNodes) {
                                if (node.nodeType === Node.TEXT_NODE) {
                                    const text = node.textContent.trim();
                                    if (text) {
                                        const textWidth = pdf.getTextWidth(text + ' ');
                                        if (currentX + textWidth > pdfWidth - margin && currentX > indentX) {
                                            lineY += 14;
                                            currentX = indentX;
                                            if (lineY > pdfHeight - margin) {
                                                pageNum++;
                                                pdf.addPage();
                                                lineY = margin + 12;
                                            }
                                        }
                                        pdf.setTextColor(0, 0, 0);
                                        pdf.text(text + ' ', currentX, lineY);
                                        currentX += textWidth;
                                    }
                                } else if (node.tagName === 'A') {
                                    const linkText = node.textContent;
                                    const linkUrl = node.href;
                                    const linkWidth = pdf.getTextWidth(linkText + ' ');

                                    if (currentX + linkWidth > pdfWidth - margin && currentX > indentX) {
                                        lineY += 14;
                                        currentX = indentX;
                                        if (lineY > pdfHeight - margin) {
                                            pageNum++;
                                            pdf.addPage();
                                            lineY = margin + 12;
                                        }
                                    }

                                    pdf.setTextColor(0, 102, 204);
                                    pdf.textWithLink(linkText + ' ', currentX, lineY, { url: linkUrl });
                                    pdf.setDrawColor(0, 102, 204);
                                    pdf.setLineWidth(0.3);
                                    pdf.line(currentX, lineY + 2, currentX + linkWidth - 5, lineY + 2);
                                    currentX += linkWidth;
                                }
                            }
                            currentY = lineY + 5;
                        } else {
                            // Plain text list item
                            const itemText = item.textContent.trim();
                            const textLines = pdf.splitTextToSize(itemText, contentWidth - 25);

                            pdf.text(textLines, indentX, currentY + 12);
                            currentY += (textLines.length * 14) + 5;
                        }
                    }
                    currentY += 5; // Add some space after the list
                }
                // Tables - render with proper cells
                else if (element.tagName === 'TABLE') {
                    const rows = Array.from(element.querySelectorAll('tr'));
                    if (rows.length === 0) continue;

                    // Check if we need multiple pages for wide tables
                    const headerCells = Array.from(rows[0].querySelectorAll('th, td'));
                    const colCount = headerCells.length || 1;

                    // For wide tables, use smaller font and narrower columns
                    let fontSize = 10;
                    let cellPadding = 5;
                    if (colCount > 4) {
                        fontSize = 8;
                        cellPadding = 3;
                    }

                    // Calculate column widths - make them proportional to content
                    const colWidths = [];
                    let totalWidth = contentWidth;

                    // For tables with many columns, allow some overflow
                    if (colCount > 3) {
                        totalWidth = Math.min(contentWidth, pdfWidth - 20);
                    }

                    const baseColWidth = totalWidth / colCount;
                    for (let i = 0; i < colCount; i++) {
                        colWidths.push(baseColWidth);
                    }

                    // Start table at current position
                    let tableY = currentY;

                    // Check if we need a new page
                    if (tableY + 20 > pdfHeight - margin) {
                        pageNum++;
                        pdf.addPage();
                        tableY = margin;
                    }

                    // Draw header background
                    pdf.setFillColor(0, 51, 102);
                    pdf.rect(margin - 10, tableY, totalWidth + 20, 25, 'F');

                    // Draw header text
                    pdf.setFont("helvetica", "bold");
                    pdf.setFontSize(fontSize);
                    pdf.setTextColor(255, 255, 255);

                    let headerHeight = 25;
                    headerCells.forEach((cell, index) => {
                        const text = cell.textContent.trim();
                        let x = margin;
                        for (let i = 0; i < index; i++) {
                            x += colWidths[i];
                        }

                        // Wrap text in cells
                        const cellText = pdf.splitTextToSize(text, colWidths[index] - 2 * cellPadding);
                        pdf.text(cellText, x + cellPadding, tableY + 15);

                        // Track max height needed
                        const cellHeight = cellText.length * (fontSize + 2) + 10;
                        headerHeight = Math.max(headerHeight, cellHeight);
                    });

                    // Draw horizontal line after header
                    pdf.setDrawColor(200, 200, 200);
                    pdf.setLineWidth(0.5);
                    pdf.line(margin - 10, tableY + headerHeight, margin + totalWidth + 10, tableY + headerHeight);

                    tableY += headerHeight;

                    // Draw table rows
                    pdf.setFont("helvetica", "normal");
                    pdf.setFontSize(fontSize);

                    for (let i = 1; i < rows.length; i++) {
                        // Get cells for this row
                        const cells = Array.from(rows[i].querySelectorAll('td, th'));

                        // Calculate row height based on content
                        let rowHeight = 20;
                        const cellContents = [];

                        cells.forEach((cell, index) => {
                            const text = cell.textContent.trim();
                            const wrappedText = pdf.splitTextToSize(text, colWidths[index] - 2 * cellPadding);
                            cellContents.push(wrappedText);
                            const cellHeight = wrappedText.length * (fontSize + 2) + 10;
                            rowHeight = Math.max(rowHeight, cellHeight);
                        });

                        // Check if we need a new page
                        if (tableY + rowHeight > pdfHeight - margin) {
                            pageNum++;
                            pdf.addPage();
                            tableY = margin;

                            // Redraw header on new page
                            pdf.setFillColor(0, 51, 102);
                            pdf.rect(margin - 10, tableY, totalWidth + 20, 25, 'F');

                            pdf.setFont("helvetica", "bold");
                            pdf.setTextColor(255, 255, 255);
                            // tableY is mutated by the surrounding loop but the forEach is
                            // synchronous, so no closure-over-loop-var hazard.
                            // eslint-disable-next-line no-loop-func
                            headerCells.forEach((cell, index) => {
                                const text = cell.textContent.trim();
                                let x = margin;
                                for (let j = 0; j < index; j++) {
                                    x += colWidths[j];
                                }
                                const cellText = pdf.splitTextToSize(text, colWidths[index] - 2 * cellPadding);
                                pdf.text(cellText, x + cellPadding, tableY + 15);
                            });

                            pdf.line(margin - 10, tableY + 25, margin + totalWidth + 10, tableY + 25);
                            tableY += 25;
                            pdf.setFont("helvetica", "normal");
                            pdf.setTextColor(0, 0, 0);
                        }

                        // Alternate row background
                        if (i % 2 === 0) {
                            pdf.setFillColor(245, 245, 245);
                            pdf.rect(margin - 10, tableY, totalWidth + 20, rowHeight, 'F');
                        }

                        // Draw cell content
                        pdf.setTextColor(0, 0, 0);
                        // Synchronous forEach; closure-over-tableY is benign.
                        // eslint-disable-next-line no-loop-func
                        cellContents.forEach((cellText, index) => {
                            let x = margin;
                            for (let j = 0; j < index; j++) {
                                x += colWidths[j];
                            }
                            pdf.text(cellText, x + cellPadding, tableY + 12);
                        });

                        // Draw horizontal line after row
                        pdf.setDrawColor(220, 220, 220);
                        pdf.line(margin - 10, tableY + rowHeight, margin + totalWidth + 10, tableY + rowHeight);
                        tableY += rowHeight;
                    }

                    // Draw vertical lines
                    pdf.setDrawColor(220, 220, 220);
                    let xPos = margin - 10;
                    for (let i = 0; i <= colCount; i++) {
                        pdf.line(xPos, currentY, xPos, tableY);
                        if (i < colCount) {
                            xPos += colWidths[i];
                        }
                    }

                    // Update current position to after the table
                    currentY = tableY + 15;
                }
                // Code blocks - render with monospace font and background
                else if (element.tagName === 'PRE' || element.querySelector('pre')) {
                    const preElement = element.tagName === 'PRE' ? element : element.querySelector('pre');
                    const codeText = preElement.textContent.trim();
                    if (!codeText) continue;

                    pdf.setFont("courier", "normal"); // Use monospace font for code
                    pdf.setFontSize(9);
                    pdf.setTextColor(0, 0, 0);

                    // Split code into lines, respecting line breaks
                    const codeLines = codeText.split(/\r?\n/);
                    const wrappedLines = [];

                    codeLines.forEach(line => {
                        // Wrap long lines
                        const wrappedLine = pdf.splitTextToSize(line, contentWidth - 20);
                        wrappedLines.push(...wrappedLine);
                    });

                    // Calculate code block height
                    const lineHeight = 12;
                    const codeHeight = wrappedLines.length * lineHeight + 20; // 10px padding top and bottom

                    // Check if we need a new page
                    if (currentY + codeHeight > pdfHeight - margin) {
                        pageNum++;
                        pdf.addPage();
                        currentY = margin;
                    }

                    // Draw code block background
                    pdf.setFillColor(245, 245, 245);
                    pdf.rect(margin, currentY, contentWidth, codeHeight, 'F');

                    // Draw code content
                    pdf.setTextColor(0, 0, 0);
                    // Synchronous forEach; closure-over-currentY is benign.
                    // eslint-disable-next-line no-loop-func
                    wrappedLines.forEach((line, index) => {
                        pdf.text(line, margin + 10, currentY + 15 + (index * lineHeight));
                    });

                    // Update position
                    currentY += codeHeight + 10;
                }
                // Images - render as images
                else if (element.tagName === 'IMG' || element.querySelector('img')) {
                    const imgElement = element.tagName === 'IMG' ? element : element.querySelector('img');

                    if (!imgElement || !imgElement.src) continue;

                    try {
                        // Create a new image to get dimensions
                        const img = new Image();
                        if (typeof URLValidator !== 'undefined' && URLValidator.safeAssign) {
                            URLValidator.safeAssign(img, 'src', imgElement.src);
                        } else {
                            img.src = imgElement.src;
                        }

                        // Calculate dimensions
                        const imgWidth = contentWidth;
                        const imgHeight = img.height * (contentWidth / img.width);

                        // Check if we need a new page
                        if (currentY + imgHeight > pdfHeight - margin) {
                            pageNum++;
                            pdf.addPage();
                            currentY = margin;
                        }

                        // Add image to PDF
                        pdf.addImage(img.src, 'JPEG', margin, currentY, imgWidth, imgHeight);
                        currentY += imgHeight + 10;
                    } catch (imgError) {
                        SafeLogger.error('Error adding image:', imgError);
                        pdf.text("[Image could not be rendered]", margin, currentY + 12);
                        currentY += 20;
                    }
                }
                // Fallback for moderately complex elements - try to extract text first before using canvas
                else {
                    // Try to extract text content and render it directly first
                    const textContent = element.textContent.trim();

                    if (textContent) {
                        pdf.setFont('helvetica', 'normal');
                        pdf.setFontSize(11);
                        pdf.setTextColor(0, 0, 0);

                        const textLines = pdf.splitTextToSize(textContent, contentWidth);

                        // Check if we need a new page
                        if (currentY + (textLines.length * 14) > pdfHeight - margin) {
                            pageNum++;
                            pdf.addPage();
                            currentY = margin;
                        }

                        pdf.text(textLines, margin, currentY + 12);
                        currentY += (textLines.length * 14) + 10;
                    } else {
                        // Only use html2canvas as a last resort for elements with no text content
                        try {
                            const canvas = await html2canvasLib(element, {
                                scale: 2,
                                useCORS: true,
                                logging: false,
                                backgroundColor: '#FFFFFF'
                            });

                            const imgData = canvas.toDataURL('image/png');
                            const imgWidth = contentWidth;
                            const imgHeight = (canvas.height * contentWidth) / canvas.width;

                            if (currentY + imgHeight > pdfHeight - margin) {
                                pageNum++;
                                pdf.addPage();
                                currentY = margin;
                            }

                            pdf.addImage(imgData, 'PNG', margin, currentY, imgWidth, imgHeight);
                            currentY += imgHeight + 10;
                        } catch {
                            pdf.text("[Content could not be rendered]", margin, currentY + 12);
                            currentY += 20;
                        }
                    }
                }
            } catch (elementError) {
                SafeLogger.error('Error processing element:', elementError);
                pdf.text("[Error rendering content]", margin, currentY + 12);
                currentY += 20;
            }
        }

        // Generate the PDF blob
        const blob = pdf.output('blob');
        return blob;
    } catch (error) {
        SafeLogger.error('Error in PDF generation:', error);
        throw error;
    } finally {
        // Clean up
        if (document.body.contains(tempContainer)) {
            document.body.removeChild(tempContainer);
        }
    }
}

/**
 * Generate and download a PDF from research content
 * @param {Object|string} titleOrData - Either the research title or the entire research data object
 * @param {string|null} content - The markdown content of the research, or research ID if first param is data object
 * @param {Object} metadata - Additional metadata for the PDF
 * @returns {Promise<void>} A promise that resolves when the PDF has been downloaded
 */
async function downloadPdf(titleOrData, content, metadata = {}) {
    try {

        let title, pdfContent, pdfMetadata;

        // Determine if we're being passed a research data object or direct parameters
        if (typeof titleOrData === 'object' && titleOrData !== null) {
            // We were passed a research data object
            const researchData = titleOrData;
            const researchId = content; // Second parameter is research ID in this case


            // Extract title from research data
            title = researchData.query || researchData.title || researchData.prompt || `Research ${researchId}`;

            // Extract content - try all possible locations based on how data might be structured
            if (researchData.markdown) {
                pdfContent = researchData.markdown;
            } else if (researchData.content) {
                pdfContent = researchData.content;
            } else if (researchData.text) {
                pdfContent = researchData.text;
            } else if (researchData.summary) {
                pdfContent = researchData.summary;
            } else if (researchData.results && Array.isArray(researchData.results)) {
                pdfContent = researchData.results.join('\n\n');
            } else if (researchData.report) {
                pdfContent = researchData.report;
            } else if (researchData.research && researchData.research.content) {
                pdfContent = researchData.research.content;
            } else if (researchData.html) {
                // If we have HTML, convert it to a reasonable markdown-like format
                pdfContent = researchData.html
                    .replace(/<h1[^>]*>(.*?)<\/h1>/gi, '# $1\n\n')
                    .replace(/<h2[^>]*>(.*?)<\/h2>/gi, '## $1\n\n')
                    .replace(/<h3[^>]*>(.*?)<\/h3>/gi, '### $1\n\n')
                    .replace(/<p[^>]*>(.*?)<\/p>/gi, '$1\n\n')
                    .replace(/<li[^>]*>(.*?)<\/li>/gi, '- $1\n')
                    .replace(/<br\s*\/?>/gi, '\n');

                // Use DOM-based HTML stripping for security (prevents XSS)
                const tempDiv = document.createElement('div');
                // bearer:disable javascript_lang_dangerous_insert_html
                // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: detached div used only for textContent extraction, never rendered
                tempDiv.innerHTML = pdfContent;
                pdfContent = (tempDiv.textContent || tempDiv.innerText || '')
                    .replace(/&nbsp;/gi, ' ')
                    .replace(/&lt;/gi, '<')
                    .replace(/&gt;/gi, '>')
                    .replace(/&quot;/gi, '"')
                    .replace(/&apos;/gi, "'")
                    .replace(/&amp;/gi, '&')  // MUST be last to prevent double-unescaping
                    .replace(/\n{3,}/g, '\n\n'); // Normalize excessive newlines
            } else {
                // Last resort: stringify the entire object
                pdfContent = JSON.stringify(researchData, null, 2);
            }


            // Extract metadata
            pdfMetadata = {
                mode: researchData.mode,
                iterations: researchData.iterations,
                timestamp: researchData.timestamp || researchData.created_at || new Date().toISOString(),
                id: researchId
            };
        } else {
            // We were passed direct parameters
            title = titleOrData || 'Research Report';
            pdfContent = content || '';
            pdfMetadata = metadata || {};
        }

        // Ensure title is a string
        title = String(title || 'Research Report');


        // Show loading indicator
        const loadingIndicator = document.createElement('div');
        loadingIndicator.className = 'ldr-loading-indicator';
        loadingIndicator.innerHTML = '<div class="ldr-spinner"></div><div>Generating PDF...</div>';
        loadingIndicator.style.position = 'fixed';
        loadingIndicator.style.top = '50%';
        loadingIndicator.style.left = '50%';
        loadingIndicator.style.transform = 'translate(-50%, -50%)';
        loadingIndicator.style.zIndex = '9999';
        loadingIndicator.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
        loadingIndicator.style.color = 'white';
        loadingIndicator.style.padding = '20px';
        loadingIndicator.style.borderRadius = '5px';
        document.body.appendChild(loadingIndicator);

        // Generate the PDF
        const blob = await generatePdf(title, pdfContent, pdfMetadata);

        // Create a download link
        const url = URL.createObjectURL(blob);
        const downloadLink = document.createElement('a');
        if (typeof URLValidator !== 'undefined' && URLValidator.safeAssign) {
            URLValidator.safeAssign(downloadLink, 'href', url);
        } else {
            downloadLink.href = url;
        }

        // Generate a filename based on the title
        const safeTitle = title.replace(/[^a-z0-9]/gi, '_').toLowerCase().substring(0, 30);
        downloadLink.download = `${safeTitle}_research.pdf`;

        // Trigger the download
        document.body.appendChild(downloadLink);
        downloadLink.click();

        // Clean up
        document.body.removeChild(downloadLink);
        URL.revokeObjectURL(url);

        return true;
    } catch (error) {
        SafeLogger.error('Error generating PDF:', error);
        alert('Error generating PDF: ' + (error.message || 'Unknown error'));
        throw error;
    } finally {
        // Remove loading indicator
        const loadingIndicator = document.querySelector('.ldr-loading-indicator');
        if (loadingIndicator) {
            document.body.removeChild(loadingIndicator);
        }
    }
}

// Export PDF functions
window.pdfService = {
    generatePdf,
    downloadPdf,
    replaceKatexWithLatex
};
