import "../../../lib/vendor/codemirror/addon/mode/simple.js";
import {
    org_cycle, org_shifttab, org_metaleft, org_metaright, org_meta_return, org_metaup,
    org_metadown, org_insert_todo_heading, org_shiftleft, org_shiftright, fold, unfold,
    isFold, org_shiftmetaleft, org_shiftmetaright,
} from "./emacs-org.js";
import { join } from "../../../lib/path.js";
import { currentShare } from "../../filespage/cache.js";

window.CodeMirror.__mode = "orgmode";

window.CodeMirror.defineSimpleMode("orgmode", {
    start: [
        { regex: /(\*\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: ["header level1 org-level-star", "header level1 org-todo", "header level1 org-done", "header level1 org-priority", "header level1", "header level1 void", "header level1 comment"] },
        { regex: /(\*{1,}\s)(TODO|DOING|WAITING|NEXT|PENDING|)(CANCELLED|CANCELED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED|)(\s+\[\#[A-C]\]\s+|)(.*?)(?:(\s{10,}|))(\:[\S]+\:|)$/, sol: true, token: ["header org-level-star", "header org-todo", "header org-done", "header org-priority", "header", "header void", "header comment"] },
        { regex: /(\+[^\+]+\+)/, token: ["strikethrough"] },
        { regex: /(\*[^\*]+\*)/, token: ["strong"] },
        { regex: /(\/[^\/]+\/)/, token: ["em"] },
        { regex: /(\_[^\_]+\_)/, token: ["link"] },
        { regex: /(\~[^\~]+\~)/, token: ["comment"] },
        { regex: /(\=[^\=]+\=)/, token: ["comment"] },
        { regex: /\[\[[^\[\]]+\]\[[^\[\]]+\]\]/, token: "org-url" }, // links
        { regex: /\[\[[^\[\]]+\]\]/, token: "org-image" }, // image
        { regex: /\[[xX\s\-\_]\]/, token: "qualifier org-toggle" }, // checkbox
        { regex: /\#\+(?:(BEGIN|begin))_[a-zA-Z]*/, token: "comment", next: "env", sol: true }, // comments
        { regex: /:?[A-Z_]+\:.*/, token: "comment", sol: true }, // property drawers
        { regex: /(\#\+[a-zA-Z_]*)(\:.*)/, token: ["keyword", "qualifier"], sol: true }, // environments
        { regex: /(CLOCK\:|SHEDULED\:|DEADLINE\:)(\s.+)/, token: ["comment", "keyword"] },
    ],
    env: [
        { regex: /\#\+(?:(END|end))_[a-zA-Z]*/, token: "comment", next: "start", sol: true },
        { regex: /.*/, token: "comment" },
    ],
});

window.CodeMirror.registerHelper("fold", "orgmode", function(cm, start) {
    // init
    const levelToMatch = headerLevel(start.line);

    // no folding needed
    if (levelToMatch === null) return;

    // find folding limits
    const lastLine = cm.lastLine();
    let end = start.line;
    while (end < lastLine) {
        end += 1;
        const level = headerLevel(end);
        if (level && level <= levelToMatch) {
            end = end - 1;
            break;
        };
    }

    return {
        from: window.CodeMirror.Pos(start.line, cm.getLine(start.line).length),
        to: window.CodeMirror.Pos(end, cm.getLine(end).length),
    };

    function headerLevel(lineNo) {
        const line = cm.getLine(lineNo);
        const match = /^\*+/.exec(line);
        if (match && match.length === 1 && /header/.test(cm.getTokenTypeAt(window.CodeMirror.Pos(lineNo, 0)))) {
            return match[0].length;
        }
        return null;
    }
});

window.CodeMirror.registerGlobalHelper("fold", "drawer", function(mode) {
    return mode.name === "orgmode";
}, function(cm, start) {
    const drawer = isBeginningOfADrawer(start.line);
    if (drawer === false) return;

    // find folding limits
    const lastLine = cm.lastLine();
    let end = start.line;
    while (end < lastLine) {
        end += 1;
        if (isEndOfADrawer(end)) {
            break;
        }
    }

    return {
        from: window.CodeMirror.Pos(start.line, cm.getLine(start.line).length),
        to: window.CodeMirror.Pos(end, cm.getLine(end).length),
    };

    function isBeginningOfADrawer(lineNo) {
        const line = cm.getLine(lineNo);
        const match = /^\:.*\:$/.exec(line);
        if (match && match.length === 1 && match[0] !== ":END:") {
            return true;
        }
        return false;
    }
    function isEndOfADrawer(lineNo) {
        const line = cm.getLine(lineNo);
        return line.trim() === ":END:";
    }
});

window.CodeMirror.registerHelper("orgmode", "init", (editor) => {
    editor.setOption("extraKeys", {
        "Tab": (cm) => org_cycle(cm),
        "Shift-Tab": (cm) => org_shifttab(cm),
        "Alt-Left": (cm) => org_metaleft(cm),
        "Alt-Right": (cm) => org_metaright(cm),
        "Alt-Enter": (cm) => org_meta_return(cm),
        "Alt-Up": (cm) => org_metaup(cm),
        "Alt-Down": (cm) => org_metadown(cm),
        "Shift-Alt-Left": (cm) => org_shiftmetaleft(cm),
        "Shift-Alt-Right": (cm) => org_shiftmetaright(cm),
        "Shift-Alt-Enter": (cm) => org_insert_todo_heading(cm),
        "Shift-Left": (cm) => org_shiftleft(cm),
        "Shift-Right": (cm) => org_shiftright(cm),
    });

    editor.on("mousedown", toggleHandler);
    editor.on("touchstart", toggleHandler);
    editor.on("gutterClick", foldLine);

    // fold everything except headers by default
    editor.operation(function() {
        for (let i = 0; i < editor.lineCount(); i++) {
            if (/header/.test(editor.getTokenTypeAt(window.CodeMirror.Pos(i, 0))) === false) {
                fold(editor, window.CodeMirror.Pos(i, 0));
            }
        }
    });
    return window.CodeMirror.orgmode.destroy.bind(this, editor);
});

window.CodeMirror.registerHelper("orgmode", "destroy", (editor) => {
    editor.off("mousedown", toggleHandler);
    editor.off("touchstart", toggleHandler);
    editor.off("gutterClick", foldLine);
});

function foldLine(cm, line) {
    const cursor = { line, ch: 0 };
    isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor);
}

let widgets = [];
function toggleHandler(cm, e) {
    const position = cm.coordsChar({
        left: e.clientX || (e.targetTouches && e.targetTouches[0].clientX),
        top: e.clientY || (e.targetTouches && e.targetTouches[0].clientY),
    }, "page");
    const token = cm.getTokenAt(position);

    _disableSelection();
    if (/org-level-star/.test(token.type)) {
        _preventIfShould();
        _foldHeadline();
        _disableSelection();
    } else if (/org-toggle/.test(token.type)) {
        _preventIfShould();
        _toggleCheckbox();
        _disableSelection();
    } else if (/org-todo/.test(token.type)) {
        _preventIfShould();
        _toggleTodo();
        _disableSelection();
    } else if (/org-done/.test(token.type)) {
        _preventIfShould();
        _toggleDone();
        _disableSelection();
    } else if (/org-priority/.test(token.type)) {
        _preventIfShould();
        _togglePriority();
        _disableSelection();
    } else if (/org-url/.test(token.type)) {
        _disableSelection();
        _navigateLink();
    } else if (/org-image/.test(token.type)) {
        _disableSelection();
        _toggleImageWidget();
    }

    function _preventIfShould() {
        if ("ontouchstart" in window) e.preventDefault();
    }
    function _disableSelection() {
        cm.on("beforeSelectionChange", _onSelectionChangeHandler);
        function _onSelectionChangeHandler(cm, obj) {
            obj.update([{
                anchor: position,
                head: position,
            }]);
            cm.off("beforeSelectionChange", _onSelectionChangeHandler);
        }
    }

    function _foldHeadline() {
        const line = position.line;
        if (line >= 0) {
            const cursor = { line, ch: 0 };
            isFold(cm, cursor) ? unfold(cm, cursor) : fold(cm, cursor);
        }
    }

    function _toggleCheckbox() {
        const line = position.line;
        const content = cm.getRange(
            { line, ch: token.start },
            { line, ch: token.end },
        );
        const new_content = content === "[X]" || content === "[x]" ? "[ ]" : "[X]";
        cm.replaceRange(
            new_content,
            { line, ch: token.start },
            { line, ch: token.end },
        );
    }

    function _toggleTodo() {
        const line = position.line;
        cm.replaceRange(
            "DONE",
            { line, ch: token.start },
            { line, ch: token.end },
        );
    }

    function _toggleDone() {
        const line = position.line;
        cm.replaceRange(
            "TODO",
            { line, ch: token.start },
            { line, ch: token.end },
        );
    }

    function _togglePriority() {
        const PRIORITIES = [" [#A] ", " [#B] ", " [#C] ", " [#A] "];
        const line = position.line;
        const content = cm.getRange({ line, ch: token.start }, { line, ch: token.end });
        const new_content = PRIORITIES[PRIORITIES.indexOf(content) + 1];
        cm.replaceRange(
            new_content,
            { line, ch: token.start },
            { line, ch: token.end },
        );
    }

    function _toggleImageWidget() {
        const exist = !!widgets
            .filter((line) => line === position.line)[0];

        if (exist === false) {
            if (!token.string.match(/\[\[(.*)\]\]/)) return null;
            const $node = _buildImage(RegExp.$1);
            const widget = cm.addLineWidget(position.line, $node, { coverGutter: false });
            widgets.push(position.line);
            $node.addEventListener("click", closeWidget);

            function closeWidget() {
                widget.clear();
                $node.removeEventListener("click", closeWidget);
                widgets = widgets.filter((line) => line !== position.line);
            }
        }
        function _buildImage(src) {
            const $el = document.createElement("div");
            const $img = document.createElement("img");

            if (/^https?\:\/\//.test(src)) {
                $img.src = src;
            } else {
                const path = join(location, src).replace(/^\/view/, "");
                $img.src = "/api/files/cat?path=" + path;
                const share = currentShare();
                if (share) $img.src += "&share=" + share;
            }
            $el.appendChild($img);
            return $el;
        }
        return null;
    }

    function _navigateLink() {
        token.string.match(/\[\[(.*?)\]\[/);
        const link = RegExp.$1;
        if (!link) return;

        let open = "_blank";
        const isMobile = screen.availWidth < screen.availHeight;
        if (!document.querySelector(".component_fab img.component_icon[alt=\"save\"]")) {
            open = "_self";
        } else if (isMobile) {
            open = "_self";
        }

        if (/^https?\:\/\//.test(link)) {
            window.open(link, open);
        } else {
            let url = join(location, link);
            const share = currentShare();
            if (share) url += "?share=" + share;
            window.open(url, open);
        }
    }
}
export default window.CodeMirror;
