/**
 * ZDDC — shared preview helpers
 *
 * Cross-tool helpers for previewing file types that need a decoder:
 *   - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
 *   - ZIP listing (JSZip) — sortable file-list view
 *
 * Renderers operate on any document (parent window or popup window), so the
 * same code works for tools whose preview opens in a popup (classifier,
 * archive, transmittal) and tools that render inline (mdedit).
 *
 * Public API on window.zddc.preview:
 *   loadLibrary(url)                                  → Promise<void>
 *   renderTiff(doc, container, arrayBuffer, opts)     → Promise<void>
 *   renderZipListing(doc, container, arrayBuffer, opts) → Promise<void>
 *   TIFF_EXTENSIONS, IMAGE_EXTENSIONS, TEXT_EXTENSIONS, OFFICE_EXTENSIONS
 *   isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
 *
 * Each tool keeps its own dispatcher; this lib only owns the heavy renderers.
 */

(function (root) {
    'use strict';

    var TIFF_EXTENSIONS = ['tif', 'tiff'];
    var IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp', 'ico'];
    var TEXT_EXTENSIONS = [
        'txt', 'md', 'markdown', 'json', 'xml', 'csv', 'tsv', 'log',
        'html', 'htm', 'css', 'js', 'mjs', 'ts', 'tsx', 'jsx',
        'py', 'rb', 'sh', 'bash', 'zsh', 'bat', 'ps1',
        'yaml', 'yml', 'ini', 'cfg', 'conf', 'toml',
        'c', 'cc', 'cpp', 'h', 'hpp', 'go', 'rs', 'java', 'kt',
        'sql', 'env'
    ];
    var OFFICE_EXTENSIONS = ['docx', 'xlsx', 'xls'];

    function lowerExt(ext) { return (ext || '').toLowerCase(); }
    function isTiff(ext)   { return TIFF_EXTENSIONS.indexOf(lowerExt(ext)) !== -1; }
    function isImage(ext)  { return IMAGE_EXTENSIONS.indexOf(lowerExt(ext)) !== -1; }
    function isText(ext)   { return TEXT_EXTENSIONS.indexOf(lowerExt(ext)) !== -1; }
    function isZip(ext)    { return lowerExt(ext) === 'zip'; }
    function isOffice(ext) { return OFFICE_EXTENSIONS.indexOf(lowerExt(ext)) !== -1; }

    // ── CDN library loader (parent window cache) ─────────────────────────────

    var _libCache = new Map();

    function loadLibrary(url) {
        if (_libCache.has(url)) return _libCache.get(url);
        var p = new Promise(function (resolve, reject) {
            var s = document.createElement('script');
            s.src = url;
            s.onload = function () { resolve(); };
            s.onerror = function () { reject(new Error('Failed to load: ' + url)); };
            document.head.appendChild(s);
        });
        _libCache.set(url, p);
        return p;
    }

    // ── Style injection (idempotent per-document) ────────────────────────────

    function injectStyles(doc, id, css) {
        if (doc.getElementById(id)) return;
        var style = doc.createElement('style');
        style.id = id;
        style.textContent = css;
        doc.head.appendChild(style);
    }

    // ── Helpers ──────────────────────────────────────────────────────────────

    function formatSize(bytes) {
        if (bytes == null) return '';
        if (bytes < 1024) return bytes + ' B';
        if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
        if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
        return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
    }

    function formatDate(d) {
        if (!d) return '';
        var pad = function (n) { return n < 10 ? '0' + n : '' + n; };
        return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
            + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
    }

    function escapeHtml(s) {
        return String(s)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;');
    }

    // ── TIFF renderer ────────────────────────────────────────────────────────

    var TIFF_CSS =
        '.tiff-toolbar{display:flex;align-items:center;gap:.4rem;padding:.4rem .6rem;' +
            'background:#f5f5f5;border-bottom:1px solid #ddd;flex-wrap:wrap;font-size:.85rem;}' +
        '.tiff-toolbar .tiff-btn{padding:.25rem .55rem;border:1px solid #ccc;border-radius:3px;' +
            'background:#fff;cursor:pointer;font-size:.85rem;line-height:1;min-width:1.8rem;}' +
        '.tiff-toolbar .tiff-btn:hover:not(:disabled){background:#e8e8e8;}' +
        '.tiff-toolbar .tiff-btn:disabled{opacity:.4;cursor:default;}' +
        '.tiff-toolbar .tiff-page-info{display:inline-flex;align-items:center;gap:.3rem;}' +
        '.tiff-toolbar .tiff-page-input{width:3.2rem;padding:.2rem .3rem;border:1px solid #ccc;' +
            'border-radius:3px;text-align:center;font-size:.85rem;}' +
        '.tiff-toolbar .tiff-zoom-select{padding:.2rem .3rem;border:1px solid #ccc;border-radius:3px;' +
            'background:#fff;font-size:.85rem;}' +
        '.tiff-toolbar .tiff-spacer{flex:1;}' +
        '.tiff-viewport{flex:1;overflow:auto;background:#525659;display:flex;align-items:flex-start;' +
            'justify-content:center;padding:1rem;}' +
        '.tiff-canvas{background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.4);display:block;' +
            'image-rendering:auto;}' +
        '.tiff-error{flex:1;display:flex;align-items:center;justify-content:center;color:#900;' +
            'padding:2rem;text-align:center;}';

    function renderTiff(doc, container, arrayBuffer, opts) {
        opts = opts || {};
        injectStyles(doc, 'zddc-tiff-styles', TIFF_CSS);

        // UTIF is bundled (shared/vendor/utif.min.js) — window.UTIF is
        // available synchronously. Promise.resolve() keeps the existing
        // .then() chain shape so callers don't need to change.
        return Promise.resolve().then(function () {
            var ifds;
            try {
                ifds = window.UTIF.decode(arrayBuffer);
            } catch (e) {
                container.innerHTML = '<div class="tiff-error">Failed to parse TIFF: '
                    + escapeHtml(e.message || e) + '</div>';
                return;
            }
            if (!ifds || !ifds.length) {
                container.innerHTML = '<div class="tiff-error">No images found in TIFF.</div>';
                return;
            }

            // Reset container to a flex column
            container.innerHTML = '';
            container.style.display = 'flex';
            container.style.flexDirection = 'column';
            container.style.minHeight = '0';
            container.style.height = '100%';
            container.style.overflow = 'hidden';

            // Toolbar
            var toolbar = doc.createElement('div');
            toolbar.className = 'tiff-toolbar';

            var btnPrev = doc.createElement('button');
            btnPrev.className = 'tiff-btn'; btnPrev.type = 'button';
            btnPrev.title = 'Previous page'; btnPrev.textContent = '◀';

            var pageInfo = doc.createElement('span');
            pageInfo.className = 'tiff-page-info';
            var pageInput = doc.createElement('input');
            pageInput.type = 'number'; pageInput.min = '1'; pageInput.value = '1';
            pageInput.className = 'tiff-page-input';
            var pageOf = doc.createElement('span');
            pageOf.textContent = ' of ' + ifds.length;
            pageInfo.appendChild(doc.createTextNode('Page '));
            pageInfo.appendChild(pageInput);
            pageInfo.appendChild(pageOf);

            var btnNext = doc.createElement('button');
            btnNext.className = 'tiff-btn'; btnNext.type = 'button';
            btnNext.title = 'Next page'; btnNext.textContent = '▶';

            var spacer = doc.createElement('span');
            spacer.className = 'tiff-spacer';

            var btnZoomOut = doc.createElement('button');
            btnZoomOut.className = 'tiff-btn'; btnZoomOut.type = 'button';
            btnZoomOut.title = 'Zoom out'; btnZoomOut.textContent = '−';

            var zoomSelect = doc.createElement('select');
            zoomSelect.className = 'tiff-zoom-select';
            var zoomOptions = [
                ['fit-width', 'Fit width'],
                ['fit-page', 'Fit page'],
                ['0.5', '50%'],
                ['0.75', '75%'],
                ['1', '100%'],
                ['1.25', '125%'],
                ['1.5', '150%'],
                ['2', '200%'],
                ['3', '300%'],
                ['4', '400%']
            ];
            zoomOptions.forEach(function (z) {
                var o = doc.createElement('option');
                o.value = z[0]; o.textContent = z[1];
                zoomSelect.appendChild(o);
            });
            zoomSelect.value = 'fit-width';

            var btnZoomIn = doc.createElement('button');
            btnZoomIn.className = 'tiff-btn'; btnZoomIn.type = 'button';
            btnZoomIn.title = 'Zoom in'; btnZoomIn.textContent = '+';

            toolbar.appendChild(btnPrev);
            toolbar.appendChild(pageInfo);
            toolbar.appendChild(btnNext);
            toolbar.appendChild(spacer);
            toolbar.appendChild(btnZoomOut);
            toolbar.appendChild(zoomSelect);
            toolbar.appendChild(btnZoomIn);

            // Viewport with canvas
            var viewport = doc.createElement('div');
            viewport.className = 'tiff-viewport';
            var canvas = doc.createElement('canvas');
            canvas.className = 'tiff-canvas';
            viewport.appendChild(canvas);

            container.appendChild(toolbar);
            container.appendChild(viewport);

            // Render state
            var currentPage = 0;
            var zoom = 1;
            var fitMode = 'width'; // 'width' | 'page' | null
            var decoded = new Array(ifds.length);

            function decodePage(i) {
                if (decoded[i]) return decoded[i];
                var ifd = ifds[i];
                window.UTIF.decodeImage(arrayBuffer, ifd);
                var rgba = window.UTIF.toRGBA8(ifd);
                decoded[i] = { rgba: rgba, w: ifd.width, h: ifd.height };
                return decoded[i];
            }

            function applyZoom() {
                var page = decoded[currentPage];
                if (!page) return;
                var availW = viewport.clientWidth - 32; // padding
                var availH = viewport.clientHeight - 32;
                var scale;
                if (fitMode === 'width') {
                    scale = availW / page.w;
                } else if (fitMode === 'page') {
                    scale = Math.min(availW / page.w, availH / page.h);
                } else {
                    scale = zoom;
                }
                if (!isFinite(scale) || scale <= 0) scale = 1;
                canvas.style.width = (page.w * scale) + 'px';
                canvas.style.height = (page.h * scale) + 'px';
            }

            function renderPage() {
                var page;
                try {
                    page = decodePage(currentPage);
                } catch (e) {
                    container.innerHTML = '<div class="tiff-error">Failed to decode page '
                        + (currentPage + 1) + ': ' + escapeHtml(e.message || e) + '</div>';
                    return;
                }
                canvas.width = page.w;
                canvas.height = page.h;
                var ctx = canvas.getContext('2d');
                var imgData = ctx.createImageData(page.w, page.h);
                imgData.data.set(page.rgba);
                ctx.putImageData(imgData, 0, 0);
                applyZoom();
                pageInput.value = String(currentPage + 1);
                btnPrev.disabled = currentPage <= 0;
                btnNext.disabled = currentPage >= ifds.length - 1;
            }

            function setZoomFromSelect() {
                var v = zoomSelect.value;
                if (v === 'fit-width') { fitMode = 'width'; }
                else if (v === 'fit-page') { fitMode = 'page'; }
                else { fitMode = null; zoom = parseFloat(v) || 1; }
                applyZoom();
            }

            function nudgeZoom(factor) {
                if (fitMode) {
                    // capture current effective scale before leaving fit mode
                    var page = decoded[currentPage];
                    if (page) {
                        var availW = viewport.clientWidth - 32;
                        var availH = viewport.clientHeight - 32;
                        zoom = fitMode === 'width'
                            ? availW / page.w
                            : Math.min(availW / page.w, availH / page.h);
                    } else {
                        zoom = 1;
                    }
                    fitMode = null;
                }
                zoom = Math.max(0.1, Math.min(8, zoom * factor));
                // Match select option if any are close, else show as percent
                var matched = false;
                for (var i = 0; i < zoomSelect.options.length; i++) {
                    var ov = zoomSelect.options[i].value;
                    if (ov !== 'fit-width' && ov !== 'fit-page' && Math.abs(parseFloat(ov) - zoom) < 0.001) {
                        zoomSelect.value = ov; matched = true; break;
                    }
                }
                if (!matched) {
                    // Nearest standard step
                    var best = '1', bestDiff = Infinity;
                    for (var j = 0; j < zoomSelect.options.length; j++) {
                        var v2 = zoomSelect.options[j].value;
                        if (v2 === 'fit-width' || v2 === 'fit-page') continue;
                        var diff = Math.abs(parseFloat(v2) - zoom);
                        if (diff < bestDiff) { bestDiff = diff; best = v2; }
                    }
                    zoom = parseFloat(best);
                    zoomSelect.value = best;
                }
                applyZoom();
            }

            btnPrev.addEventListener('click', function () {
                if (currentPage > 0) { currentPage--; renderPage(); }
            });
            btnNext.addEventListener('click', function () {
                if (currentPage < ifds.length - 1) { currentPage++; renderPage(); }
            });
            pageInput.addEventListener('change', function () {
                var n = parseInt(pageInput.value, 10);
                if (!isNaN(n) && n >= 1 && n <= ifds.length) {
                    currentPage = n - 1;
                    renderPage();
                } else {
                    pageInput.value = String(currentPage + 1);
                }
            });
            zoomSelect.addEventListener('change', setZoomFromSelect);
            btnZoomIn.addEventListener('click', function () { nudgeZoom(1.25); });
            btnZoomOut.addEventListener('click', function () { nudgeZoom(1 / 1.25); });

            // Keyboard nav (only when toolbar/viewport in focus path)
            container.tabIndex = 0;
            container.addEventListener('keydown', function (e) {
                if (e.target === pageInput) return;
                if (e.key === 'ArrowLeft' || e.key === 'PageUp') {
                    if (currentPage > 0) { currentPage--; renderPage(); e.preventDefault(); }
                } else if (e.key === 'ArrowRight' || e.key === 'PageDown' || e.key === ' ') {
                    if (currentPage < ifds.length - 1) { currentPage++; renderPage(); e.preventDefault(); }
                }
            });

            // Re-fit on viewport resize
            if (typeof (doc.defaultView && doc.defaultView.ResizeObserver) === 'function') {
                var ro = new doc.defaultView.ResizeObserver(function () { applyZoom(); });
                ro.observe(viewport);
            } else if (doc.defaultView) {
                doc.defaultView.addEventListener('resize', function () { applyZoom(); });
            }

            renderPage();
        });
    }

    // ── ZIP listing renderer ─────────────────────────────────────────────────

    var ZIP_CSS =
        '.zip-header{padding:.4rem .8rem;background:#f5f5f5;border-bottom:1px solid #ddd;' +
            'font-size:.85rem;color:#444;}' +
        '.zip-table-wrap{flex:1;overflow:auto;}' +
        '.zip-table{width:100%;border-collapse:collapse;font-size:.85rem;font-family:' +
            '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;}' +
        '.zip-table thead th{position:sticky;top:0;background:#f0f0f0;text-align:left;' +
            'padding:.4rem .6rem;border-bottom:1px solid #ccc;cursor:pointer;user-select:none;' +
            'font-weight:600;}' +
        '.zip-table thead th:hover{background:#e6e6e6;}' +
        '.zip-table thead th.zip-sort-asc::after{content:" ▲";font-size:.7rem;color:#888;}' +
        '.zip-table thead th.zip-sort-desc::after{content:" ▼";font-size:.7rem;color:#888;}' +
        '.zip-table tbody td{padding:.3rem .6rem;border-bottom:1px solid #eee;}' +
        '.zip-table tbody tr:hover{background:#f6faff;}' +
        '.zip-table .zip-folder{color:#888;}' +
        '.zip-table .zip-name{color:#222;}' +
        '.zip-table .zip-size,.zip-table .zip-date{font-variant-numeric:tabular-nums;' +
            'white-space:nowrap;color:#555;}' +
        '.zip-table .zip-col-size,.zip-table .zip-col-date{text-align:right;}' +
        '.zip-empty{padding:2rem;text-align:center;color:#888;}';

    function renderZipListing(doc, container, arrayBuffer, opts) {
        opts = opts || {};
        injectStyles(doc, 'zddc-zip-styles', ZIP_CSS);

        // JSZip is bundled in every tool that uses preview-lib (each
        // tool's build.sh concatenates shared/vendor/jszip.min.js).
        return window.JSZip.loadAsync(arrayBuffer).then(function (zip) {
            var entries = [];
            zip.forEach(function (relativePath, zipEntry) {
                if (zipEntry.dir) return;
                var size = (zipEntry._data && zipEntry._data.uncompressedSize) || 0;
                entries.push({
                    path: relativePath,
                    name: relativePath.split('/').pop(),
                    size: size,
                    modified: zipEntry.date instanceof Date ? zipEntry.date : null
                });
            });

            container.innerHTML = '';
            container.style.display = 'flex';
            container.style.flexDirection = 'column';
            container.style.minHeight = '0';
            container.style.height = '100%';
            container.style.overflow = 'hidden';

            var totalSize = entries.reduce(function (s, e) { return s + e.size; }, 0);

            var header = doc.createElement('div');
            header.className = 'zip-header';
            header.textContent = entries.length + ' file' + (entries.length === 1 ? '' : 's')
                + (totalSize ? ' · ' + formatSize(totalSize) + ' uncompressed' : '');
            container.appendChild(header);

            if (!entries.length) {
                var empty = doc.createElement('div');
                empty.className = 'zip-empty';
                empty.textContent = '(empty archive)';
                container.appendChild(empty);
                return;
            }

            var wrap = doc.createElement('div');
            wrap.className = 'zip-table-wrap';

            var table = doc.createElement('table');
            table.className = 'zip-table';
            var thead = doc.createElement('thead');
            var trh = doc.createElement('tr');
            var cols = [
                { key: 'path', label: 'Name', cls: 'zip-col-name' },
                { key: 'size', label: 'Size', cls: 'zip-col-size' },
                { key: 'modified', label: 'Modified', cls: 'zip-col-date' }
            ];
            cols.forEach(function (c) {
                var th = doc.createElement('th');
                th.className = c.cls;
                th.dataset.key = c.key;
                th.textContent = c.label;
                trh.appendChild(th);
            });
            thead.appendChild(trh);
            table.appendChild(thead);

            var tbody = doc.createElement('tbody');
            table.appendChild(tbody);

            wrap.appendChild(table);
            container.appendChild(wrap);

            var sortKey = 'path';
            var sortDir = 1;

            function render() {
                var sorted = entries.slice().sort(function (a, b) {
                    var av, bv;
                    if (sortKey === 'size') { av = a.size; bv = b.size; }
                    else if (sortKey === 'modified') {
                        av = a.modified ? a.modified.getTime() : 0;
                        bv = b.modified ? b.modified.getTime() : 0;
                    } else {
                        av = a.path.toLowerCase(); bv = b.path.toLowerCase();
                    }
                    if (av < bv) return -1 * sortDir;
                    if (av > bv) return 1 * sortDir;
                    return 0;
                });

                tbody.innerHTML = '';
                sorted.forEach(function (e) {
                    var tr = doc.createElement('tr');
                    var td1 = doc.createElement('td');
                    var slash = e.path.lastIndexOf('/');
                    if (slash >= 0) {
                        var folder = doc.createElement('span');
                        folder.className = 'zip-folder';
                        folder.textContent = e.path.substring(0, slash + 1);
                        td1.appendChild(folder);
                    }
                    var name = doc.createElement('span');
                    name.className = 'zip-name';
                    name.textContent = e.name;
                    td1.appendChild(name);

                    var td2 = doc.createElement('td');
                    td2.className = 'zip-size';
                    td2.textContent = formatSize(e.size);

                    var td3 = doc.createElement('td');
                    td3.className = 'zip-date';
                    td3.textContent = formatDate(e.modified);

                    tr.appendChild(td1); tr.appendChild(td2); tr.appendChild(td3);
                    tbody.appendChild(tr);
                });

                // Update sort arrows
                var ths = thead.querySelectorAll('th');
                for (var i = 0; i < ths.length; i++) {
                    ths[i].classList.remove('zip-sort-asc', 'zip-sort-desc');
                    if (ths[i].dataset.key === sortKey) {
                        ths[i].classList.add(sortDir > 0 ? 'zip-sort-asc' : 'zip-sort-desc');
                    }
                }
            }

            thead.querySelectorAll('th').forEach(function (th) {
                th.addEventListener('click', function () {
                    var k = th.dataset.key;
                    if (sortKey === k) sortDir = -sortDir;
                    else { sortKey = k; sortDir = 1; }
                    render();
                });
            });

            render();
        }).catch(function (err) {
            container.innerHTML = '<div class="zip-empty">Failed to read ZIP: '
                + escapeHtml(err.message || err) + '</div>';
        });
    }

    // ── Public API ───────────────────────────────────────────────────────────

    if (!root.zddc) root.zddc = {};
    root.zddc.preview = {
        TIFF_EXTENSIONS: TIFF_EXTENSIONS,
        IMAGE_EXTENSIONS: IMAGE_EXTENSIONS,
        TEXT_EXTENSIONS: TEXT_EXTENSIONS,
        OFFICE_EXTENSIONS: OFFICE_EXTENSIONS,
        isTiff: isTiff,
        isImage: isImage,
        isText: isText,
        isZip: isZip,
        isOffice: isOffice,
        loadLibrary: loadLibrary,
        renderTiff: renderTiff,
        renderZipListing: renderZipListing,
        formatSize: formatSize,
        formatDate: formatDate
    };
})(typeof window !== 'undefined' ? window : this);
