// download.js — per-node downloads, surfaced through the tree's
// right-click menu (downloadFile / downloadFolder).
//
// downloadFile: a single file. Server mode lets the browser pull
// node.url (zddc-server emits Content-Disposition); FS-API mode
// reads bytes through the file handle and blob-downloads.
//
// downloadFolder: an arbitrary directory node as a .zip. Server
// mode points an <a download> at the virtual "<node-path>.zip"
// URL — zddc-server recognises the suffix and streams an ACL-
// filtered archive without buffering on the client. FS-API mode
// walks the picked handle in two passes — metadata first, then
// bytes — so we can warn before loading a very large tree into
// memory.
(function () {
    'use strict';

    var state = window.app.state;

    // Soft thresholds for the offline bundle: above either, confirm()
    // before loading everything into memory.
    var WARN_FILE_COUNT = 2000;
    var WARN_TOTAL_BYTES = 500 * 1024 * 1024;

    function events() { return window.app.modules.events; }

    // Canonical document-conversion matrix — mirrors zddc/internal/convert
    // Convert(): which target formats a given source extension can be exported
    // to. PDF is markdown-only (md→pdf) because the server has no docx→pdf /
    // html→pdf path. This is the SINGLE source of truth for both the Export
    // context-menu (download.exportTargets) and the markdown editor's
    // DOCX/HTML/PDF buttons (preview-markdown.js), so the two never drift.
    var EXPORT_MATRIX = {
        md: ['docx', 'html', 'pdf'],
        docx: ['md', 'html'],
        html: ['md', 'docx']
    };

    // exportTargets returns the formats a file of extension `ext` can be
    // exported to (excludes the source format itself), or [] if `ext` is not a
    // convertible source. Case-insensitive.
    function exportTargets(ext) {
        return EXPORT_MATRIX[String(ext || '').toLowerCase()] || [];
    }

    // convertUrl maps a source path/URL to its sibling virtual-conversion URL
    // (foo.md → foo.pdf). zddc-server recognises the sibling-extension pattern
    // and converts on the fly. Shared by exportFile and the editor buttons.
    function convertUrl(path, fmt) {
        return String(path || '').replace(/\.[^./]+$/, '') + '.' + fmt;
    }

    function isHiddenName(name) {
        return name.length === 0 || name[0] === '.' || name[0] === '_';
    }

    function fmtMB(bytes) { return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; }

    // Trigger a browser download of a Blob (revokes the object URL after).
    function downloadBlob(filename, blob) {
        var a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        setTimeout(function () {
            URL.revokeObjectURL(a.href);
            a.remove();
        }, 0);
    }

    // Trigger a download from a same-origin server URL via Content-Disposition.
    // NOTE: an <a download> click is fire-and-forget — a server error
    // (401/403/404/5xx) can't be observed here, so failures surface only as
    // the browser's own download error, not a toast. This is deliberate: the
    // folder path points at zddc-server's streamed virtual "<dir>.zip"
    // endpoint, and buffering it through fetch() to make errors catchable
    // would defeat the streaming (the archive can be arbitrarily large).
    function downloadUrl(filename, url) {
        var a = document.createElement('a');
        a.href = url;
        a.download = filename; // hint; the server's Content-Disposition wins
        document.body.appendChild(a);
        a.click();
        setTimeout(function () { a.remove(); }, 0);
    }

    // Recursively collect every (non-hidden) file under dirHandle into
    // `out` as { relPath, handle, size }, accumulating into `tally`.
    // relPrefix is the slash-terminated path within the picked root
    // ("" at the root).
    async function collectFiles(dirHandle, relPrefix, out, tally) {
        for await (var pair of dirHandle.entries()) {
            var name = pair[0];
            var handle = pair[1];
            if (isHiddenName(name)) continue;
            if (handle.kind === 'directory') {
                await collectFiles(handle, relPrefix + name + '/', out, tally);
            } else {
                var size = 0;
                try {
                    var f = await handle.getFile();
                    size = f.size || 0;
                } catch (_e) { /* permission lost — count it as 0 */ }
                out.push({ relPath: relPrefix + name, handle: handle, size: size });
                tally.count++;
                tally.bytes += size;
            }
        }
    }

    async function downloadFsSubtree(rootHandle) {
        var ev = events();
        ev.statusInfo('Scanning ' + rootHandle.name + '…');
        var files = [];
        var tally = { count: 0, bytes: 0 };
        await collectFiles(rootHandle, '', files, tally);
        if (files.length === 0) {
            ev.statusInfo(rootHandle.name + ' is empty — nothing to download.');
            return;
        }
        if (tally.count > WARN_FILE_COUNT || tally.bytes > WARN_TOTAL_BYTES) {
            var ok = window.confirm(
                'This folder has ' + tally.count + ' files (~' + fmtMB(tally.bytes) + ').\n\n'
                + 'Building the zip loads them all into memory — it may be slow or crash the tab.\n\n'
                + 'Continue?');
            if (!ok) { ev.statusClear(); return; }
        }
        var zip = new window.JSZip();
        for (var i = 0; i < files.length; i++) {
            ev.statusInfo('Zipping ' + rootHandle.name + '… (' + (i + 1) + '/' + files.length + ')');
            // Hand JSZip the File (a Blob, backed by disk) rather than
            // pre-reading every file's arrayBuffer — otherwise the whole
            // tree's raw bytes sit in the JS heap at once before zipping.
            // JSZip reads each Blob lazily during generateAsync.
            var f = await files[i].handle.getFile();
            zip.file(rootHandle.name + '/' + files[i].relPath, f);
        }
        ev.statusInfo('Generating ' + rootHandle.name + '.zip…');
        var blob = await zip.generateAsync({ type: 'blob' });
        downloadBlob(rootHandle.name + '.zip', blob);
        ev.statusInfo('Downloaded ' + rootHandle.name + '.zip (' + files.length + ' files)');
    }

    var busy = false;

    // Download a single file node. Server mode: rely on the node's
    // own URL (the server emits Content-Disposition). FS mode: read
    // bytes through the handle and trigger a blob download. Works
    // for ordinary files, for .zip members (the loader sets node.url
    // for zip members in server mode and a ZipFileHandle offline),
    // and for the .zip file itself.
    async function downloadFile(node) {
        if (busy) return;
        if (!node || node.isDir) {
            events().statusError('Not a file: ' + (node && node.name));
            return;
        }
        busy = true;
        try {
            if (node.url) {
                events().statusInfo('Downloading ' + node.name + '…');
                downloadUrl(node.name, node.url);
                setTimeout(function () { events().statusClear(); }, 2500);
            } else if (node.handle && typeof node.handle.getFile === 'function') {
                events().statusInfo('Preparing ' + node.name + '…');
                var f = await node.handle.getFile();
                var blob = new Blob([await f.arrayBuffer()]);
                downloadBlob(node.name, blob);
                events().statusInfo('Downloaded ' + node.name);
            } else {
                events().statusError('No download path for ' + node.name);
            }
        } catch (e) {
            events().statusError('Download failed: ' + (e && e.message ? e.message : e));
        } finally {
            busy = false;
        }
    }

    // Download an arbitrary folder node as a .zip. Server mode points
    // an <a download> at the virtual "<node-path>.zip" URL (the
    // dispatcher recognises the suffix and streams the subtree). FS
    // mode walks the directory handle.
    async function downloadFolder(node) {
        if (busy) return;
        if (!node || !node.isDir) {
            events().statusError('Not a folder: ' + (node && node.name));
            return;
        }
        busy = true;
        try {
            if (state.source === 'server') {
                var tree = window.app.modules.tree;
                var dir = tree.pathFor(node).replace(/\/$/, '');
                events().statusInfo('Preparing ' + node.name + '.zip…');
                downloadUrl(node.name + '.zip', dir + '.zip');
                setTimeout(function () { events().statusClear(); }, 2500);
            } else if (state.source === 'fs' && node.handle
                       && node.handle.kind === 'directory') {
                await downloadFsSubtree(node.handle);
            } else {
                events().statusError('Cannot download ' + node.name);
            }
        } catch (e) {
            events().statusError('Download failed: ' + (e && e.message ? e.message : e));
        } finally {
            busy = false;
        }
    }

    // Export a file converted to another format. Server-only: builds the
    // sibling-extension URL (foo.docx → foo.md) and lets the browser pull it —
    // zddc-server recognises the virtual path and converts on the fly, emitting
    // Content-Disposition. fmt is a bare extension ("md" | "docx" | "html").
    function exportFile(node, fmt) {
        if (!node || node.isDir) {
            events().statusError('Not a file: ' + (node && node.name));
            return;
        }
        if (state.source !== 'server') {
            events().statusError('Export to .' + fmt + ' needs a server connection');
            return;
        }
        var tree = window.app.modules.tree;
        var path = tree && tree.pathFor ? tree.pathFor(node) : node.url;
        if (!path) {
            events().statusError('No path for ' + node.name);
            return;
        }
        var url = convertUrl(path, fmt);
        var name = convertUrl(node.name, fmt);
        events().statusInfo('Exporting ' + name + '…');
        downloadUrl(name, url);
        setTimeout(function () { events().statusClear(); }, 2500);
    }

    window.app.modules.download = {
        downloadFile: downloadFile,
        downloadFolder: downloadFolder,
        exportFile: exportFile,
        exportTargets: exportTargets,
        convertUrl: convertUrl
    };
})();
