import { highlight_extension, ParameterizedCompletion } from "./ParameterizedCompletion.js";
import { error_highlight_extension, ErrorHighlight } from "./ErrorHighlight.js";
import { StateEffect } from "@codemirror/state";
import { Autocompletion } from "../core/Autocompletion.js";
import { ACCompleter } from "./ACCompleter.js";
import { ParseException } from "../ParseException.js";
import { NonTerminal } from "../core/NonTerminal.js";
import { keymap, highlightSpecialChars, drawSelection, highlightActiveLine, dropCursor, rectangularSelection, crosshairCursor, lineNumbers, highlightActiveLineGutter, EditorView } from "@codemirror/view";
import { EditorState } from "@codemirror/state";
import { defaultHighlightStyle, syntaxHighlighting, indentOnInput, bracketMatching, foldGutter, foldKeymap } from "@codemirror/language";
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
import { searchKeymap } from "@codemirror/search";
import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
import { lintKeymap } from "@codemirror/lint";
const basicSetup = (() => [
    lineNumbers(),
    highlightActiveLineGutter(),
    highlightSpecialChars(),
    history(),
    foldGutter(),
    drawSelection(),
    dropCursor(),
    EditorState.allowMultipleSelections.of(true),
    indentOnInput(),
    syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
    bracketMatching(),
    closeBrackets(),
    autocompletion(),
    rectangularSelection(),
    crosshairCursor(),
    highlightActiveLine(),
    // highlightSelectionMatches(),
    keymap.of([
        ...closeBracketsKeymap,
        ...defaultKeymap,
        ...searchKeymap,
        ...historyKeymap,
        ...foldKeymap,
        ...completionKeymap,
        ...lintKeymap
    ])
])();
export class ACEditor {
    constructor(parser, parent) {
        this.parameterizedCompletion = undefined;
        this.errorHighlight = undefined;
        this.onRun = () => this.run();
        this.beforeRun = () => { };
        this.afterRun = () => { };
        this.parser = parser;
        const that = this;
        const editorElement = this.createEditorElement(parent);
        this.outputElement = this.createOutputElement(parent);
        const runButton = this.createButton(parent);
        runButton.onclick = () => that.onRun();
        this.editor = new EditorView({
            extensions: [],
            parent: editorElement
        });
        this.errorHighlight = new ErrorHighlight(this.editor);
        this.completer = new ACCompleter(this.editor);
        this.editor.dispatch({
            effects: StateEffect.reconfigure.of([
                EditorView.domEventHandlers({
                    keydown: (e, view) => that.handleKeyEvent(e, view),
                }),
                this.completer.autocompletionExtension,
                basicSetup,
                EditorView.lineWrapping,
                highlight_extension,
                error_highlight_extension,
            ]),
        });
        // editor.dispatch(editor.state.replaceSelection("hahahahahah "));
        // editor.dispatch({selection: {anchor: 8, head: 11}});
    }
    setText(text) {
        this.editor.dispatch({
            changes: { from: 0, to: this.editor.state.doc.length, insert: text }
        });
    }
    getSelectedLinesStart() {
        const selFrom = this.editor.state.selection.main.from;
        return this.editor.state.doc.lineAt(selFrom).from;
    }
    getSelectedLinesEnd() {
        const selTo = this.editor.state.selection.main.to;
        return this.editor.state.doc.lineAt(selTo).to;
    }
    getSelectedLines() {
        const from = this.getSelectedLinesStart();
        const to = this.getSelectedLinesEnd();
        return this.editor.state.sliceDoc(from, to);
    }
    setOnRun(onRun) {
        this.onRun = onRun;
    }
    setBeforeRun(beforeRun) {
        this.beforeRun = beforeRun;
    }
    setAfterRun(afterRun) {
        this.afterRun = afterRun;
    }
    createEditorElement(parent) {
        const el = document.createElement("div");
        el.id = "nls-editor";
        el.setAttribute("style", "width: 100%; height: 300px; margin-bottom: 3px; border: 1px solid gray; flex-grow: 3; overflow: auto;");
        parent.appendChild(el);
        return el;
    }
    createOutputElement(parent) {
        const el = document.createElement("textarea");
        el.id = "nls-output";
        el.setAttribute("style", "width: 100%; height: 100px; margin-top: 3px; border: 1px solid gray; flex-grow: 2; padding: 0px; resize: none; overflow: auto;");
        el.setAttribute("readonly", "true");
        parent.appendChild(el);
        return el;
    }
    createButton(parent) {
        const el = document.createElement("button");
        el.id = "nls-run";
        el.setAttribute("style", "margin-top: 10px; margin-left: auto; margin-right: auto; display: block; margin-bottom: 10px;");
        el.setAttribute("type", "button");
        el.innerText = "Run";
        parent.appendChild(el);
        return el;
    }
    run(selectedLinesOnly = false) {
        console.debug("running");
        this.outputElement.textContent = "";
        const entireText = this.editor.state.doc.toString();
        try {
            this.beforeRun();
            const textToEvaluate = selectedLinesOnly ? this.getSelectedLines() : entireText;
            const pn = this.parser.parse(textToEvaluate);
            pn.evaluate();
            this.afterRun();
        }
        catch (e) {
            if (e instanceof Error)
                this.outputElement.textContent = e.message;
            else
                this.outputElement.textContent = e.toString();
            (console.error || console.log).call(console, e.stack || e);
        }
    }
    // corresponds to AutocompletionContext.insertCompletion()
    insertCompletion(completion) {
        if (!completion)
            return;
        const selection = this.editor.state.selection;
        const caret = selection.main.head;
        const entireText = this.editor.state.doc.toString();
        const cursorIsAtEnd = caret === entireText.length || entireText.substring(caret).trim().length == 0;
        if (selection.main.empty)
            this.editor.dispatch({ selection: { anchor: caret, head: caret - this.completer.completionPrefix().length } });
        const repl = completion.getCompletion(Autocompletion.Purpose.FOR_INSERTION);
        if (repl.indexOf("${") >= 0) {
            this.cancelParameterizedCompletion();
            this.parameterizedCompletion = new ParameterizedCompletion(this.editor);
            this.parameterizedCompletion.setParameterChangeListener((pIdx, wasLast) => this.parameterChanged(pIdx, wasLast));
            this.parameterizedCompletion.replaceSelection(completion);
        }
        else {
            this.editor.dispatch(this.editor.state.replaceSelection(repl));
            this.completer.hidePopup();
            if (cursorIsAtEnd)
                setTimeout(() => this.autocomplete());
        }
    }
    parameterChanged(_pIdx, wasLast) {
        if (wasLast) {
            this.cancelParameterizedCompletion();
            this.autocomplete();
        }
        else {
            this.autocomplete(false);
        }
    }
    cancelParameterizedCompletion() {
        if (this.parameterizedCompletion !== undefined) {
            this.parameterizedCompletion.cancel();
        }
        this.parameterizedCompletion = undefined;
    }
    handleKeyEvent(e, view) {
        console.log("keydown", e.key, e);
        if (this.completer.isActive()) {
            if (e.key === "Enter") {
                this.insertCompletion(this.completer.getSelected());
                return true;
            }
            if (e.key === "ArrowUp") {
                this.completer.selectPrevious();
                return true;
            }
            else if (e.key === "ArrowDown") {
                this.completer.selectNext();
                return true;
            }
            else if (e.key === "Escape") {
                this.completer.hidePopup();
                return true;
            }
            else if (e.key === "Tab") {
                this.completer.hidePopup();
                return true;
            }
        }
        else if (e.key === " " && e.ctrlKey) {
            this.autocomplete();
            return true;
        }
        if (this.parameterizedCompletion) {
            if (e.key === "Escape") {
                this.cancelParameterizedCompletion();
                return true;
            }
            this.parameterizedCompletion.handleKeyEvent(e);
            if (e.defaultPrevented)
                return true;
        }
        if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
            view.dispatch(view.state.replaceSelection(e.key));
            this.autocomplete();
            return true;
        }
        return false;
    }
    // corresponds to AutocompletionContext.doAutocompletion()
    autocomplete(autoinsertSingleOption = true) {
        var _a, _b;
        const entireText = this.editor.state.doc.toString();
        const anchor = this.editor.state.selection.main.anchor;
        const textToCursor = entireText.substring(0, anchor);
        (_a = this.errorHighlight) === null || _a === void 0 ? void 0 : _a.clearError();
        const autocompletions = [];
        let pn;
        try {
            pn = this.parser.parse(textToCursor, autocompletions);
            console.log(pn.getMatcher().state);
        }
        catch (e) {
            if (e instanceof ParseException) {
                const f = e.getFirstAutocompletingAncestorThatFailed().getMatcher();
                (_b = this.errorHighlight) === null || _b === void 0 ? void 0 : _b.setError(f.pos, f.pos + f.parsed.length);
                return;
            }
            else
                throw e;
        }
        const bnf = this.parser.getTargetGrammar().getBNF();
        if (this.parameterizedCompletion !== undefined) {
            if (autocompletions.length > 0) {
                let atLeastOneCompletionForCurrentParamter = false;
                for (let comp of autocompletions) {
                    let symbol = comp.forSymbol;
                    // if comp is an EntireSequence completion, we should just check the first
                    // we can do that using ParameterizedCompletionContext.parseParameters
                    if (comp instanceof Autocompletion.EntireSequence) {
                        let tmp = [];
                        ParameterizedCompletion.parseParameters(comp, tmp, 0);
                        comp = tmp[0].autocompletion;
                        symbol = comp.forSymbol;
                    }
                    if (symbol.equals(this.parameterizedCompletion.getForAutocompletion().forSymbol)) {
                        atLeastOneCompletionForCurrentParamter = true;
                        break;
                    }
                    // check if symbol is a descendent of the parameters autocompletion symbol
                    const pp = this.parameterizedCompletion.getCurrentParameter();
                    console.log("current param: " + (pp === null || pp === void 0 ? void 0 : pp.toString()));
                    const parameterSymbol = pp === null || pp === void 0 ? void 0 : pp.autocompletion.forSymbol;
                    // symbol == parameterSymbol? -> fine
                    if (symbol.equals(parameterSymbol)) {
                        atLeastOneCompletionForCurrentParamter = true;
                        break;
                    }
                    if (parameterSymbol instanceof NonTerminal) {
                        // check recursively if symbol is in the list of child symbols
                        if (parameterSymbol.uses(symbol, bnf)) {
                            atLeastOneCompletionForCurrentParamter = true;
                            break;
                        }
                    }
                }
                if (!atLeastOneCompletionForCurrentParamter) {
                    // console.log("now I'd call next.");
                    this.parameterizedCompletion.next();
                    return;
                }
            }
            else {
                console.log("no completions");
            }
        }
        else {
            console.log("parameterized completion == null");
        }
        if (autocompletions.length === 1) {
            if (autoinsertSingleOption || (autocompletion instanceof Autocompletion.Literal)) {
                this.completer.setCompletions(autocompletions);
                this.insertCompletion(autocompletions[0]);
                this.completer.hidePopup();
            }
        }
        else if (autocompletions.length > 1) {
            this.completer.setCompletions(autocompletions);
            this.completer.complete();
        }
        else {
            this.completer.hidePopup();
        }
    }
}
//# sourceMappingURL=ACEditor.js.map