// ==UserScript==
// @name         Numbeo Cost of Living → PDF Exporter
// @namespace    https://www.numbeo.com/
// @version      1.0.0
// @description  Export the Numbeo Cost of Living Index by Country table to a clean, well-organised PDF with selectable text
// @author       You
// @match        https://www.numbeo.com/cost-of-living/rankings_by_country.jsp*
// @match        https://www.numbeo.com/cost-of-living/rankings_current.jsp*
// @match        https://www.numbeo.com/cost-of-living/rankings.jsp*
// @grant        GM_addStyle
// @require      https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js
// ==/UserScript==

(function () {
    'use strict';

    /* ───────────────────────────────── STYLES ───────────────────────────────── */
    GM_addStyle(`
        #numbeo-pdf-btn {
            position: fixed;
            bottom: 28px;
            right: 28px;
            z-index: 99999;
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 12px 20px;
            background: linear-gradient(135deg, #1B3B6F 0%, #2563a8 100%);
            color: #fff;
            font-family: Arial, sans-serif;
            font-size: 14px;
            font-weight: 600;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            box-shadow: 0 4px 14px rgba(27,59,111,.45);
            transition: transform .15s, box-shadow .15s, opacity .15s;
            letter-spacing: .3px;
        }
        #numbeo-pdf-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 7px 20px rgba(27,59,111,.55);
        }
        #numbeo-pdf-btn:active {
            transform: translateY(0);
        }
        #numbeo-pdf-btn svg {
            flex-shrink: 0;
        }
        #numbeo-pdf-btn.loading {
            opacity: .7;
            cursor: wait;
            pointer-events: none;
        }
        #numbeo-pdf-toast {
            position: fixed;
            bottom: 90px;
            right: 28px;
            z-index: 99999;
            padding: 10px 18px;
            background: #22c55e;
            color: #fff;
            font-family: Arial, sans-serif;
            font-size: 13px;
            font-weight: 600;
            border-radius: 7px;
            box-shadow: 0 4px 12px rgba(0,0,0,.2);
            opacity: 0;
            transition: opacity .3s;
            pointer-events: none;
        }
    `);

    /* ──────────────────────────── BUTTON INJECTION ──────────────────────────── */
    const btn = document.createElement('button');
    btn.id = 'numbeo-pdf-btn';
    btn.innerHTML = `
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"
             stroke-linecap="round" stroke-linejoin="round">
            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
            <polyline points="14 2 14 8 20 8"/>
            <line x1="16" y1="13" x2="8" y2="13"/>
            <line x1="16" y1="17" x2="8" y2="17"/>
            <polyline points="10 9 9 9 8 9"/>
        </svg>
        Export to PDF
    `;
    btn.title = 'Export this table to a clean PDF with selectable text';
    document.body.appendChild(btn);

    const toast = document.createElement('div');
    toast.id = 'numbeo-pdf-toast';
    toast.textContent = '✓ PDF downloaded successfully!';
    document.body.appendChild(toast);

    /* ──────────────────────────── HELPER FUNCTIONS ──────────────────────────── */

    /**
     * Scrape the visible (post-search, post-sort) DataTable rows.
     * Falls back to raw <tr> elements if DataTables API is unavailable.
     */
    function scrapeTableData() {
        const table = document.getElementById('t2');
        if (!table) return null;

        // Column headers
        const headers = [];
        table.querySelectorAll('thead th').forEach(th => {
            const text = th.innerText.replace(/\s+/g, ' ').trim();
            headers.push(text);
        });

        // Rows — use the rendered DOM (reflects current sort/search)
        const rows = [];
        table.querySelectorAll('tbody tr').forEach(tr => {
            const cells = [];
            tr.querySelectorAll('td').forEach(td => {
                cells.push(td.innerText.replace(/\s+/g, ' ').trim());
            });
            if (cells.length) rows.push(cells);
        });

        return { headers, rows };
    }

    /** Derive the page/report title from the <h1> or document title */
    function getPageTitle() {
        const h1 = document.querySelector('h1');
        if (h1) return h1.innerText.trim();
        return document.title.trim() || 'Numbeo Cost of Living';
    }

    /** Show a brief success toast */
    function showToast() {
        toast.style.opacity = '1';
        setTimeout(() => { toast.style.opacity = '0'; }, 2800);
    }

    /* ─────────────────────────────── PDF BUILD ─────────────────────────────── */
    function buildPDF() {
        btn.classList.add('loading');
        btn.textContent = ' Generating…';

        // Small timeout so the UI can repaint before the heavy work begins
        setTimeout(() => {
            try {
                const data = scrapeTableData();
                if (!data || !data.rows.length) {
                    alert('Could not find the data table on this page.');
                    btn.classList.remove('loading');
                    btn.innerHTML = `
                        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                             stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
                            <polyline points="14 2 14 8 20 8"/>
                            <line x1="16" y1="13" x2="8" y2="13"/>
                            <line x1="16" y1="17" x2="8" y2="17"/>
                            <polyline points="10 9 9 9 8 9"/>
                        </svg>
                        Export to PDF`;
                    return;
                }

                const { headers, rows } = data;
                const title = getPageTitle();
                const today = new Date().toLocaleDateString('en-US', {
                    year: 'numeric', month: 'long', day: 'numeric'
                });
                const totalCountries = rows.length;

                /* ── jsPDF setup (A4 landscape for wide tables) ── */
                const { jsPDF } = window.jspdf;
                const doc = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' });
                const PW = doc.internal.pageSize.getWidth();   // 297
                const PH = doc.internal.pageSize.getHeight();  // 210

                /* ── Brand colour palette ── */
                const NAVY   = [27,  59, 111];
                const BLUE   = [37,  99, 168];
                const LGREY  = [245, 247, 250];
                const MGREY  = [220, 225, 232];
                const DGREY  = [80,  90, 105];
                const WHITE  = [255, 255, 255];
                const GOLD   = [210, 160,  40];

                /* ───────── PAGE RENDERER (header + footer on every page) ───────── */
                const totalPages = () => doc.internal.getNumberOfPages();

                function drawPageChrome(pageNum) {
                    /* Header bar */
                    doc.setFillColor(...NAVY);
                    doc.rect(0, 0, PW, 18, 'F');

                    /* Numbeo logo text */
                    doc.setFont('helvetica', 'bold');
                    doc.setFontSize(13);
                    doc.setTextColor(...WHITE);
                    doc.text('NUMBEO', 10, 12);

                    /* Report title centred */
                    doc.setFontSize(9);
                    doc.setFont('helvetica', 'normal');
                    doc.setTextColor(...MGREY);
                    doc.text(title, PW / 2, 12, { align: 'center' });

                    /* Date right-aligned */
                    doc.setFontSize(8);
                    doc.text(today, PW - 10, 12, { align: 'right' });

                    /* Footer */
                    doc.setFillColor(...LGREY);
                    doc.rect(0, PH - 10, PW, 10, 'F');

                    doc.setFontSize(7.5);
                    doc.setTextColor(...DGREY);
                    doc.text('Source: numbeo.com', 10, PH - 4);
                    doc.text(`Page ${pageNum} of ${totalPages()}`, PW / 2, PH - 4, { align: 'center' });
                    doc.text(`${totalCountries} countries`, PW - 10, PH - 4, { align: 'right' });

                    /* Thin accent line under header */
                    doc.setDrawColor(...GOLD);
                    doc.setLineWidth(0.6);
                    doc.line(0, 18, PW, 18);
                }

                /* ───────── COVER PAGE ───────── */
                // Background gradient-like fill
                doc.setFillColor(...NAVY);
                doc.rect(0, 0, PW, PH, 'F');

                // Decorative diagonal stripe
                doc.setFillColor(37, 80, 140);
                doc.triangle(0, PH * 0.55, PW, PH * 0.35, PW, PH, 'F');

                // Title
                doc.setFont('helvetica', 'bold');
                doc.setFontSize(26);
                doc.setTextColor(...WHITE);
                doc.text(title, PW / 2, PH * 0.32, { align: 'center', maxWidth: PW - 40 });

                // Subtitle bar
                doc.setFillColor(...GOLD);
                doc.rect(PW / 2 - 40, PH * 0.42, 80, 1.2, 'F');

                // Meta info
                doc.setFont('helvetica', 'normal');
                doc.setFontSize(12);
                doc.setTextColor(...MGREY);
                doc.text(`${totalCountries} Countries Ranked`, PW / 2, PH * 0.50, { align: 'center' });

                doc.setFontSize(10);
                doc.setTextColor(180, 200, 230);
                doc.text(`Generated on ${today}`, PW / 2, PH * 0.57, { align: 'center' });
                doc.text('Source: www.numbeo.com', PW / 2, PH * 0.63, { align: 'center' });

                // Indices legend box
                const legendX = PW / 2 - 70;
                const legendY = PH * 0.70;
                doc.setFillColor(37, 65, 120, 0.6);
                doc.roundedRect(legendX, legendY, 140, 28, 3, 3, 'F');
                doc.setFont('helvetica', 'bold');
                doc.setFontSize(8);
                doc.setTextColor(180, 210, 255);
                doc.text('INDEX KEY  (New York City = 100)', PW / 2, legendY + 7, { align: 'center' });
                doc.setFont('helvetica', 'normal');
                doc.setFontSize(7);
                doc.setTextColor(200, 220, 240);
                const legend = [
                    'Cost of Living — General price level vs NYC',
                    'Rent — Rental prices vs NYC',
                    'Groceries — Grocery prices vs NYC',
                    'Restaurant Prices — Dining-out costs vs NYC',
                    'Local Purchasing Power — Relative income buying power',
                ];
                legend.forEach((l, i) => {
                    doc.text(`• ${l}`, legendX + 8, legendY + 14 + i * 4.2);
                });

                /* ───────── DATA TABLE PAGES ───────── */
                doc.addPage();

                // Determine column widths based on header count
                const colCount = headers.length;
                // Rank + Country wide, rest numeric
                const colWidths = headers.map((h, i) => {
                    if (i === 0) return 14;   // Rank
                    if (i === 1) return 52;   // Country
                    return (PW - 14 - 52 - 20) / (colCount - 2); // numeric cols share remaining
                });

                // Column styles
                const columnStyles = {};
                headers.forEach((_, i) => {
                    if (i === 0) columnStyles[i] = { halign: 'center', cellWidth: colWidths[i] };
                    else if (i === 1) columnStyles[i] = { halign: 'left', cellWidth: colWidths[i] };
                    else columnStyles[i] = { halign: 'right', cellWidth: colWidths[i] };
                });

                // Colour-code the Cost of Living Index column (col 2) with a subtle gradient
                const coliIndex = 2; // 0-based col index for CoL value
                const allCoLValues = rows.map(r => parseFloat(r[coliIndex]) || 0);
                const maxCoL = Math.max(...allCoLValues);
                const minCoL = Math.min(...allCoLValues);

                function heatColour(value) {
                    // Low = green tint, high = red tint
                    const t = (value - minCoL) / (maxCoL - minCoL);
                    const r = Math.round(220 * t + 200 * (1 - t));
                    const g = Math.round(80  * t + 230 * (1 - t));
                    const b = Math.round(80  * t + 200 * (1 - t));
                    return [r, g, b];
                }

                doc.autoTable({
                    head: [headers],
                    body: rows,
                    startY: 22,
                    margin: { top: 22, right: 10, bottom: 14, left: 10 },
                    tableWidth: 'auto',
                    columnStyles,
                    styles: {
                        font: 'helvetica',
                        fontSize: 7.8,
                        cellPadding: { top: 2.8, bottom: 2.8, left: 3, right: 3 },
                        overflow: 'ellipsize',
                        valign: 'middle',
                        lineColor: MGREY,
                        lineWidth: 0.15,
                    },
                    headStyles: {
                        fillColor: NAVY,
                        textColor: WHITE,
                        fontStyle: 'bold',
                        fontSize: 7.5,
                        halign: 'center',
                        lineWidth: 0,
                    },
                    alternateRowStyles: {
                        fillColor: LGREY,
                    },
                    bodyStyles: {
                        fillColor: WHITE,
                        textColor: [30, 35, 45],
                    },
                    // Colour-highlight the CoL column per row
                    didParseCell(data) {
                        if (data.section === 'body' && data.column.index === coliIndex) {
                            const val = parseFloat(data.cell.raw) || 0;
                            const [r, g, b] = heatColour(val);
                            data.cell.styles.fillColor = [r, g, b];
                            data.cell.styles.textColor = val > (maxCoL * 0.6) ? WHITE : [20, 20, 20];
                            data.cell.styles.fontStyle = 'bold';
                        }
                        // Bold country names
                        if (data.section === 'body' && data.column.index === 1) {
                            data.cell.styles.fontStyle = 'bold';
                            data.cell.styles.textColor = NAVY;
                        }
                        // Rank column style
                        if (data.section === 'body' && data.column.index === 0) {
                            data.cell.styles.textColor = DGREY;
                            data.cell.styles.fontSize = 7;
                        }
                    },
                    // Draw header/footer on every page
                    didDrawPage(hookData) {
                        drawPageChrome(hookData.pageNumber);
                    },
                    // Prevent orphan header on a page with no body
                    showHead: 'everyPage',
                });

                /* ───────── SUMMARY / STATISTICS PAGE ───────── */
                doc.addPage();
                drawPageChrome(doc.internal.getCurrentPageInfo().pageNumber);

                const statsY = 26;
                doc.setFont('helvetica', 'bold');
                doc.setFontSize(14);
                doc.setTextColor(...NAVY);
                doc.text('Summary Statistics', 14, statsY);

                doc.setDrawColor(...GOLD);
                doc.setLineWidth(0.8);
                doc.line(14, statsY + 2, 120, statsY + 2);

                // Compute stats
                function colStats(colIdx) {
                    const vals = rows.map(r => parseFloat(r[colIdx])).filter(v => !isNaN(v));
                    vals.sort((a, b) => a - b);
                    const sum = vals.reduce((a, b) => a + b, 0);
                    const mean = sum / vals.length;
                    const median = vals.length % 2 === 0
                        ? (vals[vals.length / 2 - 1] + vals[vals.length / 2]) / 2
                        : vals[Math.floor(vals.length / 2)];
                    return {
                        min: vals[0].toFixed(2),
                        max: vals[vals.length - 1].toFixed(2),
                        mean: mean.toFixed(2),
                        median: median.toFixed(2),
                    };
                }

                const numericCols = headers.slice(2); // skip Rank and Country
                const statsData = numericCols.map((h, i) => {
                    const s = colStats(i + 2);
                    return [h, s.min, s.max, s.mean, s.median];
                });

                doc.autoTable({
                    head: [['Index', 'Min', 'Max', 'Mean', 'Median']],
                    body: statsData,
                    startY: statsY + 8,
                    margin: { left: 14, right: 14 },
                    styles: { fontSize: 9, cellPadding: 3.5, valign: 'middle', lineColor: MGREY, lineWidth: 0.15 },
                    headStyles: { fillColor: BLUE, textColor: WHITE, fontStyle: 'bold', halign: 'center' },
                    columnStyles: {
                        0: { halign: 'left', fontStyle: 'bold', textColor: NAVY },
                        1: { halign: 'right' },
                        2: { halign: 'right' },
                        3: { halign: 'right' },
                        4: { halign: 'right' },
                    },
                    alternateRowStyles: { fillColor: LGREY },
                    bodyStyles: { fillColor: WHITE },
                    didDrawPage(hookData) {
                        drawPageChrome(hookData.pageNumber);
                    },
                });

                // Top 10 + Bottom 10 tables
                const afterStatsY = doc.lastAutoTable.finalY + 10;

                function miniTable(title2, subset, startX, startY, tableWidth) {
                    doc.setFont('helvetica', 'bold');
                    doc.setFontSize(10);
                    doc.setTextColor(...NAVY);
                    doc.text(title2, startX, startY);

                    doc.autoTable({
                        head: [['#', 'Country', 'CoL Index']],
                        body: subset,
                        startY: startY + 4,
                        margin: { left: startX, right: PW - startX - tableWidth },
                        tableWidth,
                        styles: { fontSize: 8, cellPadding: 2.5, valign: 'middle', lineColor: MGREY, lineWidth: 0.15 },
                        headStyles: { fillColor: NAVY, textColor: WHITE, fontStyle: 'bold', fontSize: 7.5 },
                        columnStyles: {
                            0: { halign: 'center', cellWidth: 12 },
                            1: { halign: 'left', fontStyle: 'bold', textColor: NAVY },
                            2: { halign: 'right', fontStyle: 'bold' },
                        },
                        alternateRowStyles: { fillColor: LGREY },
                        bodyStyles: { fillColor: WHITE },
                        // No didDrawPage here — page chrome already drawn
                    });
                }

                // Sort rows by CoL index descending for top 10
                const sortedDesc = [...rows].sort((a, b) => (parseFloat(b[2]) || 0) - (parseFloat(a[2]) || 0));
                const top10 = sortedDesc.slice(0, 10).map((r, i) => [i + 1, r[1], r[2]]);
                const bottom10 = sortedDesc.slice(-10).reverse().map((r, i) => [i + 1, r[1], r[2]]);

                const colW = (PW - 28 - 10) / 2;
                miniTable('🏆  Top 10 Most Expensive Countries',    top10,    14, afterStatsY, colW);
                miniTable('💰  Top 10 Most Affordable Countries',   bottom10, 14 + colW + 10, afterStatsY, colW);

                /* ───────── UPDATE PAGE NUMBERS (now we know total) ───────── */
                // Re-draw chrome on all pages with correct total
                const nPages = doc.internal.getNumberOfPages();
                for (let p = 1; p <= nPages; p++) {
                    doc.setPage(p);
                    // Only re-draw footer text (don't re-paint entire chrome to avoid artefacts)
                    // Clear old footer area
                    doc.setFillColor(...LGREY);
                    doc.rect(0, PH - 10, PW, 10, 'F');

                    doc.setFontSize(7.5);
                    doc.setTextColor(...DGREY);
                    doc.text('Source: numbeo.com', 10, PH - 4);
                    doc.text(`Page ${p} of ${nPages}`, PW / 2, PH - 4, { align: 'center' });
                    doc.text(`${totalCountries} countries`, PW - 10, PH - 4, { align: 'right' });
                }

                /* ───────── SAVE ───────── */
                const safeTitle = title.replace(/[^a-z0-9]+/gi, '_').toLowerCase();
                doc.save(`${safeTitle}.pdf`);

                showToast();
            } catch (err) {
                console.error('[Numbeo PDF]', err);
                alert('An error occurred while generating the PDF. Check the browser console for details.');
            } finally {
                btn.classList.remove('loading');
                btn.innerHTML = `
                    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
                         stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
                        <polyline points="14 2 14 8 20 8"/>
                        <line x1="16" y1="13" x2="8" y2="13"/>
                        <line x1="16" y1="17" x2="8" y2="17"/>
                        <polyline points="10 9 9 9 8 9"/>
                    </svg>
                    Export to PDF`;
            }
        }, 60);
    }

    btn.addEventListener('click', buildPDF);

})();