(function () {
    var textarea = document.getElementById('editor');
    if (!textarea) return;

    var form = textarea.closest('form');

    // --- DOM helpers ---

    // Route every edit through execCommand so the browser's native undo/redo
    // stack is preserved. Direct assignment to textarea.value would wipe it.
    function replaceRange(start, end, text) {
        textarea.focus();
        textarea.selectionStart = start;
        textarea.selectionEnd = end;
        document.execCommand('insertText', false, text);
    }

    function wrap(before, after, placeholder) {
        var start = textarea.selectionStart;
        var end = textarea.selectionEnd;
        var hadSelection = end > start;
        var selected = hadSelection ? textarea.value.slice(start, end) : placeholder;
        replaceRange(start, end, before + selected + after);
        if (!hadSelection) {
            textarea.selectionStart = start + before.length;
            textarea.selectionEnd = start + before.length + placeholder.length;
        }
    }

    function linePrefix(prefix) {
        var start = textarea.selectionStart;
        var lineStart = textarea.value.lastIndexOf('\n', start - 1) + 1;
        replaceRange(lineStart, lineStart, prefix);
        textarea.selectionStart = textarea.selectionEnd = start + prefix.length;
    }

    function insertAtCursor(s) {
        replaceRange(textarea.selectionStart, textarea.selectionEnd, s);
    }

    function applyResult(result) {
        var oldText = textarea.value;
        var newText = result.text;
        var prefixLen = 0;
        var maxPrefix = Math.min(oldText.length, newText.length);
        while (prefixLen < maxPrefix && oldText.charCodeAt(prefixLen) === newText.charCodeAt(prefixLen)) {
            prefixLen++;
        }
        var oldEnd = oldText.length;
        var newEnd = newText.length;
        while (oldEnd > prefixLen && newEnd > prefixLen
                && oldText.charCodeAt(oldEnd - 1) === newText.charCodeAt(newEnd - 1)) {
            oldEnd--;
            newEnd--;
        }
        replaceRange(prefixLen, oldEnd, newText.slice(prefixLen, newEnd));
        textarea.selectionStart = textarea.selectionEnd = result.cursor;
    }

    function applyTableOp(fn, arg) {
        var result = arg !== undefined
            ? fn(textarea.value, textarea.selectionStart, arg)
            : fn(textarea.value, textarea.selectionStart);
        if (result) applyResult(result);
    }

    function promptDisplayText(initial, onDone) {
        var input = document.createElement('input');
        input.type = 'text';
        input.className = 'modal-input';
        input.placeholder = 'Display text (optional)';
        if (initial) input.value = initial;
        var handle = openModal({
            title: 'Insert link — display text?',
            body: input,
            confirm: {
                label: 'INSERT',
                onConfirm: function () {
                    handle.close();
                    onDone(input.value.trim());
                }
            }
        });
    }

    function insertWikilink() {
        var sel = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
        openTreePicker({
            title: 'Insert link',
            mode: 'any',
            initialPath: '/',
            confirmLabel: 'NEXT',
            onSelect: function (path, kind) {
                if (kind === 'folder') {
                    promptDisplayText(sel, function (display) {
                        insertAtCursor(display ? '[[' + path + '::' + display + ']]' : '[[' + path + ']]');
                    });
                } else {
                    var name = path.split('/').pop();
                    insertAtCursor('[' + (sel || name) + '](' + path + ')');
                }
            }
        });
    }

    // --- Actions ---

    var T = EditorTables;
    var L = EditorLists;
    var D = EditorDates;
    var M = EditorMovie;

    var actions = {
        save:           function () { form.submit(); },
        bold:           function () { wrap('**', '**', 'bold text'); },
        italic:         function () { wrap('*', '*', 'italic text'); },
        h1:             function () { linePrefix('# '); },
        h2:             function () { linePrefix('## '); },
        h3:             function () { linePrefix('### '); },
        code:           function () { wrap('`', '`', 'code'); },
        codeblock:      function () { wrap('```\n', '\n```', 'code'); },
        quote:          function () { linePrefix('> '); },
        link:           function () { wrap('[', '](url)', 'link text'); },
        wikilink:       insertWikilink,
        ul:             function () { linePrefix('- '); },
        ol:             function () { linePrefix('1. '); },
        task:           function () { linePrefix('- [ ] '); },
        hr:             function () { wrap('\n\n---\n\n', '', ''); },
        fmttable:       function () { applyTableOp(T.formatTableText); },
        tblalignleft:   function () { applyTableOp(T.setColumnAlignment, 'left'); },
        tblaligncenter: function () { applyTableOp(T.setColumnAlignment, 'center'); },
        tblalignright:  function () { applyTableOp(T.setColumnAlignment, 'right'); },
        tblinsertcol:   function () { applyTableOp(T.insertColumn); },
        tbldeletecol:   function () { applyTableOp(T.deleteColumn); },
        tblinsertrow:   function () { applyTableOp(T.insertRow); },
        tbldeleterow:   function () { applyTableOp(T.deleteRow); },
        dateiso:        function () { insertAtCursor(D.isoDate()); },
        datelong:       function () { insertAtCursor(D.longDate()); },
        movie:          function () { M.run(textarea); },
        wide:           function () {
            var enabled = !document.body.classList.contains('editor-wide');
            document.body.classList.toggle('editor-wide', enabled);
            sessionStorage.setItem('editor-wide', enabled ? '1' : '0');
        },
    };

    // --- Keyboard shortcut registration ---

    var keyMap = {};
    document.querySelectorAll('[data-action]').forEach(function (btn) {
        btn.addEventListener('click', function () {
            var action = actions[btn.dataset.action];
            if (action) action();
        });
        if (btn.dataset.key) {
            keyMap[btn.dataset.key] = actions[btn.dataset.action];
        }
    });

    document.addEventListener('keydown', function (e) {
        if (!e.altKey || !e.shiftKey) return;
        // Shift+digit produces a layout-dependent character in e.key (e.g. "!"
        // on US, "!" on DE), so fall back to e.code for digit rows.
        var key = /^Digit[0-9]$/.test(e.code) ? e.code.slice(5) : e.key;
        var action = keyMap[key];
        if (action) {
            e.preventDefault();
            action();
        }
    });

    // --- Textarea key handling ---

    textarea.addEventListener('keydown', function (e) {
        if (e.key === 'Delete' && e.shiftKey) {
            var result = T.deleteRow(textarea.value, textarea.selectionStart)
                      || L.deleteOrderedLine(textarea.value, textarea.selectionStart);
            if (!result) return;
            e.preventDefault();
            applyResult(result);
            return;
        }
        if (e.key === 'Enter' && e.shiftKey) {
            var result = T.insertRowBelow(textarea.value, textarea.selectionStart);
            if (!result) return;
            e.preventDefault();
            applyResult(result);
            return;
        }
        if (e.key !== 'Enter') return;
        var result = L.handleEnterKey(textarea.value, textarea.selectionStart);
        if (!result) return;
        e.preventDefault();
        applyResult(result);
    });

    // --- Dropdowns ---

    document.querySelectorAll('.dropdown-toggle').forEach(wireDropdown);
})();
