// conflict.js — shared conflict-resolution dialog for the browse tool.
//
// Surfaced when a save loses an optimistic-concurrency race: the file
// changed on the server since the user loaded it (the editor sends an
// If-Match precondition; the master replies 412). Rather than clobber the
// other writer, the editor opens this dialog showing a mine-vs-theirs diff
// and four choices.
//
// Deliberately CALLBACK-DRIVEN: it never calls saveFile / showFilePreview
// itself — the caller supplies onOverwrite / onReload / onSaveCopy. That
// keeps it reusable by a second consumer (the deferred Phase 5 cache-outbox
// conflict UI, which would resolve `.zddc-outbox/<id>.conflict-<ts>/` entries
// against new server endpoints rather than the live file).
//
// Reuses the modal shell + diff markup conventions from history.js and the
// shared css/history.css classes (md-history-*, md-diff-*) — no new CSS.
(function () {
    'use strict';

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

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

    // Render a line diff of base→mine into `pane` (theirs treated as the
    // base, so additions are what this save would introduce). Mirrors the
    // history.js diff view.
    function renderDiff(pane, theirsText, mineText) {
        pane.innerHTML = '';
        var ops = (window.zddc && window.zddc.diff)
            ? window.zddc.diff.lines(theirsText, mineText)
            : null;
        var diff = document.createElement('div');
        diff.className = 'md-diff';
        if (!ops) {
            diff.textContent = 'Diff unavailable (diff module not loaded).';
            pane.appendChild(diff);
            return;
        }
        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 g = document.createElement('span');
            g.className = 'md-diff-gutter';
            g.textContent = op.type === 'add' ? '+' : (op.type === 'del' ? '-' : ' ');
            var t = document.createElement('span');
            t.className = 'md-diff-text';
            t.textContent = op.text;
            line.appendChild(g);
            line.appendChild(t);
            diff.appendChild(line);
        });
        if (unchanged) {
            var same = document.createElement('div');
            same.className = 'md-diff-line md-diff-eq';
            same.textContent = '(no differences — your copy matches the server)';
            diff.appendChild(same);
        }
        pane.appendChild(diff);
        var s = window.zddc.diff.stats(ops);
        var stat = document.createElement('p');
        stat.className = 'md-history-hint';
        stat.textContent = 'Your version vs. current server: +' + s.added + ' / −' + s.removed;
        pane.appendChild(stat);
    }

    // open(opts) → Promise<'overwrite' | 'reload' | 'savecopy' | 'cancel'>
    //
    // opts:
    //   filename     — display name (e.g. node.name)
    //   mineText     — the user's current (unsaved) content, for the diff
    //   theirsText   — current server content (string), OR…
    //   fetchTheirs  — async () => string  — lazy fetch of current server content
    //   onOverwrite  — async () => void    — re-save, forcing past the conflict
    //   onReload     — async () => void    — discard mine, reload from server
    //   onSaveCopy   — async () => void    — write mine to a sibling path (optional)
    //
    // The matching callback runs when its button is clicked; on success the
    // dialog closes and resolves with the action name. On callback error the
    // dialog stays open (a toast explains) so the user can pick another path.
    // Cancel / Esc / backdrop resolve 'cancel' and leave the editor untouched.
    function open(opts) {
        opts = opts || {};
        return new Promise(function (resolve) {
            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 = 'Conflict — ' + (opts.filename || 'file');
            var body = document.createElement('div');
            body.className = 'md-history-body';
            box.appendChild(title);
            box.appendChild(body);
            overlay.appendChild(box);
            document.body.appendChild(overlay);

            var settled = false;
            function close() {
                if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
                document.removeEventListener('keydown', onKey);
            }
            function finish(result) {
                if (settled) return;
                settled = true;
                close();
                resolve(result);
            }
            function onKey(e) { if (e.key === 'Escape') finish('cancel'); }
            document.addEventListener('keydown', onKey);
            overlay.addEventListener('mousedown', function (e) {
                if (e.target === overlay) finish('cancel');
            });

            var hint = document.createElement('p');
            hint.className = 'md-history-hint';
            hint.textContent = '"' + (opts.filename || 'This file')
                + '" was changed by someone else since you opened it. '
                + 'Pick how to resolve — nothing is saved until you choose.';
            body.appendChild(hint);

            var diffPane = document.createElement('div');
            diffPane.textContent = 'Loading current server version…';
            body.appendChild(diffPane);

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

            function makeBtn(label, primary) {
                var b = document.createElement('button');
                b.type = 'button';
                b.textContent = label;
                if (primary) b.className = 'btn-primary';
                footer.appendChild(b);
                return b;
            }
            var overwriteBtn = makeBtn('Overwrite (keep mine)');
            var reloadBtn = makeBtn('Discard mine — reload theirs');
            var copyBtn = opts.onSaveCopy ? makeBtn('Save a copy') : null;
            var cancelBtn = makeBtn('Cancel', true);

            function setBusy(busy) {
                [overwriteBtn, reloadBtn, copyBtn, cancelBtn].forEach(function (b) {
                    if (b) b.disabled = busy;
                });
            }

            // Each action runs its callback; on success close+resolve, on
            // error toast and re-enable so the user can try another path.
            function wire(btn, fn, result) {
                if (!btn) return;
                btn.addEventListener('click', function () {
                    setBusy(true);
                    Promise.resolve()
                        .then(function () { return fn ? fn() : undefined; })
                        .then(function () { finish(result); })
                        .catch(function (e) {
                            toast('Could not ' + result + ': ' + (e && e.message ? e.message : e), 'error');
                            setBusy(false);
                        });
                });
            }
            wire(overwriteBtn, opts.onOverwrite, 'overwrite');
            wire(reloadBtn, opts.onReload, 'reload');
            wire(copyBtn, opts.onSaveCopy, 'savecopy');
            cancelBtn.addEventListener('click', function () { finish('cancel'); });

            // Resolve the "theirs" text (eagerly provided or lazily fetched)
            // then render the diff. A fetch failure leaves the actions usable
            // — the diff is an aid, not a gate.
            Promise.resolve()
                .then(function () {
                    if (typeof opts.theirsText === 'string') return opts.theirsText;
                    if (opts.fetchTheirs) return opts.fetchTheirs();
                    return null;
                })
                .then(function (theirs) {
                    if (settled) return;
                    if (theirs == null) {
                        diffPane.textContent = 'Could not load the current server version for comparison.';
                        return;
                    }
                    renderDiff(diffPane, theirs, opts.mineText || '');
                })
                .catch(function (e) {
                    if (settled) return;
                    diffPane.textContent = 'Could not load the current server version: '
                        + (e && e.message ? e.message : e);
                });
        });
    }

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