import { Prec } from "@codemirror/state";
import { KeyBinding, keymap } from "@codemirror/view";
import { Language, LanguageDescription, LanguageSupport } from "@codemirror/language";
import { MarkdownExtension, MarkdownParser, parseCode } from "@lezer/markdown";
import { html } from "@codemirror/lang-html";
import { commonmarkLanguage, getCodeParser, markdownLanguage, mkLang } from "./markdown";
import { deleteMarkupBackward, insertNewlineContinueMarkup } from "./commands";

export {
  commonmarkLanguage,
  markdownLanguage,
  insertNewlineContinueMarkup,
  deleteMarkupBackward,
};

/// A small keymap with Markdown-specific bindings. Binds Enter to
/// [`insertNewlineContinueMarkup`](#lang-markdown.insertNewlineContinueMarkup)
/// and Backspace to
/// [`deleteMarkupBackward`](#lang-markdown.deleteMarkupBackward).
export const markdownKeymap: readonly KeyBinding[] = [
  { key: "Enter", run: insertNewlineContinueMarkup },
  { key: "Backspace", run: deleteMarkupBackward },
];

const htmlNoMatch = html({ matchClosingTags: false });

/// Markdown language support.
export function markdown(
  config: {
    /// When given, this language will be used by default to parse code
    /// blocks.
    defaultCodeLanguage?: Language | LanguageSupport;
    /// A collection of language descriptions to search through for a
    /// matching language (with
    /// [`LanguageDescription.matchLanguageName`](#language.LanguageDescription^matchLanguageName))
    /// when a fenced code block has an info string.
    codeLanguages?: readonly LanguageDescription[];
    /// Set this to false to disable installation of the Markdown
    /// [keymap](#lang-markdown.markdownKeymap).
    addKeymap?: boolean;
    /// Markdown parser
    /// [extensions](https://github.com/lezer-parser/markdown#user-content-markdownextension)
    /// to add to the parser.
    extensions?: MarkdownExtension;
    /// The base language to use. Defaults to
    /// [`commonmarkLanguage`](#lang-markdown.commonmarkLanguage).
    base?: Language;
  } = {}
) {
  let {
    codeLanguages,
    defaultCodeLanguage,
    addKeymap = true,
    base: { parser } = commonmarkLanguage,
  } = config;
  if (!(parser instanceof MarkdownParser))
    throw new RangeError(
      "Base parser provided to `markdown` should be a Markdown parser"
    );
  let extensions = config.extensions ? [config.extensions] : [];
  let support = [htmlNoMatch.support],
    defaultCode;
  if (defaultCodeLanguage instanceof LanguageSupport) {
    support.push(defaultCodeLanguage.support);
    defaultCode = defaultCodeLanguage.language;
  } else if (defaultCodeLanguage) {
    defaultCode = defaultCodeLanguage;
  }
  let codeParser =
    codeLanguages || defaultCode
      ? getCodeParser(codeLanguages || [], defaultCode)
      : undefined;
  extensions.push(
    parseCode({ codeParser, htmlParser: htmlNoMatch.language.parser })
  );
  if (addKeymap) support.push(Prec.high(keymap.of(markdownKeymap)));
  return new LanguageSupport(mkLang(parser.configure(extensions)), support);
}
