'use strict'

module.exports = function (md, renderers, defaultRenderer) {
  const paramsRE = /^[ \t]*([\w+#-]+)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/

  function fence (state, startLine, endLine, silent) {
    let pos = state.bMarks[startLine] + state.tShift[startLine]
    let max = state.eMarks[startLine]

    if (state.sCount[startLine] - state.blkIndent >= 4 || pos + 3 > max) {
      return false
    }

    const marker = state.src.charCodeAt(pos)
    if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) {
      return false
    }

    let mem = pos
    pos = state.skipChars(pos, marker)

    let len = pos - mem
    if (len < 3) {
      return false
    }

    const markup = state.src.slice(mem, pos)
    const params = state.src.slice(pos, max)

    if (silent) {
      return true
    }

    let nextLine = startLine
    let haveEndMarker = false

    while (true) {
      nextLine++
      if (nextLine >= endLine) {
        break
      }

      pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
      max = state.eMarks[nextLine]

      if (pos < max && state.sCount[nextLine] < state.blkIndent) {
        break
      }
      if (state.src.charCodeAt(pos) !== marker || state.sCount[nextLine] - state.blkIndent >= 4) {
        continue
      }

      pos = state.skipChars(pos, marker)

      if (pos - mem < len) {
        continue
      }

      pos = state.skipSpaces(pos)

      if (pos >= max) {
        haveEndMarker = true
        break
      }
    }

    len = state.sCount[startLine]
    state.line = nextLine + (haveEndMarker ? 1 : 0)

    const parameters = {}
    let langType = ''
    let fileName = ''
    let firstLineNumber = 1

    let match = paramsRE.exec(params)
    if (match) {
      if (match[1]) {
        langType = match[1]
      }
      if (match[3]) {
        fileName = match[3]
      }
      if (match[4]) {
        firstLineNumber = parseInt(match[4], 10)
      }

      if (match[2]) {
        const params = match[2]
        const regex = /(\w[-\w]*)(?:=(?:'(.*?[^\\])?'|"(.*?[^\\])?"|([^'"][^\s]*)))?/g

        let name, value
        while ((match = regex.exec(params))) {
          name = match[1]
          value = match[2] || match[3] || match[4] || null

          const height = /^(\d+)h$/.exec(name)
          if (height && !value) {
            parameters.height = height[1]
          } else {
            parameters[name] = value
          }
        }
      }
    }

    let token
    if (renderers[langType]) {
      token = state.push(`${langType}_fence`, 'div', 0)
    } else {
      token = state.push('_fence', 'code', 0)
    }

    token.langType = langType
    token.fileName = fileName
    token.firstLineNumber = firstLineNumber
    token.parameters = parameters

    token.content = state.getLines(startLine + 1, nextLine, len, true)
    token.markup = markup
    token.map = [startLine, state.line]

    return true
  }

  md.block.ruler.before('fence', '_fence', fence, {
    alt: ['paragraph', 'reference', 'blockquote', 'list']
  })

  for (const name in renderers) {
    md.renderer.rules[`${name}_fence`] = (tokens, index) => renderers[name](tokens[index])
  }

  if (defaultRenderer) {
    md.renderer.rules['_fence'] = (tokens, index) => defaultRenderer(tokens[index])
  }
}
