(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);
    }

    // isValidWikiTarget mirrors the Go validator in wikilinks.go — absolute
    // path, no empty/dot segments. Used to gate the INSERT confirm button.
    function isValidWikiTarget(p) {
        if (!p || p[0] !== '/') return false;
        var trimmed = p.replace(/^\/+|\/+$/g, '');
        if (trimmed === '') return true;
        var segs = trimmed.split('/');
        for (var i = 0; i < segs.length; i++) {
            if (segs[i] === '' || segs[i] === '.' || segs[i] === '..') return false;
        }
        return true;
    }

    function insertWikilink() {
        var sel = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);

        var container = document.createElement('div');

        var targetWrap = document.createElement('div');
        var targetInput = document.createElement('input');
        targetInput.type = 'text';
        targetInput.className = 'input';
        targetInput.placeholder = 'Page path or search…';
        targetWrap.appendChild(targetInput);

        var displayInput = document.createElement('input');
        displayInput.type = 'text';
        displayInput.className = 'input';
        displayInput.placeholder = 'Display text (optional)';
        if (sel) displayInput.value = sel;

        container.appendChild(targetWrap);
        container.appendChild(displayInput);

        var handle = openModal({
            title: 'Insert link',
            body: container,
            confirm: {
                label: 'INSERT',
                initiallyDisabled: true,
                onConfirm: function () {
                    var target = targetInput.value.trim();
                    if (!isValidWikiTarget(target)) return;
                    var display = displayInput.value.trim();
                    handle.close();
                    insertAtCursor(display ? '[[' + target + '::' + display + ']]' : '[[' + target + ']]');
                }
            }
        });

        function updateConfirm() {
            handle.setConfirmDisabled(!isValidWikiTarget(targetInput.value.trim()));
        }
        targetInput.addEventListener('input', updateConfirm);

        window.attachSuggestions(targetInput, {
            showFooter: false,
            container: targetWrap,
            onPick: function (r) {
                targetInput.value = '/' + r.path;
                updateConfirm();
                displayInput.focus();
                displayInput.select();
            }
        });
    }

    // --- 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);
})();
