// stage.js — Stage and Unstage workflow modals.
//
// In the flat-peer layout working/ and staging/ are top-level peers,
// each partitioned by party: working/<party>/<file> and
// staging/<party>/<batch>/<file>. Stage and Unstage are per-party — the
// destination batch is always inside the SAME party's staging peer. The
// party context is read from the source file's path.
//
// Stage: move a file from working/<party>/<…> into a transmittal folder
// under staging/<party>/<…>. Modal lists existing transmittal folders in
// the party's staging/ plus a "New transmittal folder…" option that
// prompts for a ZDDC-conforming name and mkdirs it before the move.
//
// Unstage: move a file from staging/<party>/<transmittal>/ back to
// working/<party>/ (overridable).
//
// Both reuse the existing X-ZDDC-Op: move primitive — no new composite
// endpoint is needed; the client just orchestrates one POST per file
// (a multi-file selection iterates and reports aggregate status).

(function () {
    'use strict';

    function status(msg, level) {
        var t = window.zddc && window.zddc.toast;
        if (t) t(msg, level || 'info');
    }
    // Re-fetch the current listing so the moved file appears/disappears
    // without a manual reload. Best-effort: absent on older builds.
    function refreshListing() {
        var ev = window.app.modules.events;
        if (ev && typeof ev.refreshListing === 'function') ev.refreshListing();
    }
    // Guard against a second invocation while a move is mid-flight (e.g. a
    // double menu click). The picker modal also blocks re-entry while open.
    var busy = false;
    var escapeHtml = window.app.modules.util.escapeHtml;

    // ── Scope detection: path-shape, not cascade-content ──────────────
    // A file is stageable if its path matches
    // /<project>/working/<party>/<…>. Unstageable if it matches
    // /<project>/staging/<party>/<transmittal>/<…>. Both are path-shape
    // queries — content/ACL is enforced server-side.

    var WORKSPACE_PEERS = { working: 1, staging: 1, reviewing: 1, incoming: 1 };

    // projectPartySlot returns { project, party, slot, rest } when path
    // matches /<project>/<slot>/<party>/<rest…> for a workspace peer, or
    // null on non-match.
    function projectPartySlot(path) {
        var rel = path.replace(/^\/+|\/+$/g, '').split('/');
        if (rel.length < 3) return null;
        var slot = rel[1].toLowerCase();
        if (!WORKSPACE_PEERS[slot]) return null;
        return { project: rel[0], slot: slot, party: rel[2], rest: rel.slice(3) };
    }

    function isStageableFile(node) {
        if (!node || node.isDir || node.virtual) return false;
        var tree = window.app.modules.tree;
        if (!tree) return false;
        var p = projectPartySlot(tree.pathFor(node));
        return !!(p && p.slot === 'working' && p.rest.length >= 1);
    }
    function isUnstageableFile(node) {
        if (!node || node.isDir || node.virtual) return false;
        var tree = window.app.modules.tree;
        if (!tree) return false;
        var p = projectPartySlot(tree.pathFor(node));
        // archive/<party>/staging/<transmittal-folder>/<file> — at
        // least one folder segment between staging/ and the file.
        return !!(p && p.slot === 'staging' && p.rest.length >= 2);
    }

    // ── Server helpers ─────────────────────────────────────────────────

    // Fetch directory listing JSON. Returns [] on 404.
    async function listDir(absUrl) {
        if (!absUrl.endsWith('/')) absUrl += '/';
        var resp = await fetch(absUrl, {
            headers: { 'Accept': 'application/json' },
            credentials: 'same-origin'
        });
        if (resp.status === 404) return [];
        if (!resp.ok) throw new Error('HTTP ' + resp.status + ' fetching ' + absUrl);
        var data = await resp.json();
        return Array.isArray(data) ? data : [];
    }

    async function fetchStagingFolders(project, party) {
        var entries = await listDir(
            '/' + project + '/staging/' + encodeURIComponent(party) + '/');
        return entries
            .filter(function (e) { return e && e.isDir; })
            .map(function (e) { return e.name; });
    }

    // POST X-ZDDC-Op: mkdir to create a new directory. Idempotent.
    async function mkdir(absUrl) {
        var resp = await fetch(absUrl, {
            method: 'POST',
            headers: { 'X-ZDDC-Op': 'mkdir' },
            credentials: 'same-origin'
        });
        if (!resp.ok) {
            var text = ''; try { text = await resp.text(); } catch (_e) {}
            throw new Error('mkdir ' + absUrl + ' failed (' + resp.status + '): ' + text);
        }
    }

    // POST X-ZDDC-Op: move + X-ZDDC-Destination header. Reuses the
    // file-API move primitive (atomic os.Rename, dual ACL gates).
    async function moveFile(srcUrl, dstUrl) {
        var resp = await fetch(srcUrl, {
            method: 'POST',
            headers: {
                'X-ZDDC-Op': 'move',
                'X-ZDDC-Destination': dstUrl
            },
            credentials: 'same-origin'
        });
        if (!resp.ok) {
            var text = ''; try { text = await resp.text(); } catch (_e) {}
            throw new Error('move ' + srcUrl + ' → ' + dstUrl + ' failed (' + resp.status + '): ' + text);
        }
    }

    // ── Stage picker modal ─────────────────────────────────────────────

    function openStagePicker(initial) {
        return new Promise(function (resolve, reject) {
            var overlay = document.createElement('div');
            overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center;z-index:9000;';
            var box = document.createElement('div');
            box.style.cssText = 'background:var(--bg,#fff);color:var(--fg,#111);padding:1.25rem 1.5rem;border-radius:6px;min-width:28rem;max-width:36rem;box-shadow:0 4px 20px rgba(0,0,0,0.25);';

            var folderList = initial.folders.map(function (name) {
                return '<label style="display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0;cursor:pointer;">' +
                  '<input type="radio" name="stage-target" value="' + escapeHtml(name) + '">' +
                  '<span style="font-family:var(--code,monospace);">' + escapeHtml(name) + '</span>' +
                  '</label>';
            }).join('');

            box.innerHTML =
                '<h2 style="margin:0 0 0.5rem 0;font-size:1.1rem;">Stage ' +
                  initial.fileCount + ' file' + (initial.fileCount === 1 ? '' : 's') + ' to…</h2>' +
                '<p style="margin:0 0 0.5rem 0;font-size:0.85rem;color:#666;">' +
                  'Pick the transmittal folder in <code>staging/</code> these files should join. ' +
                  'You can move them back to <code>working/</code> later if they need correction.' +
                '</p>' +
                '<div style="max-height:14rem;overflow-y:auto;border:1px solid rgba(0,0,0,0.1);padding:0.3rem 0.6rem;margin-bottom:0.5rem;">' +
                  (folderList || '<em style="color:#888;">No existing transmittal folders in staging/.</em>') +
                  '<label style="display:flex;align-items:center;gap:0.5rem;padding:0.25rem 0;cursor:pointer;border-top:1px solid rgba(0,0,0,0.05);margin-top:0.3rem;padding-top:0.5rem;">' +
                    '<input type="radio" name="stage-target" value="__new__">' +
                    '<span><strong>New transmittal folder…</strong></span>' +
                  '</label>' +
                '</div>' +
                '<div id="stage-newname-row" style="display:none;font-size:0.9rem;">' +
                  '<label for="stage-newname">Folder name (ZDDC convention)</label><br>' +
                  '<input id="stage-newname" type="text" style="width:100%;padding:0.3rem;font-family:var(--code,monospace);" ' +
                    'placeholder="YYYY-MM-DD_TRACKING (PURPOSE) - SUBJECT">' +
                  '<div id="stage-newname-feedback" style="font-size:0.8rem;color:#888;margin-top:0.2rem;"></div>' +
                '</div>' +
                '<div style="display:flex;justify-content:flex-end;gap:0.5rem;margin-top:1rem;">' +
                  '<button type="button" id="stage-cancel">Cancel</button>' +
                  '<button type="button" id="stage-submit" class="btn-primary">Stage</button>' +
                '</div>';
            overlay.appendChild(box);
            document.body.appendChild(overlay);

            var newRow = box.querySelector('#stage-newname-row');
            var newInput = box.querySelector('#stage-newname');
            var feedback = box.querySelector('#stage-newname-feedback');
            box.querySelectorAll('input[name="stage-target"]').forEach(function (r) {
                r.addEventListener('change', function () {
                    newRow.style.display = (r.value === '__new__' && r.checked) ? '' : 'none';
                    if (r.value === '__new__' && r.checked) newInput.focus();
                });
            });
            newInput.addEventListener('input', function () {
                var v = newInput.value.trim();
                if (!v) { feedback.textContent = ''; return; }
                var parsed = window.zddc.parseFolder(v);
                if (parsed && parsed.valid) {
                    feedback.style.color = '#2a8';
                    feedback.textContent = '✓ tracking=' + parsed.trackingNumber +
                        ', status=' + parsed.status + ', title=' + parsed.title;
                } else {
                    feedback.style.color = '#c33';
                    feedback.textContent = '✗ does not match YYYY-MM-DD_TRACKING (PURPOSE) - SUBJECT';
                }
            });

            function close() { if (overlay.parentNode) overlay.parentNode.removeChild(overlay); }
            box.querySelector('#stage-cancel').addEventListener('click', function () {
                close(); reject(new Error('cancelled'));
            });
            overlay.addEventListener('click', function (e) {
                if (e.target === overlay) { close(); reject(new Error('cancelled')); }
            });
            box.querySelector('#stage-submit').addEventListener('click', function () {
                var sel = box.querySelector('input[name="stage-target"]:checked');
                if (!sel) { status('Pick a destination folder.', 'error'); return; }
                if (sel.value === '__new__') {
                    var name = newInput.value.trim();
                    var parsed = window.zddc.parseFolder(name);
                    if (!parsed || !parsed.valid) {
                        status('Folder name must conform to ZDDC convention.', 'error');
                        return;
                    }
                    close(); resolve({ create: true, folderName: name });
                } else {
                    close(); resolve({ create: false, folderName: sel.value });
                }
            });
        });
    }

    // ── Unstage picker modal ───────────────────────────────────────────

    function openUnstagePicker(initial) {
        return new Promise(function (resolve, reject) {
            var overlay = document.createElement('div');
            overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center;z-index:9000;';
            var box = document.createElement('div');
            box.style.cssText = 'background:var(--bg,#fff);color:var(--fg,#111);padding:1.25rem 1.5rem;border-radius:6px;min-width:28rem;max-width:36rem;box-shadow:0 4px 20px rgba(0,0,0,0.25);';
            box.innerHTML =
                '<h2 style="margin:0 0 0.5rem 0;font-size:1.1rem;">Unstage ' +
                  initial.fileCount + ' file' + (initial.fileCount === 1 ? '' : 's') + '</h2>' +
                '<p style="margin:0 0 0.5rem 0;font-size:0.85rem;color:#666;">' +
                  'Move these files back into your drafting workspace under <code>working/</code> ' +
                  'so they can be corrected. Stage them again when ready.' +
                '</p>' +
                '<label for="unstage-target" style="font-size:0.9rem;">Destination folder</label>' +
                '<input id="unstage-target" type="text" style="width:100%;padding:0.3rem;font-family:var(--code,monospace);" value="' +
                  escapeHtml(initial.defaultTarget) + '">' +
                '<div style="display:flex;justify-content:flex-end;gap:0.5rem;margin-top:1rem;">' +
                  '<button type="button" id="unstage-cancel">Cancel</button>' +
                  '<button type="button" id="unstage-submit" class="btn-primary">Unstage</button>' +
                '</div>';
            overlay.appendChild(box);
            document.body.appendChild(overlay);

            var input = box.querySelector('#unstage-target');
            function close() { if (overlay.parentNode) overlay.parentNode.removeChild(overlay); }
            box.querySelector('#unstage-cancel').addEventListener('click', function () {
                close(); reject(new Error('cancelled'));
            });
            overlay.addEventListener('click', function (e) {
                if (e.target === overlay) { close(); reject(new Error('cancelled')); }
            });
            box.querySelector('#unstage-submit').addEventListener('click', function () {
                var target = input.value.trim();
                if (!target) { status('Destination is required.', 'error'); return; }
                close(); resolve({ target: target });
            });
        });
    }

    // ── Action drivers ─────────────────────────────────────────────────

    async function invokeStage(node) {
        if (busy) return;
        var tree = window.app.modules.tree;
        if (!tree) return;
        var srcUrl = tree.pathFor(node);
        var info = projectPartySlot(srcUrl);
        if (!info || info.slot !== 'working') {
            status('Stage applies only to files under working/<party>/.', 'error');
            return;
        }
        var stagingBase = '/' + info.project + '/staging/' +
            encodeURIComponent(info.party) + '/';
        var folders;
        try { folders = await fetchStagingFolders(info.project, info.party); }
        catch (e) {
            status('Could not list staging/: ' + (e && e.message ? e.message : e), 'error');
            return;
        }

        var choice;
        try {
            choice = await openStagePicker({ fileCount: 1, folders: folders });
        } catch (_e) { return; }

        busy = true;
        try {
            // Stage is a non-atomic mkdir-then-move (no single composite op).
            // Track whether the folder was freshly created so that, if the
            // move then fails, we can tell the user the folder exists but the
            // file didn't make it — otherwise an empty folder appears with a
            // generic "move failed" and no explanation.
            var createdFolder = false;
            if (choice.create) {
                try {
                    await mkdir(stagingBase + encodeURIComponent(choice.folderName) + '/');
                    createdFolder = true;
                } catch (e) {
                    status((e && e.message) || 'mkdir failed', 'error');
                    return;
                }
            }

            var dstUrl = stagingBase + encodeURIComponent(choice.folderName) + '/' + encodeURIComponent(node.name);
            try {
                await moveFile(srcUrl, dstUrl);
            } catch (e) {
                var msg = (e && e.message) || 'move failed';
                if (createdFolder) {
                    msg += ' — the new folder "' + choice.folderName
                        + '" was created but ' + node.name + ' was not moved into it.';
                }
                status(msg, 'error');
                refreshListing(); // surface the (possibly empty) new folder
                return;
            }
            status('Staged ' + node.name + ' → ' + info.party + '/staging/' + choice.folderName + '/', 'success');
            refreshListing();
        } finally {
            busy = false;
        }
    }

    async function invokeUnstage(node) {
        if (busy) return;
        var tree = window.app.modules.tree;
        if (!tree) return;
        var srcUrl = tree.pathFor(node);
        var info = projectPartySlot(srcUrl);
        if (!info || info.slot !== 'staging') {
            status('Unstage applies only to files under staging/<party>/.', 'error');
            return;
        }
        var defaultTarget = '/' + info.project + '/working/' +
            encodeURIComponent(info.party) + '/';
        var choice;
        try {
            choice = await openUnstagePicker({ fileCount: 1, defaultTarget: defaultTarget });
        } catch (_e) { return; }
        var target = choice.target;
        if (!target.endsWith('/')) target += '/';
        var dstUrl = target + encodeURIComponent(node.name);
        busy = true;
        try {
            try {
                await moveFile(srcUrl, dstUrl);
            } catch (e) {
                status((e && e.message) || 'move failed', 'error');
                return;
            }
            status('Unstaged ' + node.name + ' → ' + target, 'success');
            refreshListing();
        } finally {
            busy = false;
        }
    }

    window.app.modules.stage = {
        isStageableFile: isStageableFile,
        isUnstageableFile: isUnstageableFile,
        invokeStage: invokeStage,
        invokeUnstage: invokeUnstage
    };
})();
