'use strict'

module.exports = function definitionListPlugin (md) {
  var isSpace = md.utils.isSpace

  // Search `[:~][\n ]`, returns next pos after marker on success
  // or -1 on fail.
  function skipMarker (state, line) {
    let start = state.bMarks[line] + state.tShift[line]
    const max = state.eMarks[line]

    if (start >= max) { return -1 }

    // Check bullet
    const marker = state.src.charCodeAt(start++)
    if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1 }

    const pos = state.skipSpaces(start)

    // require space after ":"
    if (start === pos) { return -1 }

    return start
  }

  function markTightParagraphs (state, idx) {
    const level = state.level + 2

    let i
    let l
    for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
      if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
        state.tokens[i + 2].hidden = true
        state.tokens[i].hidden = true
        i += 2
      }
    }
  }

  function deflist (state, startLine, endLine, silent) {
    var ch,
      contentStart,
      ddLine,
      dtLine,
      itemLines,
      listLines,
      listTokIdx,
      max,
      newEndLine,
      nextLine,
      offset,
      oldDDIndent,
      oldIndent,
      oldLineMax,
      oldParentType,
      oldSCount,
      oldTShift,
      oldTight,
      pos,
      prevEmptyEnd,
      tight,
      token

    if (silent) {
      // quirk: validation mode validates a dd block only, not a whole deflist
      if (state.ddIndent < 0) { return false }
      return skipMarker(state, startLine) >= 0
    }

    nextLine = startLine + 1
    if (nextLine >= endLine) { return false }

    if (state.isEmpty(nextLine)) {
      nextLine++
      if (nextLine >= endLine) { return false }
    }

    if (state.sCount[nextLine] < state.blkIndent) { return false }
    contentStart = skipMarker(state, nextLine)
    if (contentStart < 0) { return false }

    // Start list
    listTokIdx = state.tokens.length
    tight = true

    token = state.push('dl_open', 'dl', 1)
    token.map = listLines = [ startLine, 0 ]

    //
    // Iterate list items
    //

    dtLine = startLine
    ddLine = nextLine

    // One definition list can contain multiple DTs,
    // and one DT can be followed by multiple DDs.
    //
    // Thus, there is two loops here, and label is
    // needed to break out of the second one
    //
    /* eslint no-labels:0,block-scoped-var:0 */
    OUTER:
    for (;;) {
      prevEmptyEnd = false

      token = state.push('dt_open', 'dt', 1)
      token.map = [ dtLine, dtLine ]

      token = state.push('inline', '', 0)
      token.map = [ dtLine, dtLine ]
      token.content = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim()
      token.children = []

      token = state.push('dt_close', 'dt', -1)

      for (;;) {
        token = state.push('dd_open', 'dd', 1)
        token.map = itemLines = [ ddLine, 0 ]

        pos = contentStart
        max = state.eMarks[ddLine]
        offset = state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine])

        while (pos < max) {
          ch = state.src.charCodeAt(pos)

          if (isSpace(ch)) {
            if (ch === 0x09) {
              offset += 4 - offset % 4
            } else {
              offset++
            }
          } else {
            break
          }

          pos++
        }

        contentStart = pos

        oldTight = state.tight
        oldDDIndent = state.ddIndent
        oldIndent = state.blkIndent
        oldTShift = state.tShift[ddLine]
        oldSCount = state.sCount[ddLine]
        oldParentType = state.parentType
        state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2
        state.tShift[ddLine] = contentStart - state.bMarks[ddLine]
        state.sCount[ddLine] = offset
        state.tight = true
        state.parentType = 'deflist'

        newEndLine = ddLine
        while (++newEndLine < endLine && (state.sCount[newEndLine] >= state.sCount[ddLine] || state.isEmpty(newEndLine))) {
        }

        oldLineMax = state.lineMax
        state.lineMax = newEndLine

        state.md.block.tokenize(state, ddLine, newEndLine, true)

        state.lineMax = oldLineMax

        // If any of list item is tight, mark list as tight
        if (!state.tight || prevEmptyEnd) {
          tight = false
        }
        // Item become loose if finish with empty line,
        // but we should filter last element, because it means list finish
        prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1)

        state.tShift[ddLine] = oldTShift
        state.sCount[ddLine] = oldSCount
        state.tight = oldTight
        state.parentType = oldParentType
        state.blkIndent = oldIndent
        state.ddIndent = oldDDIndent

        token = state.push('dd_close', 'dd', -1)

        itemLines[1] = nextLine = state.line

        if (nextLine >= endLine) { break OUTER }

        if (state.sCount[nextLine] < state.blkIndent) { break OUTER }
        contentStart = skipMarker(state, nextLine)
        if (contentStart < 0) { break }

        ddLine = nextLine

        // go to the next loop iteration:
        // insert DD tag and repeat checking
      }

      if (nextLine >= endLine) { break }
      dtLine = nextLine

      if (state.isEmpty(dtLine)) { break }
      if (state.sCount[dtLine] < state.blkIndent) { break }

      ddLine = dtLine + 1
      if (ddLine >= endLine) { break }
      if (state.isEmpty(ddLine)) { ddLine++ }
      if (ddLine >= endLine) { break }

      if (state.sCount[ddLine] < state.blkIndent) { break }
      contentStart = skipMarker(state, ddLine)
      if (contentStart < 0) { break }

      // go to the next loop iteration:
      // insert DT and DD tags and repeat checking
    }

    // Finilize list
    token = state.push('dl_close', 'dl', -1)

    listLines[1] = nextLine

    state.line = nextLine

    // mark paragraphs tight if needed
    if (tight) {
      markTightParagraphs(state, listTokIdx)
    }

    return true
  }

  md.block.ruler.before('paragraph', 'deflist', deflist, { alt: [ 'paragraph', 'reference' ] })
}
