import MarkdownIt from 'markdown-it'
import mdAttrs from 'markdown-it-attrs'
import mdDecorate from 'markdown-it-decorate'
import mdEmoji from 'markdown-it-emoji'
import mdTaskLists from 'markdown-it-task-lists'
import mdExpandTabs from 'markdown-it-expand-tabs'
import mdAbbr from 'markdown-it-abbr'
import mdSup from 'markdown-it-sup'
import mdSub from 'markdown-it-sub'
import mdMark from 'markdown-it-mark'
import mdMultiTable from 'markdown-it-multimd-table'
import mdFootnote from 'markdown-it-footnote'
import katex from 'katex'
import mdUnderline from './modules/markdown-it-underline'
import mdImsize from './modules/markdown-it-imsize'
import 'katex/dist/contrib/mhchem'
import twemoji from 'twemoji'
import plantuml from './modules/plantuml'
import kroki from './modules/kroki.mjs'
import katexHelper from './modules/katex'

import hljs from 'highlight.js'

import { escape, findLast, times } from 'lodash-es'

const quoteStyles = {
  chinese: '””‘’',
  english: '“”‘’',
  french: ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'],
  german: '„“‚‘',
  greek: '«»‘’',
  japanese: '「」「」',
  hungarian: '„”’’',
  polish: '„”‚‘',
  portuguese: '«»‘’',
  russian: '«»„“',
  spanish: '«»‘’',
  swedish: '””’’'
}

export class MarkdownRenderer {
  constructor (config = {}) {
    this.md = new MarkdownIt({
      html: config.allowHTML,
      breaks: config.lineBreaks,
      linkify: config.linkify,
      typography: config.typographer,
      quotes: quoteStyles[config.quotes] ?? quoteStyles.english,
      highlight (str, lang) {
        if (lang === 'diagram') {
          return `<pre class="diagram">${Buffer.from(str, 'base64').toString()}</pre>`
        } else if (['mermaid', 'plantuml'].includes(lang)) {
          return `<pre class="codeblock-${lang}"><code>${escape(str)}</code></pre>`
        } else {
          const highlighted = lang ? hljs.highlight(str, { language: lang, ignoreIllegals: true }) : { value: str }
          const lineCount = highlighted.value.match(/\n/g).length
          const lineNums = lineCount > 1 ? `<span aria-hidden="true" class="line-numbers-rows">${times(lineCount, n => '<span></span>').join('')}</span>` : ''
          return `<pre class="codeblock hljs ${lineCount > 1 && 'line-numbers'}"><code class="language-${lang}">${highlighted.value}${lineNums}</code></pre>`
        }
      }
    })
      .use(mdAttrs, {
        allowedAttributes: ['id', 'class', 'target']
      })
      .use(mdDecorate)
      .use(mdEmoji)
      .use(mdTaskLists, { label: false, labelAfter: false })
      .use(mdExpandTabs, { tabWidth: config.tabWidth })
      .use(mdAbbr)
      .use(mdSup)
      .use(mdSub)
      .use(mdMark)
      .use(mdFootnote)
      .use(mdImsize)

    if (config.underline) {
      this.md.use(mdUnderline)
    }

    if (config.mdmultiTable) {
      this.md.use(mdMultiTable, { multiline: true, rowspan: true, headerless: true })
    }

    // --------------------------------
    // PLANTUML
    // --------------------------------

    if (config.plantuml) {
      plantuml.init(this.md, { server: config.plantumlServerUrl })
    }

    // --------------------------------
    // KROKI
    // --------------------------------

    if (config.kroki) {
      kroki.init(this.md, { server: config.krokiServerUrl })
    }

    // --------------------------------
    // KATEX
    // --------------------------------

    const macros = {}

    // TODO: Add mhchem (needs esm conversion)
    // Add \ce, \pu, and \tripledash to the KaTeX macros.
    // katex.__defineMacro('\\ce', function (context) {
    //   return chemParse(context.consumeArgs(1)[0], 'ce')
    // })
    // katex.__defineMacro('\\pu', function (context) {
    //   return chemParse(context.consumeArgs(1)[0], 'pu')
    // })

    //  Needed for \bond for the ~ forms
    //  Raise by 2.56mu, not 2mu. We're raising a hyphen-minus, U+002D, not
    //  a mathematical minus, U+2212. So we need that extra 0.56.
    katex.__defineMacro('\\tripledash', '{\\vphantom{-}\\raisebox{2.56mu}{$\\mkern2mu' + '\\tiny\\text{-}\\mkern1mu\\text{-}\\mkern1mu\\text{-}\\mkern2mu$}}')
    this.md.inline.ruler.after('escape', 'katex_inline', katexHelper.katexInline)
    this.md.renderer.rules.katex_inline = (tokens, idx) => {
      try {
        return katex.renderToString(tokens[idx].content, {
          displayMode: false, macros
        })
      } catch (err) {
        console.warn(err)
        return tokens[idx].content
      }
    }
    this.md.block.ruler.after('blockquote', 'katex_block', katexHelper.katexBlock, {
      alt: ['paragraph', 'reference', 'blockquote', 'list']
    })
    this.md.renderer.rules.katex_block = (tokens, idx) => {
      try {
        return '<p>' + katex.renderToString(tokens[idx].content, {
          displayMode: true, macros
        }) + '</p>'
      } catch (err) {
        console.warn(err)
        return tokens[idx].content
      }
    }

    // --------------------------------
    // TWEMOJI
    // --------------------------------

    this.md.renderer.rules.emoji = (token, idx) => {
      return twemoji.parse(token[idx].content, {
        callback (icon, opts) {
          return `/_assets/svg/twemoji/${icon}.svg`
        }
      })
    }

    // --------------------------------
    // Inject line numbers for preview scroll sync
    // --------------------------------

    this.linesMap = []
    const injectLineNumbers = (tokens, idx, options, env, slf) => {
      let line
      if (tokens[idx].map && tokens[idx].level === 0) {
        line = tokens[idx].map[0] + 1
        tokens[idx].attrJoin('class', 'line')
        tokens[idx].attrSet('data-line', String(line))
        this.linesMap.push(line)
      }
      return slf.renderToken(tokens, idx, options, env, slf)
    }
    this.md.renderer.rules.paragraph_open = injectLineNumbers
    this.md.renderer.rules.heading_open = injectLineNumbers
    this.md.renderer.rules.blockquote_open = injectLineNumbers
  }

  render (src) {
    this.linesMap = []
    return this.md.render(src)
  }

  getClosestPreviewLine (line) {
    return findLast(this.linesMap, n => n <= line)
  }
}