import 'prismjs/prism.js'
import 'prismjs/components/prism-json.min.js'
import 'prismjs/components/prism-javascript.js'
import 'prismjs/components/prism-markdown.js'
import 'prismjs/components/prism-markup.js'
import 'prismjs/components/prism-yaml.js'
import 'prismjs/components/prism-xml-doc.js'
import 'prismjs/components/prism-dart.js'
// DART seems to be not working with auto-linker...
// import 'prismjs/plugins/autolinker/prism-autolinker.js';

export class PrismHighlighter {
  protected listener?: (code: string) => unknown

  /**
   * The number of milliseconds the library will wait before it runs the tokenizer.
   */
  debounceTimeout = 1

  protected debounceTimer?: number

  /**
   * @param highlightListener The listener for the debounced code highlight
   */
  constructor(highlightListener?: (code: string) => unknown) {
    this.listener = highlightListener
  }

  /**
   * A function to be called to run the highlighter in a debouncer.
   * To use this library this way set the `highlightListener` in the constructor
   * which will be called when highlighting is done.
   *
   * @param code The code to highlight
   * @param lang The language to use.
   */
  debounce(code: string, lang: string): void {
    if (!this.listener) {
      throw new Error('The highlight listener is not set.')
    }
    if (this.debounceTimer) {
      clearTimeout(this.debounceTimer)
    }
    this.debounceTimer = setTimeout(() => {
      this.debounceTimer = undefined
      const result = this.tokenize(code, lang)
      if (this.listener) {
        this.listener(result)
      }
    }, this.debounceTimeout) as unknown as number
  }

  /**
   * Translates the code into a HTML markup
   */
  tokenize(code: string, lang?: string): string {
    const grammar = this.detectLang(code, lang)
    const env = {
      code,
      grammar,
      language: lang,
    }
    // @ts-expect-error These are globally loaded...
    Prism.hooks.run('before-highlight', env)
    // @ts-expect-error These are globally loaded...
    const result = Prism.highlight(code, grammar, lang || '')
    return result
  }

  /**
   * Picks a Prism formatter based on the `lang` hint and `code`.
   *
   * @param code The source being highlighted.
   * @param lang A language hint (e.g. ````LANG`).
   */
  detectLang(code: string, lang?: string): Prism.Grammar {
    if (!lang) {
      // Stupid simple detection if we have no lang, courtesy of:
      // https://github.com/robdodson/mark-down/blob/ac2eaa/mark-down.html#L93-101
      // @ts-expect-error These are globally loaded...
      return code.match(/^\s*</) ? Prism.languages.markup : Prism.languages.javascript
    }
    // @ts-expect-error These are globally loaded...
    if (Prism.languages[lang]) {
      // @ts-expect-error These are globally loaded...
      return Prism.languages[lang]
    }
    switch (lang.substring(0, 2)) {
      case 'js':
      case 'es':
      case 'mj':
        // @ts-expect-error These are globally loaded...
        return Prism.languages.javascript
      case 'c':
        // @ts-expect-error These are globally loaded...
        return Prism.languages.clike
      default:
        // The assumption is that you're mostly documenting HTML when in HTML.
        // @ts-expect-error These are globally loaded...
        return Prism.languages.markup
    }
  }
}
