/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
import { Hono } from 'hono';
const MARKDOWN_CSS = `
  body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    padding: 1em;
    max-width: 800px;
    margin: 0 auto;
    color: #1a1a1a;
    line-height: 1.6;
  }
  h1, h2, h3, h4, h5, h6 { margin-top: 1.2em; margin-bottom: 0.4em; }
  pre { background: #f5f5f5; padding: 1em; border-radius: 4px; overflow-x: auto; }
  code { background: #f5f5f5; padding: 0.2em 0.4em; border-radius: 3px; font-size: 0.9em; }
  pre code { background: none; padding: 0; }
  table { border-collapse: collapse; width: 100%; }
  th, td { border: 1px solid #ddd; padding: 0.5em; text-align: left; }
  th { background: #f5f5f5; }
  blockquote { border-left: 3px solid #ddd; margin-left: 0; padding-left: 1em; color: #555; }
`;
function wrapMarkdownAsHtml(markdown) {
    // Basic markdown→HTML: wrap in HTML page with stylesheet.
    // The browser engine in puppeteer will render plain text; for richer rendering
    // we do minimal transformations here.
    const escaped = markdown.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    // Convert markdown headings, bold, italic, code blocks
    let html = escaped
        .replace(/^######\s+(.+)$/gm, '<h6>$1</h6>')
        .replace(/^#####\s+(.+)$/gm, '<h5>$1</h5>')
        .replace(/^####\s+(.+)$/gm, '<h4>$1</h4>')
        .replace(/^###\s+(.+)$/gm, '<h3>$1</h3>')
        .replace(/^##\s+(.+)$/gm, '<h2>$1</h2>')
        .replace(/^#\s+(.+)$/gm, '<h1>$1</h1>')
        .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
        .replace(/\*(.+?)\*/g, '<em>$1</em>')
        .replace(/`([^`]+)`/g, '<code>$1</code>')
        .replace(/\n{2,}/g, '</p><p>')
        .replace(/\n/g, '<br/>');
    html = `<p>${html}</p>`;
    return `<!DOCTYPE html>
<html><head><meta charset="utf-8"><style>${MARKDOWN_CSS}</style></head>
<body>${html}</body></html>`;
}
function wrapSvgAsHtml(svg) {
    return `<!DOCTYPE html>
<html><head><meta charset="utf-8"><style>
  body { margin: 0; padding: 0; display: flex; align-items: center; justify-content: center; }
  svg { max-width: 100%; height: auto; }
</style></head>
<body>${svg}</body></html>`;
}
/**
 * Escape a field value per RFC 4180.
 * Fields containing commas, double-quotes, or newlines are wrapped in double-quotes.
 * Any embedded double-quotes are doubled.
 */
function escapeCsvField(value) {
    if (value.includes(',') || value.includes('"') || value.includes('\n') || value.includes('\r')) {
        return `"${value.replace(/"/g, '""')}"`;
    }
    return value;
}
/**
 * Parse inline markdown formatting (bold, code) into docx TextRun instances.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function parseInlineFormatting(TextRun, text) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const runs = [];
    // Match **bold**, `code`, and plain text segments
    const regex = /(\*\*(.+?)\*\*|`([^`]+)`|([^*`]+))/g;
    let match;
    while ((match = regex.exec(text)) !== null) {
        if (match[2]) {
            // Bold
            runs.push(new TextRun({ text: match[2], bold: true }));
        }
        else if (match[3]) {
            // Code (monospace)
            runs.push(new TextRun({ text: match[3], font: 'Courier New' }));
        }
        else if (match[4]) {
            // Plain text
            runs.push(new TextRun({ text: match[4] }));
        }
    }
    if (runs.length === 0) {
        runs.push(new TextRun({ text }));
    }
    return runs;
}
export function exportRoute() {
    const app = new Hono();
    app.post('/pdf', async (c) => {
        const body = await c.req.json();
        if (!body.content || !body.type) {
            return c.json({ error: 'Missing required fields: content, type' }, 400);
        }
        if (!['svg', 'html', 'markdown'].includes(body.type)) {
            return c.json({ error: 'Invalid type. Must be svg, html, or markdown' }, 400);
        }
        // Dynamic import — puppeteer bundles its own Chromium.
        // Use a variable to prevent TS from resolving the module at compile time.
        const puppeteerPkg = 'puppeteer';
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let puppeteerMod;
        try {
            puppeteerMod = await import(puppeteerPkg);
        }
        catch {
            return c.json({ error: 'Puppeteer is not available. Install it with: npm install puppeteer' }, 503);
        }
        const launch = puppeteerMod.default?.launch ?? puppeteerMod.launch;
        if (!launch) {
            return c.json({ error: 'Puppeteer module loaded but launch() not found' }, 503);
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let browser;
        try {
            browser = await launch({
                headless: true,
                args: ['--no-sandbox', '--disable-setuid-sandbox'],
            });
            const page = await browser.newPage();
            let htmlContent;
            switch (body.type) {
                case 'svg':
                    htmlContent = wrapSvgAsHtml(body.content);
                    break;
                case 'markdown':
                    htmlContent = wrapMarkdownAsHtml(body.content);
                    break;
                case 'html':
                    htmlContent = body.content;
                    break;
            }
            await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
            // For SVGs, try to match viewport to content dimensions
            if (body.type === 'svg') {
                // The evaluate callback runs in the browser context (has access to DOM)
                const dimensions = await page.evaluate('(() => { const svg = document.querySelector("svg"); if (svg) { const b = svg.getBoundingClientRect(); return { width: Math.ceil(b.width), height: Math.ceil(b.height) }; } return null; })()');
                if (dimensions) {
                    await page.setViewport({
                        width: Math.max(dimensions.width + 40, 800),
                        height: Math.max(dimensions.height + 40, 600),
                    });
                    // Re-set content after viewport change for proper layout
                    await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
                }
            }
            const pdfBuffer = await page.pdf({
                format: 'A4',
                printBackground: true,
                margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
            });
            const fileName = body.fileName ?? 'export';
            const safeFileName = fileName.replace(/[^a-zA-Z0-9_-]/g, '_');
            return new Response(pdfBuffer, {
                headers: {
                    'Content-Type': 'application/pdf',
                    'Content-Disposition': `attachment; filename="${safeFileName}.pdf"`,
                },
            });
        }
        catch (err) {
            const message = err instanceof Error ? err.message : 'PDF generation failed';
            return c.json({ error: message }, 500);
        }
        finally {
            if (browser) {
                await browser.close();
            }
        }
    });
    app.post('/docx', async (c) => {
        const body = await c.req.json();
        if (!body.content || !body.type) {
            return c.json({ error: 'Missing required fields: content, type' }, 400);
        }
        if (!['markdown', 'html'].includes(body.type)) {
            return c.json({ error: 'Invalid type. Must be markdown or html' }, 400);
        }
        // Dynamic import to avoid bundling docx at compile time
        const docxPkg = 'docx';
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let docxMod;
        try {
            docxMod = await import(docxPkg);
        }
        catch {
            return c.json({ error: 'docx package is not available. Install it with: npm install docx' }, 503);
        }
        const { Document, Packer, Paragraph, TextRun, HeadingLevel } = docxMod;
        try {
            const lines = body.content.split('\n');
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const children = [];
            for (const line of lines) {
                // Headings
                const h1Match = /^#\s+(.+)$/.exec(line);
                const h2Match = /^##\s+(.+)$/.exec(line);
                const h3Match = /^###\s+(.+)$/.exec(line);
                const h4Match = /^####\s+(.+)$/.exec(line);
                const h5Match = /^#####\s+(.+)$/.exec(line);
                const h6Match = /^######\s+(.+)$/.exec(line);
                const bulletMatch = /^\s*[-*]\s+(.+)$/.exec(line);
                const codeBlockMatch = /^```/.exec(line);
                if (h6Match) {
                    children.push(new Paragraph({ text: h6Match[1], heading: HeadingLevel.HEADING_6 }));
                }
                else if (h5Match) {
                    children.push(new Paragraph({ text: h5Match[1], heading: HeadingLevel.HEADING_5 }));
                }
                else if (h4Match) {
                    children.push(new Paragraph({ text: h4Match[1], heading: HeadingLevel.HEADING_4 }));
                }
                else if (h3Match) {
                    children.push(new Paragraph({ text: h3Match[1], heading: HeadingLevel.HEADING_3 }));
                }
                else if (h2Match) {
                    children.push(new Paragraph({ text: h2Match[1], heading: HeadingLevel.HEADING_2 }));
                }
                else if (h1Match) {
                    children.push(new Paragraph({ text: h1Match[1], heading: HeadingLevel.HEADING_1 }));
                }
                else if (bulletMatch) {
                    children.push(new Paragraph({
                        children: parseInlineFormatting(TextRun, bulletMatch[1]),
                        bullet: { level: 0 },
                    }));
                }
                else if (codeBlockMatch) {
                    // Skip code fence markers — content lines handled as monospace
                    continue;
                }
                else if (line.trim() === '') {
                    children.push(new Paragraph({ text: '' }));
                }
                else {
                    children.push(new Paragraph({ children: parseInlineFormatting(TextRun, line) }));
                }
            }
            const doc = new Document({
                sections: [{ children }],
            });
            const buffer = await Packer.toBuffer(doc);
            const fileName = body.fileName ?? 'export';
            const safeFileName = fileName.replace(/[^a-zA-Z0-9_-]/g, '_');
            return new Response(buffer, {
                headers: {
                    'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                    'Content-Disposition': `attachment; filename="${safeFileName}.docx"`,
                },
            });
        }
        catch (err) {
            const message = err instanceof Error ? err.message : 'DOCX generation failed';
            return c.json({ error: message }, 500);
        }
    });
    app.post('/xlsx', async (c) => {
        const body = await c.req.json();
        if (!body.content || !body.type) {
            return c.json({ error: 'Missing required fields: content, type' }, 400);
        }
        if (body.type !== 'json') {
            return c.json({ error: 'Invalid type. Must be json' }, 400);
        }
        // Dynamic import to avoid bundling xlsx at compile time
        const xlsxPkg = 'xlsx';
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let xlsxMod;
        try {
            xlsxMod = await import(xlsxPkg);
        }
        catch {
            return c.json({ error: 'xlsx package is not available. Install it with: npm install xlsx' }, 503);
        }
        try {
            const parsed = JSON.parse(body.content);
            const workbook = xlsxMod.utils.book_new();
            if (Array.isArray(parsed)) {
                // Flat array of objects → single sheet
                const ws = xlsxMod.utils.json_to_sheet(parsed);
                xlsxMod.utils.book_append_sheet(workbook, ws, 'Sheet1');
            }
            else if (parsed && typeof parsed === 'object') {
                // Object with nested arrays → one sheet per key
                const obj = parsed;
                for (const [key, value] of Object.entries(obj)) {
                    if (Array.isArray(value)) {
                        const ws = xlsxMod.utils.json_to_sheet(value);
                        // Sheet names max 31 chars
                        const sheetName = key.slice(0, 31);
                        xlsxMod.utils.book_append_sheet(workbook, ws, sheetName);
                    }
                }
                // If no arrays were found, treat the entire object as a single row
                if (workbook.SheetNames.length === 0) {
                    const ws = xlsxMod.utils.json_to_sheet([obj]);
                    xlsxMod.utils.book_append_sheet(workbook, ws, 'Sheet1');
                }
            }
            else {
                return c.json({ error: 'Content must be a JSON object or array' }, 400);
            }
            const xlsxBuffer = xlsxMod.write(workbook, { type: 'buffer', bookType: 'xlsx' });
            const fileName = body.fileName ?? 'export';
            const safeFileName = fileName.replace(/[^a-zA-Z0-9_-]/g, '_');
            return new Response(xlsxBuffer, {
                headers: {
                    'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                    'Content-Disposition': `attachment; filename="${safeFileName}.xlsx"`,
                },
            });
        }
        catch (err) {
            const message = err instanceof Error ? err.message : 'XLSX generation failed';
            if (message.includes('JSON')) {
                return c.json({ error: `Invalid JSON content: ${message}` }, 400);
            }
            return c.json({ error: message }, 500);
        }
    });
    app.post('/csv', async (c) => {
        const body = await c.req.json();
        if (!body.content || !body.type) {
            return c.json({ error: 'Missing required fields: content, type' }, 400);
        }
        if (body.type !== 'json') {
            return c.json({ error: 'Invalid type. Must be json' }, 400);
        }
        try {
            const parsed = JSON.parse(body.content);
            if (!Array.isArray(parsed)) {
                return c.json({ error: 'CSV export requires a flat JSON array of objects' }, 400);
            }
            if (parsed.length === 0) {
                return new Response('', {
                    headers: {
                        'Content-Type': 'text/csv',
                        'Content-Disposition': 'attachment; filename="export.csv"',
                    },
                });
            }
            // Validate all items are flat objects
            for (const item of parsed) {
                if (!item || typeof item !== 'object' || Array.isArray(item)) {
                    return c.json({ error: 'CSV export requires a flat JSON array of objects' }, 400);
                }
            }
            const headers = Object.keys(parsed[0]);
            const rows = [headers.map(escapeCsvField).join(',')];
            for (const item of parsed) {
                const obj = item;
                const row = headers.map((h) => escapeCsvField(String(obj[h] ?? '')));
                rows.push(row.join(','));
            }
            const csvContent = rows.join('\r\n');
            const fileName = body.fileName ?? 'export';
            const safeFileName = fileName.replace(/[^a-zA-Z0-9_-]/g, '_');
            return new Response(csvContent, {
                headers: {
                    'Content-Type': 'text/csv',
                    'Content-Disposition': `attachment; filename="${safeFileName}.csv"`,
                },
            });
        }
        catch (err) {
            const message = err instanceof Error ? err.message : 'CSV generation failed';
            if (message.includes('JSON')) {
                return c.json({ error: `Invalid JSON content: ${message}` }, 400);
            }
            return c.json({ error: message }, 500);
        }
    });
    return app;
}
//# sourceMappingURL=export.js.map