// search-suggest.js — instant typeahead dropdown.
//
// Exposes window.attachSuggestions(inputEl, opts) used by both the header
// search box and the editor's "Insert link" modal. Owns: debounced fetching,
// request ordering, DOM creation, keyboard handling, open/close lifecycle.
//
// opts:
//   onPick(result)        — called when the user selects a row
//   onShowAll(query)      — optional; called when the footer row activates
//   showFooter (bool)     — show the "Show all N matches" footer row
//   container (Element)   — optional parent (defaults to inputEl.parentNode)
(function () {
    var DEBOUNCE_MS = 100;
    var MIN_QUERY_LEN = 2;

    function escapeHTML(s) {
        return String(s)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;');
    }

    function tokenize(s) {
        return s.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
    }

    // highlight bolds the substring spans in `name` that match any of the
    // query tokens (case-insensitive). Overlapping/adjacent spans merge.
    // Returns a safe HTML string.
    function highlight(name, tokens) {
        if (!tokens.length) return escapeHTML(name);
        var lower = name.toLowerCase();
        var spans = [];
        tokens.forEach(function (t) {
            if (!t) return;
            var idx = lower.indexOf(t);
            if (idx >= 0) spans.push([idx, idx + t.length]);
        });
        if (!spans.length) return escapeHTML(name);
        spans.sort(function (a, b) { return a[0] - b[0]; });
        var merged = [spans[0].slice()];
        for (var i = 1; i < spans.length; i++) {
            var last = merged[merged.length - 1];
            if (spans[i][0] <= last[1]) {
                last[1] = Math.max(last[1], spans[i][1]);
            } else {
                merged.push(spans[i].slice());
            }
        }
        var out = '';
        var cursor = 0;
        merged.forEach(function (sp) {
            out += escapeHTML(name.slice(cursor, sp[0]));
            out += '<strong>' + escapeHTML(name.slice(sp[0], sp[1])) + '</strong>';
            cursor = sp[1];
        });
        out += escapeHTML(name.slice(cursor));
        return out;
    }

    function attachSuggestions(inputEl, opts) {
        if (!inputEl) return;
        opts = opts || {};
        var host = opts.container || inputEl.parentNode;
        if (!host) return;
        host.classList.add('suggest-host');

        var dropdown = document.createElement('div');
        dropdown.className = 'suggest-dropdown';
        host.appendChild(dropdown);

        function makeRow(cls, tabbable) {
            var tr = document.createElement('tr');
            tr.className = cls;
            if (tabbable) tr.setAttribute('tabindex', '0');
            var td = document.createElement('td');
            tr.appendChild(td);
            return { tr: tr, td: td };
        }

        var state = {
            results: [],
            total: 0,
            query: '',
            activeIdx: -1,
            open: false,
            reqSeq: 0,
            debounceTimer: null,
            blurTimer: null,
        };

        function rowCount() {
            var n = state.results.length;
            if (state.results.length === 0 && state.query.length >= MIN_QUERY_LEN) {
                return 0; // "no matches" row is non-interactive
            }
            if (opts.showFooter && state.total > state.results.length) n += 1;
            return n;
        }

        function isFooterIdx(idx) {
            return opts.showFooter
                && state.total > state.results.length
                && idx === state.results.length;
        }

        function render() {
            dropdown.textContent = '';
            if (!state.open) {
                dropdown.classList.remove('is-open');
                return;
            }
            var table = document.createElement('table');
            table.className = 'data-table';
            var tbody = document.createElement('tbody');
            table.appendChild(tbody);

            var tokens = tokenize(state.query);
            if (state.results.length === 0) {
                var empty = makeRow('is-empty', false);
                empty.td.textContent = 'No matches';
                tbody.appendChild(empty.tr);
            } else {
                state.results.forEach(function (r, i) {
                    var row = makeRow('suggest-row', true);
                    row.tr.setAttribute('data-idx', String(i));
                    var nameEl = document.createElement('span');
                    nameEl.className = 'suggest-name';
                    nameEl.innerHTML = highlight(r.name, tokens);
                    var pathEl = document.createElement('span');
                    pathEl.className = 'suggest-path';
                    pathEl.textContent = '/' + r.path;
                    row.td.appendChild(nameEl);
                    row.td.appendChild(pathEl);
                    if (i === state.activeIdx) row.tr.classList.add('is-active');
                    row.tr.addEventListener('mousedown', function (e) {
                        // mousedown (not click) so the input doesn't blur-close
                        // the dropdown before the pick handler fires.
                        e.preventDefault();
                        pick(i);
                    });
                    tbody.appendChild(row.tr);
                });
                if (opts.showFooter && state.total > state.results.length) {
                    var footer = makeRow('suggest-row suggest-footer', true);
                    footer.td.textContent = 'Show all ' + state.total + ' matches';
                    var footerIdx = state.results.length;
                    if (state.activeIdx === footerIdx) footer.tr.classList.add('is-active');
                    footer.tr.addEventListener('mousedown', function (e) {
                        e.preventDefault();
                        pickFooter();
                    });
                    tbody.appendChild(footer.tr);
                }
            }
            dropdown.appendChild(table);
            dropdown.classList.add('is-open');
        }

        function pick(idx) {
            var r = state.results[idx];
            if (!r) return;
            close();
            if (opts.onPick) opts.onPick(r);
        }

        function pickFooter() {
            close();
            if (opts.onShowAll) {
                opts.onShowAll(state.query);
            } else if (inputEl.form) {
                inputEl.form.submit();
            } else {
                window.location.href = '/?q=' + encodeURIComponent(state.query);
            }
        }

        function open() {
            state.open = true;
            render();
        }

        function close() {
            state.open = false;
            state.activeIdx = -1;
            render();
        }

        function fetchResults(query) {
            var seq = ++state.reqSeq;
            fetch('/_search?q=' + encodeURIComponent(query), {
                credentials: 'same-origin',
                headers: { 'Accept': 'application/json' },
            }).then(function (r) {
                if (!r.ok) throw new Error('HTTP ' + r.status);
                return r.json();
            }).then(function (resp) {
                if (seq !== state.reqSeq) return; // stale
                state.results = resp.results || [];
                state.total = resp.total || 0;
                state.query = resp.query || query;
                state.activeIdx = -1;
                open();
            }).catch(function () {
                if (seq !== state.reqSeq) return;
                state.results = [];
                state.total = 0;
                close();
            });
        }

        function onInput() {
            var q = inputEl.value.trim();
            state.query = q;
            if (state.debounceTimer) clearTimeout(state.debounceTimer);
            if (q.length < MIN_QUERY_LEN) {
                state.reqSeq++; // invalidate any in-flight response
                state.results = [];
                state.total = 0;
                close();
                return;
            }
            state.debounceTimer = setTimeout(function () {
                fetchResults(q);
            }, DEBOUNCE_MS);
        }

        function moveActive(delta) {
            var n = rowCount();
            if (n === 0) return;
            var next = state.activeIdx + delta;
            if (next < 0) next = n - 1;
            if (next >= n) next = 0;
            state.activeIdx = next;
            render();
            // Keep the active row in view.
            var active = dropdown.querySelector('tr.is-active');
            if (active && active.scrollIntoView) {
                try { active.scrollIntoView({ block: 'nearest' }); } catch (e) {}
            }
        }

        function activateCurrent() {
            if (state.activeIdx < 0) return false;
            if (isFooterIdx(state.activeIdx)) {
                pickFooter();
                return true;
            }
            pick(state.activeIdx);
            return true;
        }

        inputEl.addEventListener('input', onInput);
        inputEl.addEventListener('focus', function () {
            if (state.blurTimer) {
                clearTimeout(state.blurTimer);
                state.blurTimer = null;
            }
            if (inputEl.value.trim().length >= MIN_QUERY_LEN
                    && (state.results.length || state.query)) {
                open();
            }
        });
        inputEl.addEventListener('blur', function () {
            // Delay so click/mousedown on a row still resolves.
            state.blurTimer = setTimeout(close, 150);
        });
        inputEl.addEventListener('keydown', function (e) {
            if (e.key === 'ArrowDown') {
                if (!state.open) return;
                e.preventDefault();
                moveActive(1);
            } else if (e.key === 'ArrowUp') {
                if (!state.open) return;
                e.preventDefault();
                moveActive(-1);
            } else if (e.key === 'Escape') {
                if (!state.open) return;
                e.preventDefault();
                close();
            } else if (e.key === 'Enter') {
                if (state.open && state.activeIdx >= 0) {
                    e.preventDefault();
                    activateCurrent();
                }
                // else: native form submit behaviour (full results page)
            } else if (e.key === 'Tab') {
                if (!state.open || rowCount() === 0) return;
                e.preventDefault();
                moveActive(e.shiftKey ? -1 : 1);
            }
        });

        // Click outside the host closes the dropdown.
        document.addEventListener('mousedown', function (e) {
            if (!state.open) return;
            if (host.contains(e.target)) return;
            close();
        });

        return {
            close: close,
            destroy: function () {
                if (dropdown.parentNode) dropdown.parentNode.removeChild(dropdown);
                host.classList.remove('suggest-host');
            },
        };
    }

    window.attachSuggestions = attachSuggestions;

    // Auto-bind to the header search input. Header search submits the form
    // for the "show all" action; we route to a navigate-on-pick handler.
    document.addEventListener('DOMContentLoaded', function () {
        var input = document.querySelector('header .search-input');
        if (!input) return;
        attachSuggestions(input, {
            showFooter: true,
            onPick: function (r) { window.location.href = r.url; },
        });
    });
})();
