import { Prec } from "@codemirror/state";
import { KeyBinding, keymap } from "@codemirror/view";
import {
  Language,
  LanguageSupport,
  LanguageDescription,
} from "@codemirror/language";
import { MarkdownExtension, MarkdownParser, parseCode } from "@lezer/markdown";
import { html } from "@codemirror/lang-html";
import {
  commonmarkLanguage,
  markdownLanguage,
  mkLang,
  getCodeParser,
} from "./markdown";
import { insertNewlineContinueMarkup, deleteMarkupBackward } 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 source of language support for highlighting fenced code
    /// blocks. When it is an array, the parser will use
    /// [`LanguageDescription.matchLanguageName`](#language.LanguageDescription^matchLanguageName)
    /// with the fenced code info to find a matching language. When it
    /// is a function, will be called with the info string and may
    /// return a language or `LanguageDescription` object.
    codeLanguages?:
      | readonly LanguageDescription[]
      | ((info: string) => Language | LanguageDescription | null);
    /// 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);
}