// history.js — markdown edit-history viewer for the browse tool.
//
// Surfaced by events.js as a "History…" right-click item on files in a
// history:true cascade subtree (working/). Server mode only — the audit
// trail (who saved when) is stamped server-side, so there's no offline
// equivalent.
//
// Talks to the zddc-server history endpoints on the file's own URL:
//   GET <url>?history=1      → JSON [{ts, by, id, bytes, current}]
//   GET <url>?history=<id>   → that version's raw bytes (id = snapshot filename)
// Restore re-PUTs a chosen version's bytes to <url>, which the server
// records as a new version (forward-only; never destructive).
//
// Diffs are computed client-side via window.zddc.diff (shared/diff.js).

(function () {
    'use strict';

    var escapeHtml = window.app.modules.util.escapeHtml;

    function toast(msg, kind) {
        if (window.zddc && typeof window.zddc.toast === 'function') {
            window.zddc.toast(msg, kind || 'info');
        }
    }

    // Append ?history=<v> (or &history=) to a file URL.
    function histURL(baseURL, v) {
        var sep = baseURL.indexOf('?') === -1 ? '?' : '&';
        return baseURL + sep + 'history=' + encodeURIComponent(v);
    }

    function fmtTime(ts) {
        var d = new Date(ts);
        if (isNaN(d.getTime())) return ts || '';
        return d.toLocaleString();
    }

    var fmtBytes = window.app.modules.util.fmtSize;

    // Can the principal write (restore) to this file? Mirrors the
    // events.js Rename/Delete gating: verbs===undefined means a non-zddc
    // backend (no cascade signal) → allow the attempt; otherwise check w.
    function canRestore(node) {
        if (!node || !node.url) return false;
        if (!window.zddc || !window.zddc.cap) return true;
        if (typeof node.verbs !== 'string') return true;
        return window.zddc.cap.has(node, 'w');
    }

    async function fetchList(node) {
        var resp = await fetch(histURL(node.url, '1'), {
            headers: { 'Accept': 'application/json' },
            credentials: 'same-origin'
        });
        if (!resp.ok) throw new Error('HTTP ' + resp.status);
        var data = await resp.json();
        return Array.isArray(data) ? data : [];
    }

    async function fetchVersion(node, id) {
        var resp = await fetch(histURL(node.url, id), { credentials: 'same-origin' });
        if (!resp.ok) throw new Error('HTTP ' + resp.status);
        return await resp.text();
    }

    // ── Modal shell ──────────────────────────────────────────────────────
    // One overlay; its body is swapped between the list, a diff, and a
    // single-version view. Returns { overlay, body, setTitle, close }.
    function makeModal(titleText) {
        var overlay = document.createElement('div');
        overlay.className = 'modal-overlay md-history-overlay';
        overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom: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.className = 'md-history-box';

        var title = document.createElement('h2');
        title.className = 'md-history-title';
        title.textContent = titleText;

        var body = document.createElement('div');
        body.className = 'md-history-body';

        box.appendChild(title);
        box.appendChild(body);
        overlay.appendChild(box);
        document.body.appendChild(overlay);

        function close() {
            if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
            document.removeEventListener('keydown', onKey);
        }
        function onKey(e) { if (e.key === 'Escape') close(); }
        document.addEventListener('keydown', onKey);
        overlay.addEventListener('mousedown', function (e) {
            if (e.target === overlay) close();
        });

        return {
            overlay: overlay,
            body: body,
            setTitle: function (t) { title.textContent = t; },
            close: close
        };
    }

    function footerBar() {
        var f = document.createElement('div');
        f.className = 'md-history-footer';
        return f;
    }

    function button(label, opts) {
        opts = opts || {};
        var b = document.createElement('button');
        b.type = 'button';
        b.textContent = label;
        if (opts.primary) b.className = 'btn-primary';
        if (opts.disabled) b.disabled = true;
        if (opts.onClick) b.addEventListener('click', opts.onClick);
        return b;
    }

    // ── List view ──────────────────────────────────────────────────────
    function renderList(modal, node, entries) {
        modal.setTitle('History — ' + node.name);
        var body = modal.body;
        body.innerHTML = '';

        if (!entries.length) {
            var empty = document.createElement('p');
            empty.className = 'md-history-empty';
            empty.textContent = 'No saved versions yet. Each save of this file is recorded here.';
            body.appendChild(empty);
            var f0 = footerBar();
            f0.appendChild(button('Close', { onClick: modal.close }));
            body.appendChild(f0);
            return;
        }

        var hint = document.createElement('p');
        hint.className = 'md-history-hint';
        hint.textContent = 'Newest first. Select two versions to diff.';
        body.appendChild(hint);

        var list = document.createElement('div');
        list.className = 'md-history-list';
        var selected = []; // shas, in click order (max 2)

        var diffBtn;
        function syncDiffBtn() {
            if (diffBtn) diffBtn.disabled = selected.length !== 2;
        }

        entries.forEach(function (ent) {
            var row = document.createElement('div');
            row.className = 'md-history-row' + (ent.current ? ' is-current' : '');

            var cb = document.createElement('input');
            cb.type = 'checkbox';
            cb.className = 'md-history-pick';
            cb.addEventListener('change', function () {
                if (cb.checked) {
                    selected.push(ent.id);
                    // Keep at most two: drop the oldest selection.
                    if (selected.length > 2) {
                        var dropped = selected.shift();
                        var others = list.querySelectorAll('.md-history-pick');
                        others.forEach(function (o, i) {
                            if (o !== cb && entries[i] && entries[i].id === dropped) o.checked = false;
                        });
                    }
                } else {
                    selected = selected.filter(function (s) { return s !== ent.id; });
                }
                syncDiffBtn();
            });

            var meta = document.createElement('div');
            meta.className = 'md-history-meta';
            meta.innerHTML =
                '<span class="md-history-time">' + escapeHtml(fmtTime(ent.ts)) + '</span>' +
                '<span class="md-history-by">' + escapeHtml(ent.by || '—') + '</span>' +
                '<span class="md-history-size">' + escapeHtml(fmtBytes(ent.bytes)) + '</span>' +
                (ent.current ? '<span class="md-history-badge">current</span>' : '');

            var actions = document.createElement('div');
            actions.className = 'md-history-actions';
            actions.appendChild(button('View', {
                onClick: function () { renderView(modal, node, ent, entries); }
            }));
            if (!ent.current && canRestore(node)) {
                actions.appendChild(button('Restore', {
                    onClick: function () { restore(modal, node, ent); }
                }));
            }

            row.appendChild(cb);
            row.appendChild(meta);
            row.appendChild(actions);
            list.appendChild(row);
        });

        body.appendChild(list);

        var f = footerBar();
        diffBtn = button('Diff selected', {
            primary: true, disabled: true,
            onClick: function () {
                if (selected.length !== 2) return;
                // Order oldest→newest by the entries' position (newest
                // first in the list), so the diff reads old → new.
                var picks = entries.filter(function (e) { return selected.indexOf(e.id) !== -1; });
                picks.sort(function (a, b) { return (a.ts < b.ts ? -1 : 1); });
                renderDiff(modal, node, picks[0], picks[1], entries);
            }
        });
        f.appendChild(diffBtn);
        f.appendChild(button('Close', { onClick: modal.close }));
        body.appendChild(f);
    }

    // ── Single-version view ──────────────────────────────────────────────
    async function renderView(modal, node, ent, entries) {
        modal.setTitle('Version — ' + fmtTime(ent.ts));
        var body = modal.body;
        body.innerHTML = '<p class="md-history-hint">Loading…</p>';
        var text;
        try {
            text = await fetchVersion(node, ent.id);
        } catch (e) {
            body.innerHTML = '';
            var err = document.createElement('p');
            err.className = 'md-history-empty';
            err.textContent = 'Could not load this version: ' + (e.message || e);
            body.appendChild(err);
            return;
        }
        body.innerHTML = '';
        var meta = document.createElement('p');
        meta.className = 'md-history-hint';
        meta.textContent = (ent.by || '—') + ' · ' + fmtTime(ent.ts);
        body.appendChild(meta);

        var pre = document.createElement('pre');
        pre.className = 'md-history-pre';
        pre.textContent = text;
        body.appendChild(pre);

        var f = footerBar();
        f.appendChild(button('Back', { onClick: function () { renderList(modal, node, entries); } }));
        if (!ent.current && canRestore(node)) {
            f.appendChild(button('Restore this version', {
                primary: true, onClick: function () { restore(modal, node, ent); }
            }));
        }
        body.appendChild(f);
    }

    // ── Diff view ─────────────────────────────────────────────────────────
    async function renderDiff(modal, node, oldEnt, newEnt, entries) {
        modal.setTitle('Diff');
        var body = modal.body;
        body.innerHTML = '<p class="md-history-hint">Loading…</p>';
        var oldText, newText;
        try {
            oldText = await fetchVersion(node, oldEnt.id);
            newText = await fetchVersion(node, newEnt.id);
        } catch (e) {
            body.innerHTML = '';
            var err = document.createElement('p');
            err.className = 'md-history-empty';
            err.textContent = 'Could not load versions: ' + (e.message || e);
            body.appendChild(err);
            return;
        }
        body.innerHTML = '';

        var hdr = document.createElement('p');
        hdr.className = 'md-history-hint';
        hdr.innerHTML =
            '<span class="md-diff-old">' + escapeHtml(fmtTime(oldEnt.ts)) + ' · ' + escapeHtml(oldEnt.by || '—') + '</span>' +
            ' → ' +
            '<span class="md-diff-new">' + escapeHtml(fmtTime(newEnt.ts)) + ' · ' + escapeHtml(newEnt.by || '—') + '</span>';
        body.appendChild(hdr);

        var ops = (window.zddc && window.zddc.diff)
            ? window.zddc.diff.lines(oldText, newText)
            : null;

        var pane = document.createElement('div');
        pane.className = 'md-diff';
        if (!ops) {
            pane.textContent = 'Diff unavailable (diff module not loaded).';
        } else {
            var unchanged = true;
            ops.forEach(function (op) {
                if (op.type !== 'eq') unchanged = false;
                var line = document.createElement('div');
                line.className = 'md-diff-line md-diff-' + op.type;
                var gutter = op.type === 'add' ? '+' : (op.type === 'del' ? '-' : ' ');
                var g = document.createElement('span');
                g.className = 'md-diff-gutter';
                g.textContent = gutter;
                var t = document.createElement('span');
                t.className = 'md-diff-text';
                t.textContent = op.text;
                line.appendChild(g);
                line.appendChild(t);
                pane.appendChild(line);
            });
            if (unchanged) {
                var same = document.createElement('div');
                same.className = 'md-diff-line md-diff-eq';
                same.textContent = '(no differences)';
                pane.appendChild(same);
            }
        }
        body.appendChild(pane);

        if (window.zddc && window.zddc.diff && ops) {
            var s = window.zddc.diff.stats(ops);
            var statline = document.createElement('p');
            statline.className = 'md-history-hint';
            statline.textContent = '+' + s.added + ' / −' + s.removed;
            body.appendChild(statline);
        }

        var f = footerBar();
        f.appendChild(button('Back', { onClick: function () { renderList(modal, node, entries); } }));
        body.appendChild(f);
    }

    // ── Restore ───────────────────────────────────────────────────────────
    async function restore(modal, node, ent) {
        if (!confirm('Restore the version from ' + fmtTime(ent.ts) + '?\nThis is saved as a new version — nothing is lost.')) {
            return;
        }
        // The restore itself (the PUT) is the operation that can "fail".
        // Keep it in its own try so a later error while refreshing the UI
        // can't surface a misleading "Restore failed" after the restore has
        // already been persisted.
        try {
            var text = await fetchVersion(node, ent.id);
            var resp = await fetch(node.url, {
                method: 'PUT',
                credentials: 'same-origin',
                headers: { 'Content-Type': 'text/markdown' },
                body: text
            });
            if (!resp.ok) throw new Error('HTTP ' + resp.status);
        } catch (e) {
            toast('Restore failed: ' + (e.message || e), 'error');
            return;
        }
        toast('Restored version from ' + fmtTime(ent.ts), 'success');
        // Best-effort UI refresh — the restore already succeeded, so a
        // failure here is logged but never reported as a restore failure.
        try {
            var entries = await fetchList(node);
            renderList(modal, node, entries);
            // If the file is open in the preview pane, reload it.
            var preview = window.app && window.app.modules && window.app.modules.preview;
            if (preview && typeof preview.showFilePreview === 'function') {
                preview.showFilePreview(node);
            }
        } catch (_e) { /* refresh is best-effort; restore is done */ }
    }

    // ── Entry point ─────────────────────────────────────────────────────
    async function open(node) {
        if (!node || !node.url) {
            toast('History is only available in server mode.', 'error');
            return;
        }
        var modal = makeModal('History — ' + node.name);
        modal.body.innerHTML = '<p class="md-history-hint">Loading…</p>';
        try {
            var entries = await fetchList(node);
            renderList(modal, node, entries);
        } catch (e) {
            modal.body.innerHTML = '';
            var err = document.createElement('p');
            err.className = 'md-history-empty';
            err.textContent = 'Could not load history: ' + (e.message || e);
            modal.body.appendChild(err);
            var f = footerBar();
            f.appendChild(button('Close', { onClick: modal.close }));
            modal.body.appendChild(f);
        }
    }

    window.app.modules.history = { open: open };
})();
