import { toggleMark } from "prosemirror-commands";
import {
  MarkSpec,
  MarkType,
  Node as ProsemirrorNode,
  Mark as ProsemirrorMark,
} from "prosemirror-model";
import { Plugin } from "prosemirror-state";
import moveLeft from "../commands/moveLeft";
import moveRight from "../commands/moveRight";
import markInputRule from "../lib/markInputRule";
import { MarkdownSerializerState } from "../lib/markdown/serializer";
import Mark from "./Mark";

function backticksFor(node: ProsemirrorNode, side: -1 | 1) {
  const ticks = /`+/g;
  let match: RegExpMatchArray | null;
  let len = 0;

  if (node.isText) {
    while ((match = ticks.exec(node.text || ""))) {
      len = Math.max(len, match[0].length);
    }
  }

  let result = len > 0 && side > 0 ? " `" : "`";
  for (let i = 0; i < len; i++) {
    result += "`";
  }
  if (len > 0 && side < 0) {
    result += " ";
  }
  return result;
}

export default class Code extends Mark {
  get name() {
    return "code_inline";
  }

  get schema(): MarkSpec {
    return {
      excludes: "_",
      parseDOM: [{ tag: "code.inline", preserveWhitespace: true }],
      toDOM: () => ["code", { class: "inline", spellCheck: "false" }],
    };
  }

  inputRules({ type }: { type: MarkType }) {
    return [markInputRule(/(?:^|[^`])(`([^`]+)`)$/, type)];
  }

  keys({ type }: { type: MarkType }) {
    // Note: This key binding only works on non-Mac platforms
    // https://github.com/ProseMirror/prosemirror/issues/515
    return {
      "Mod`": toggleMark(type),
      ArrowLeft: moveLeft(),
      ArrowRight: moveRight(),
    };
  }

  get plugins() {
    return [
      new Plugin({
        props: {
          // Typing a character inside of two backticks will wrap the character
          // in an inline code mark.
          handleTextInput: (view, from: number, to: number, text: string) => {
            const { state } = view;

            // Prevent access out of document bounds
            if (from === 0 || to === state.doc.nodeSize - 1 || text === "`") {
              return false;
            }

            if (
              from === to &&
              state.doc.textBetween(from - 1, from) === "`" &&
              state.doc.textBetween(to, to + 1) === "`"
            ) {
              const start = from - 1;
              const end = to + 1;
              view.dispatch(
                state.tr
                  .delete(start, end)
                  .insertText(text, start)
                  .addMark(
                    start,
                    start + text.length,
                    state.schema.marks.code_inline.create()
                  )
              );
              return true;
            }

            return false;
          },

          // Pasting a character inside of two backticks will wrap the character
          // in an inline code mark.
          handlePaste: (view, _event, slice) => {
            const { state } = view;
            const { from, to } = state.selection;

            // Prevent access out of document bounds
            if (from === 0 || to === state.doc.nodeSize - 1) {
              return false;
            }

            const start = from - 1;
            const end = to + 1;
            if (
              from === to &&
              state.doc.textBetween(start, from) === "`" &&
              state.doc.textBetween(to, end) === "`"
            ) {
              view.dispatch(
                state.tr
                  .replaceRange(start, end, slice)
                  .addMark(
                    start,
                    start + slice.size,
                    state.schema.marks.code_inline.create()
                  )
              );
              return true;
            }

            return false;
          },
        },
      }),
    ];
  }

  toMarkdown() {
    return {
      open(
        _state: MarkdownSerializerState,
        _mark: ProsemirrorMark,
        parent: ProsemirrorNode,
        index: number
      ) {
        return backticksFor(parent.child(index), -1);
      },
      close(
        _state: MarkdownSerializerState,
        _mark: ProsemirrorMark,
        parent: ProsemirrorNode,
        index: number
      ) {
        return backticksFor(parent.child(index - 1), 1);
      },
      escape: false,
    };
  }

  parseMarkdown() {
    return { mark: "code_inline" };
  }
}
