// wikicomplete.js — the `[[` wikilink autocomplete source for CodeMirror.
//
// A level-by-level folder/file browser scoped to the path typed so far. It
// fires only once the `[[` token's content begins with `/` (targets are
// absolute; free-text search lives in the toolbar modal instead). The content
// is split into a parent path (up to and including the last `/`) and a partial
// segment (the text after it); the parent's children are fetched from the
// existing `?tree=1` endpoint and filtered to names containing the partial
// (case-insensitive substring).
//
// Picking a folder inserts `<name>/` and re-opens the popup to drill one level
// deeper; picking a file inserts `<name>` and stops. Only the current segment
// is replaced, so the trailing `]]` stays put and the cursor parks before it,
// leaving room to type a `::display` alias. Exposes window.WikiComplete.source
// for main.js to register via CM's autocompletion().
window.WikiComplete = (function () {
    // treeURL builds the `?tree=1` request URL for an absolute parent path,
    // percent-encoding each segment. A leading/trailing slash is tolerated;
    // root resolves to `/?tree=1`.
    function treeURL(parent) {
        var trimmed = parent.replace(/^\/+/, '').replace(/\/+$/, '');
        if (trimmed === '') return '/?tree=1';
        var enc = trimmed.split('/').map(encodeURIComponent).join('/');
        return '/' + enc + '/?tree=1';
    }

    function fetchTree(parent) {
        return fetch(treeURL(parent), {
            credentials: 'same-origin',
            headers: { 'Accept': 'application/json' },
        }).then(function (r) {
            // A 404 means the parent folder doesn't exist (typo, or a path under
            // a file) — treat it as "no completions", not an error.
            if (r.status === 404) return null;
            if (!r.ok) throw new Error('HTTP ' + r.status);
            return r.json();
        });
    }

    // makeApply builds the apply() for a chosen entry. It replaces only the
    // current segment ([from, to]); the trailing `]]` is untouched, so the
    // cursor ends up parked before it. Folders append `/` and re-open the popup
    // to drill into the next level; files terminate.
    function makeApply(name, kind) {
        return function (view, completion, from, to) {
            var isFolder = kind === 'folder';
            var insert = isFolder ? name + '/' : name;
            view.dispatch({
                changes: { from: from, to: to, insert: insert },
                selection: { anchor: from + insert.length },
                scrollIntoView: true,
            });
            if (isFolder) {
                // Re-open after the transaction so the completion plugin sees
                // the updated document (the next level's parent path).
                setTimeout(function () { CM.startCompletion(view); }, 0);
            }
        };
    }

    // CM completion source. Activates when `[[` is followed by content that
    // begins with `/`. The content is split at its last `/` into a parent path
    // and a partial segment; the parent's children are fetched, filtered to
    // names containing the partial (case-insensitive substring), and offered
    // with name-only labels.
    function source(context) {
        var match = context.matchBefore(/\[\[[^\]\n]*/);
        if (!match) return null;
        var content = context.state.sliceDoc(match.from + 2, context.pos);
        if (content[0] !== '/') return null;

        var lastSlash = content.lastIndexOf('/');
        var parent = content.slice(0, lastSlash + 1);
        var partial = content.slice(lastSlash + 1);

        // Replace only the current segment: from the start of the partial up to
        // the next `/` or `]` (or end of line). This narrows re-edits inside an
        // existing `[[…]]` so drilling doesn't duplicate trailing text.
        var from = match.from + 2 + lastSlash + 1;
        var line = context.state.doc.lineAt(context.pos);
        var to = context.pos;
        while (to < line.to) {
            var ch = context.state.sliceDoc(to, to + 1);
            if (ch === '/' || ch === ']') break;
            to++;
        }

        return new Promise(function (resolve) {
            if (context.aborted) { resolve(null); return; }
            fetchTree(parent).then(function (resp) {
                if (context.aborted || !resp) { resolve(null); return; }
                var needle = partial.toLowerCase();
                var options = (resp.entries || []).reduce(function (acc, e) {
                    if (needle && e.name.toLowerCase().indexOf(needle) === -1) {
                        return acc;
                    }
                    acc.push({
                        label: e.name,
                        type: e.kind === 'folder' ? 'folder' : 'file',
                        apply: makeApply(e.name, e.kind),
                    });
                    return acc;
                }, []);
                // No validFor: the source re-runs on each keystroke, so every
                // edit (more chars, backspace, or a `/` that drills into the
                // next folder) re-fetches and re-filters from scratch.
                resolve({ from: from, to: to, options: options });
            }).catch(function () {
                resolve(null);
            });
        });
    }

    return { source: source };
})();
