// upload.js — drag-drop file upload into the current scope.
//
// Active only in server mode and only at paths where uploads make
// sense (any segment named working / staging / incoming, case-
// insensitive). At other scopes the handlers stay armed but ignore
// drops silently — there is no visible drop-zone overlay outside an
// upload-eligible context.
//
// Wire model:
//   - dragenter on the document raises a counter; first-enter shows
//     the overlay.
//   - dragleave decrements; reaching zero hides the overlay.
//   - drop short-circuits: prevent default, PUT each file under the
//     current state.currentPath, surface per-file toast results,
//     refetch the listing on completion.
//
// The PUT uses fetch(`<currentPath><filename>`, method: 'PUT'). The
// server's authorizeAction enforces write ACL on the parent; a 403
// surfaces as an error toast and the rest of the batch proceeds.
//
// Per-file size cap (UPLOAD_MAX_BYTES): files larger than the cap
// are rejected client-side with a clear toast — the server would
// accept them in chunks but browse's v1 PUT is a single body, and
// dropping a 4 GB CAD bundle into the browser tab as a Blob is a
// poor experience. Operators with larger uploads should use a
// dedicated client (zddc-cli or the cache/mirror downstream).
(function () {
    'use strict';

    if (!window.app || !window.app.modules) return;

    var UPLOAD_MAX_BYTES = 256 * 1024 * 1024;       // 256 MiB per file
    // Path segments where uploads are allowed. Matches the current
    // hardcoded surface (working / staging / incoming). Will become
    // configurable when the folders: schema lands.
    var UPLOAD_SCOPES = /\/(working|staging|incoming)(\/|$)/i;

    var state = window.app.state;
    var enterCount = 0;
    var overlayEl = null;

    function ensureOverlay() {
        if (overlayEl) return overlayEl;
        overlayEl = document.createElement('div');
        overlayEl.className = 'upload-overlay';
        overlayEl.setAttribute('aria-hidden', 'true');
        overlayEl.innerHTML =
            '<div class="upload-overlay__panel">'
            + '<div class="upload-overlay__icon">⤴</div>'
            + '<div class="upload-overlay__title">Drop to upload</div>'
            + '<div class="upload-overlay__path" id="uploadOverlayPath"></div>'
            + '</div>';
        document.body.appendChild(overlayEl);
        return overlayEl;
    }

    function currentScopeAllows() {
        if (!state || state.source !== 'server') return false;
        var p = state.currentPath || '';
        return UPLOAD_SCOPES.test(p);
    }

    function showOverlay() {
        var el = ensureOverlay();
        var pathEl = el.querySelector('#uploadOverlayPath');
        if (pathEl) pathEl.textContent = state.currentPath || '/';
        el.classList.add('is-active');
    }

    function hideOverlay() {
        if (overlayEl) overlayEl.classList.remove('is-active');
    }

    function dragHasFiles(e) {
        if (!e.dataTransfer || !e.dataTransfer.types) return false;
        var types = e.dataTransfer.types;
        for (var i = 0; i < types.length; i++) {
            if (types[i] === 'Files') return true;
        }
        return false;
    }

    function uploadUrl(filename) {
        var base = state.currentPath || '/';
        if (!base.endsWith('/')) base += '/';
        return base + encodeURIComponent(filename);
    }

    async function uploadOne(file) {
        if (file.size > UPLOAD_MAX_BYTES) {
            return {
                file: file,
                ok: false,
                status: 0,
                message: 'too large (max ' + Math.round(UPLOAD_MAX_BYTES / 1024 / 1024) + ' MiB)'
            };
        }
        try {
            var resp = await fetch(uploadUrl(file.name), {
                method: 'PUT',
                body: file,
                credentials: 'same-origin',
                headers: {
                    'Content-Type': file.type || 'application/octet-stream'
                }
            });
            return {
                file: file,
                ok: resp.ok,
                status: resp.status,
                message: resp.ok ? '' : ('HTTP ' + resp.status)
            };
        } catch (e) {
            return {
                file: file,
                ok: false,
                status: 0,
                message: (e && e.message) ? e.message : 'network error'
            };
        }
    }

    async function handleDrop(e) {
        e.preventDefault();
        e.stopPropagation();
        enterCount = 0;
        hideOverlay();

        if (!currentScopeAllows()) return;
        var dt = e.dataTransfer;
        if (!dt || !dt.files || dt.files.length === 0) return;

        var files = Array.from(dt.files);
        var note = window.zddc && window.zddc.toast;
        if (note) note('Uploading ' + files.length + ' file' + (files.length === 1 ? '' : 's') + '…', 'info');

        // Sequential — predictable progress + ordering. Can parallelise
        // later if it matters.
        var ok = 0, fail = 0;
        for (var i = 0; i < files.length; i++) {
            var res = await uploadOne(files[i]);
            if (res.ok) {
                ok++;
            } else {
                fail++;
                if (note) {
                    note('Upload failed: ' + res.file.name + ' — ' + res.message, 'error');
                }
            }
        }
        if (note) {
            if (fail === 0) {
                note('Uploaded ' + ok + ' file' + (ok === 1 ? '' : 's'), 'success');
            } else if (ok === 0) {
                note('All ' + fail + ' upload' + (fail === 1 ? '' : 's') + ' failed', 'error');
            } else {
                note(ok + ' uploaded, ' + fail + ' failed', 'warning');
            }
        }

        // Refresh the listing so newly-uploaded files appear.
        var loader = window.app.modules.loader;
        var tree = window.app.modules.tree;
        if (loader && tree && state.currentPath) {
            try {
                var es = await loader.fetchServerChildren(state.currentPath);
                tree.setRoot(es);
                tree.render();
            } catch (_e) { /* swallow; user can hard-reload */ }
        }
    }

    function onEnter(e) {
        if (!dragHasFiles(e)) return;
        enterCount++;
        if (enterCount === 1 && currentScopeAllows()) {
            showOverlay();
        }
    }

    function onLeave(e) {
        if (!dragHasFiles(e)) return;
        enterCount = Math.max(0, enterCount - 1);
        if (enterCount === 0) hideOverlay();
    }

    function onOver(e) {
        if (!dragHasFiles(e)) return;
        // preventDefault on dragover is required for drop to fire.
        e.preventDefault();
        if (e.dataTransfer && currentScopeAllows()) {
            e.dataTransfer.dropEffect = 'copy';
        } else if (e.dataTransfer) {
            e.dataTransfer.dropEffect = 'none';
        }
    }

    function init() {
        document.addEventListener('dragenter', onEnter, false);
        document.addEventListener('dragleave', onLeave, false);
        document.addEventListener('dragover',  onOver,  false);
        document.addEventListener('drop',      handleDrop, false);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    window.app.modules.upload = {
        currentScopeAllows: currentScopeAllows,
        UPLOAD_MAX_BYTES: UPLOAD_MAX_BYTES
    };
})();
