import {
    wrapIn, setBlockType, chainCommands, toggleMark, exitCode,
    joinUp, joinDown, lift, selectParentNode
} from "prosemirror-commands";
import { wrapInList, splitListItem, liftListItem, sinkListItem } from "prosemirror-schema-list";
import { undo, redo } from "prosemirror-history";
import { undoInputRule } from "prosemirror-inputrules";

const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;

// :: (Schema, ?Object) → Object
// inspect the given schema looking for marks and nodes from the
// basic schema, and if found, add key bindings related to them.
// this will add:
//
// * **Mod-b** for toggling [strong](#schema-basic.StrongMark)
// * **Mod-i** for toggling [emphasis](#schema-basic.EmMark)
// * **Mod-`** for toggling [code font](#schema-basic.CodeMark)
// * **Ctrl-Shift-0** for making the current textblock a paragraph
// * **Ctrl-Shift-1** to **Ctrl-Shift-Digit6** for making the current
//   textblock a heading of the corresponding level
// * **Ctrl-Shift-Backslash** to make the current textblock a code block
// * **Ctrl-Shift-8** to wrap the selection in an ordered list
// * **Ctrl-Shift-9** to wrap the selection in a bullet list
// * **Ctrl->** to wrap the selection in a block quote
// * **Enter** to split a non-empty textblock in a list item while at
//   the same time splitting the list item
// * **Mod-Enter** to insert a hard break
// * **Mod-_** to insert a horizontal rule
// * **Backspace** to undo an input rule
// * **Alt-ArrowUp** to `joinUp`
// * **Alt-ArrowDown** to `joinDown`
// * **Mod-BracketLeft** to `lift`
// * **Escape** to `selectParentNode`
//
// you can suppress or map these bindings by passing a `mapKeys`
// argument, which maps key names (say `"Mod-B"` to either `false`, to
// remove the binding, or a new key name string.
export function buildKeymap(schema, mapKeys) {
    let keys = {}, type;
    function bind(key, cmd) {
        if (mapKeys) {
            const mapped = mapKeys[key];
            if (mapped === false) { return; }
            if (mapped) { key = mapped; }
        }
        keys[key] = cmd;
    }


    bind("Mod-z", undo);
    bind("Shift-Mod-z", redo);
    bind("Backspace", undoInputRule);
    if (!mac) { bind("Mod-y", redo); }

    bind("Alt-ArrowUp", joinUp);
    bind("Alt-ArrowDown", joinDown);
    bind("Mod-BracketLeft", lift);
    bind("Escape", selectParentNode);

    if (type = schema.marks.bold) {
        bind("Mod-b", toggleMark(type));
    }

    if (type = schema.marks.italic) {
        bind("Mod-i", toggleMark(type));
    }

    if (type = schema.marks.code) {
        bind("Mod-`", toggleMark(type));
    }

    if (type = schema.nodes.bullet_list) {
        bind("Shift-Ctrl-8", wrapInList(type));
    }

    if (type = schema.nodes.ordered_list) {
        bind("Shift-Ctrl-9", wrapInList(type));
    }

    if (type = schema.nodes.blockquote) {
        bind("Ctrl->", wrapIn(type));
    }

    if (type = schema.nodes.break) {
        const br = type, cmd = chainCommands(exitCode, (state, dispatch) => {
            const $anchor = state.selection.$anchor;

            if (!$anchor) {
                return;
            }

            if ($anchor.nodeBefore && $anchor.nodeBefore.type.name === "break") {
                dispatch(state.tr.delete($anchor.pos - 1, $anchor.pos)
                    .replaceSelectionWith(schema.nodes.paragraph.create()).scrollIntoView());
            }
            else {
                dispatch(state.tr.replaceSelectionWith(br.create()).scrollIntoView());
            }

            return true;
        });
        bind("Mod-Enter", cmd);
        bind("Shift-Enter", cmd);
        bind("Ctrl-Enter", cmd);
    }

    if (type = schema.nodes.list_item) {
        bind("Enter", splitListItem(type));
        bind("Mod-[", liftListItem(type));
        bind("Mod-]", sinkListItem(type));
    }

    if (type = schema.nodes.paragraph) {
        bind("Shift-Ctrl-0", setBlockType(type));
    }

    if (type = schema.nodes.code_block) {
        bind("Shift-Ctrl-\\", setBlockType(type));
    }

    if (type = schema.nodes.heading) {
        for (let i = 1; i <= 6; i++) { bind("Shift-Ctrl-" + i, setBlockType(type, { level: i })); }
    }

    if (type = schema.nodes.horizontal_rule) {
        const hr = type;

        bind("Mod-_", (state, dispatch) => {
            dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
            return true;
        });
    }

    return keys;
}