text: import mermaid from 'mermaid'
  import flowchart from 'flowchart.js'
  import Diagram from './sequence'
  // import vegaEmbed from 'vega-embed'
  import { CLASS_OR_ID } from '../../config'
  import { conflict, mixins } from '../../utils'
  import { patch, toVNode, toHTML, h } from './snabbdom'
  import { beginRules } from '../rules'
  import renderInlines from './renderInlines'
  import renderBlock from './renderBlock'
  
  class StateRender {
    constructor (muya) {
      this.muya = muya
      this.eventCenter = muya.eventCenter
      this.loadImageMap = new Map()
      this.loadMathMap = new Map()
      this.mermaidCache = new Set()
      this.diagramCache = new Map()
      this.tokenCache = new Map()
      this.labels = new Map()
      this.container = null
    }
  
    setContainer (container) {
      this.container = container
    }
  
    collectLabels (blocks) {
      this.labels.clear()
  
      const travel = block => {
        const { text, children } = block
        if (children && children.length) {
          children.forEach(c => travel(c))
        } else if (text) {
          const tokens = beginRules['reference_definition'].exec(text)
          if (tokens) {
            const key = (tokens[2] + tokens[3]).toLowerCase()
            if (!this.labels.has(key)) {
              this.labels.set(key, {
                href: tokens[6],
                title: tokens[10] || ''
              })
            }
          }
        }
      }
  
      blocks.forEach(b => travel(b))
    }
  
    checkConflicted (block, token, cursor) {
      const { start, end } = cursor
      const key = block.key
      const { start: tokenStart, end: tokenEnd } = token.range
  
      if (key !== start.key && key !== end.key) {
        return false
      } else if (key === start.key && key !== end.key) {
        return conflict([tokenStart, tokenEnd], [start.offset, start.offset])
      } else if (key !== start.key && key === end.key) {
        return conflict([tokenStart, tokenEnd], [end.offset, end.offset])
      } else {
        return conflict([tokenStart, tokenEnd], [start.offset, start.offset]) ||
          conflict([tokenStart, tokenEnd], [end.offset, end.offset])
      }
    }
  
    getClassName (outerClass, block, token, cursor) {
      return outerClass || (this.checkConflicted(block, token, cursor) ? CLASS_OR_ID['AG_GRAY'] : CLASS_OR_ID['AG_HIDE'])
    }
  
    getHighlightClassName (active) {
      return active ? CLASS_OR_ID['AG_HIGHLIGHT'] : CLASS_OR_ID['AG_SELECTION']
    }
  
    getSelector (block, cursor, activeBlocks) {
      const type = block.type === 'hr' ? 'p' : block.type
      const isActive = activeBlocks.some(b => b.key === block.key) || block.key === cursor.start.key
  
      let selector = `${type}#${block.key}.${CLASS_OR_ID['AG_PARAGRAPH']}`
      if (isActive) {
        selector += `.${CLASS_OR_ID['AG_ACTIVE']}`
      }
      if (type === 'span') {
        selector += `.${CLASS_OR_ID['AG_LINE']}`
      }
      return selector
    }
  
    renderMermaid () {
      if (this.mermaidCache.size) {
        mermaid.init(undefined, document.querySelectorAll([...this.mermaidCache].join(', ')))
        this.mermaidCache.clear()
      }
    }
  
    async renderDiagram () {
      const cache = this.diagramCache
      const RENDER_MAP = {
        'flowchart': flowchart,
        'sequence': Diagram// ,
        // 'vega-lite': vegaEmbed
      }
      if (cache.size) {
        for (const [key, value] of cache.entries()) {
          const target = document.querySelector(key)
          const { code, functionType } = value
          const render = RENDER_MAP[functionType]
          const options = {}
          if (functionType === 'sequence') {
            Object.assign(options, { theme: this.muya.options.sequenceTheme })
          } else if (functionType === 'vega-lite') {
            Object.assign(options, { actions: false, tooltip: false, renderer: 'svg' })
          }
          try {
            if (functionType === 'flowchart' || functionType === 'sequence') {
              const diagram = render.parse(code)
              target.innerHTML = ''
              diagram.drawSVG(target, options)
            } else if (functionType === 'vega-lite') {
              await render(key, JSON.parse(code), options)
            }
          } catch (err) {
            target.innerHTML = `< Invalid ${functionType === 'flowchart' ? 'Flow Chart' : 'Sequence'} Codes >`
            target.classList.add(CLASS_OR_ID['AG_MATH_ERROR'])
          }
        }
        this.diagramCache.clear()
      }
    }
  
    render (blocks, cursor, activeBlocks, matches) {
      const selector = `div#${CLASS_OR_ID['AG_EDITOR_ID']}`
  
      const children = blocks.map(block => {
        return this.renderBlock(block, cursor, activeBlocks, matches, true)
      })
  
      const newVdom = h(selector, children)
      const rootDom = document.querySelector(selector) || this.container
      const oldVdom = toVNode(rootDom)
  
      patch(oldVdom, newVdom)
      this.renderMermaid()
      this.renderDiagram()
    }
  
    // Only render the blocks which you updated
    partialRender (blocks, cursor, activeBlocks, matches, startKey, endKey) {
      const cursorOutMostBlock = activeBlocks[activeBlocks.length - 1]
      // If cursor is not in render blocks, need to render cursor block independently
      const needRenderCursorBlock = blocks.indexOf(cursorOutMostBlock) === -1
      const newVnode = h('section', blocks.map(block => this.renderBlock(block, cursor, activeBlocks, matches)))
      const html = toHTML(newVnode).replace(/^<section>([\s\S]+?)<\/section>$/, '$1')
  
      const needToRemoved = []
      const firstOldDom = startKey
        ? document.querySelector(`#${startKey}`)
        : document.querySelector(`div#${CLASS_OR_ID['AG_EDITOR_ID']}`).firstElementChild
      if (!firstOldDom) {
        // TODO@Jocs Just for fix #541, Because I'll rewrite block and render method, it will nolonger have this issue.
        return
      }
      needToRemoved.push(firstOldDom)
      let nextSibling = firstOldDom.nextElementSibling
      while (nextSibling && nextSibling.id !== endKey) {
        needToRemoved.push(nextSibling)
        nextSibling = nextSibling.nextElementSibling
      }
      nextSibling && needToRemoved.push(nextSibling)
  
      firstOldDom.insertAdjacentHTML('beforebegin', html)
  
      Array.from(needToRemoved).forEach(dom => dom.remove())
  
      // Render cursor block independently
      if (needRenderCursorBlock) {
        const { key } = cursorOutMostBlock
        const cursorDom = document.querySelector(`#${key}`)
        if (cursorDom) {
          const oldCursorVnode = toVNode(cursorDom)
          const newCursorVnode = this.renderBlock(cursorOutMostBlock, cursor, activeBlocks, matches)
          patch(oldCursorVnode, newCursorVnode)
        }
      }
      this.renderMermaid()
      this.renderDiagram()
    }
  }
  
  mixins(StateRender, renderInlines, renderBlock)
  
  export default StateRender
  