const { drop, dropRight, first, last } = require('lodash')

// ----- START LIQUID PATTERNS ----- //
const startTag = /(?:^ *?)?{%-?/
const endTag = /-?%}/
const contentRegex = /[\s\S]*?/gm
const emptyString = /^\s*?$/m
const nonEmptyString = /^.*?\S.*?/m
const ifStatement = new RegExp(startTag.source + ' if .*?' + endTag.source)
const ifRegex = new RegExp(ifStatement.source, 'gm')
const firstIfRegex = new RegExp(ifStatement.source, 'm')
const elseRegex = new RegExp(startTag.source + ' else ?' + endTag.source)
const endRegex = new RegExp(startTag.source + ' endif ?' + endTag.source, 'g')
const captureEndRegex = new RegExp('(' + endRegex.source + ')', 'g')
const dropSecondEndIf = new RegExp('(' + endRegex.source + contentRegex.source + ')' + endRegex.source, 'm')
const inlineEndRegex = new RegExp(nonEmptyString.source + endRegex.source, 'gm')
const inlineIfRegex = new RegExp(nonEmptyString.source + ifStatement.source, 'gm')

// include one level of nesting
const liquidBlockRegex = new RegExp(ifRegex.source + '((' + ifRegex.source + contentRegex.source + endRegex.source + '|[\\s\\S])*?)' + endRegex.source, 'gm')
const elseBlockRegex = new RegExp(elseRegex.source + '(?:' + ifRegex.source + contentRegex.source + endRegex.source + '|[\\s\\S])*?' + endRegex.source, 'gm')
// ----- END LIQUID PATTERNS ----- //

module.exports = function removeLiquidStatements (content, versionToDeprecate, nextOldestVersion) {
  // see tests/fixtures/remove-liquid-statements for examples
  const regexes = {
    // remove liquid only
    greaterThanVersionToDeprecate: new RegExp(startTag.source + ` if ?(?: currentVersion == ('|")'?free-pro-team@latest'?('|")  ?or)? currentVersion ver_gt ('|")${versionToDeprecate}('|") ` + endTag.source, 'gm'),
    andGreaterThanVersionToDeprecate1: new RegExp('(' + startTag.source + ` if .*?) and currentVersion ver_gt (?:'|")${versionToDeprecate}(?:'|")( ` + endTag.source + ')', 'gm'),
    andGreaterThanVersionToDeprecate2: new RegExp('(' + startTag.source + ` if )currentVersion ver_gt (?:'|")${versionToDeprecate}(?:'|") and (.*? ` + endTag.source + ')', 'gm'),
    notEqualsVersionToDeprecate: new RegExp('(' + startTag.source + ` if)(?:( currentVersion .*?) or)? currentVersion != (?:'|")${versionToDeprecate}(?:'|")( ` + endTag.source + ')', 'gm'),
    andNotEqualsVersionToDeprecate: new RegExp('(' + startTag.source + ` if .*?) and currentVersion != (?:'|")${versionToDeprecate}(?:'|")( ` + endTag.source + ')', 'gm'),
    // remove liquid and content
    lessThanNextOldestVersion: new RegExp(startTag.source + ` if .*?ver_lt ('|")${nextOldestVersion}('|") ?` + endTag.source, 'm'),
    equalsVersionToDeprecate: new RegExp(startTag.source + ` if .*?== ('|")${versionToDeprecate}('|") ?` + endTag.source, 'm')
  }

  let allLiquidBlocks = getLiquidBlocks(content)
  if (!allLiquidBlocks) return content

  // remove markup like ver_gt "2.13" and leave content
  content = removeLiquidOnly(content, allLiquidBlocks, regexes)

  // get latest liquidBlocks based on updated content
  allLiquidBlocks = getLiquidBlocks(content)

  if (allLiquidBlocks) {
    // remove content and surrounding markup like ver_lt "2.14" and == "2.13"
    content = removeLiquidAndContent(content, allLiquidBlocks, regexes)
  }

  // clean up
  content = removeExtraNewlines(content)

  return content
}

function getLiquidBlocks (content) {
  const liquidBlocks = content.match(liquidBlockRegex)
  if (!liquidBlocks) return

  // handle up to one level of nesting
  const innerBlocks = getInnerBlocks(liquidBlocks)

  return innerBlocks ? liquidBlocks.concat(innerBlocks) : liquidBlocks
}

function getInnerBlocks (liquidBlocks) {
  const innerBlocks = []

  liquidBlocks.forEach(block => {
    const ifStatements = block.match(ifRegex)
    if (!ifStatements) return

    // only process blocks that have more than one if statement
    if (!(ifStatements.length > 1)) return

    // drop first character so we don't match outer if statement
    // because it's already included in array of blocks
    const newBlock = block.slice(1)
    const innerIfStatements = newBlock.match(liquidBlockRegex)

    // add each inner if statement to array of blocks
    innerIfStatements.forEach(innerIfStatement => {
      innerBlocks.push(innerIfStatement)
    })
  })

  return innerBlocks
}

function removeLiquidOnly (content, allLiquidBlocks, regexes) {
  const blocksToUpdate = allLiquidBlocks
    .filter(block => {
      // inner blocks are processed separately, so we only care about first if statements
      const firstIf = block.match(firstIfRegex)
      if (block.match(regexes.greaterThanVersionToDeprecate)) return firstIf[0] === block.match(regexes.greaterThanVersionToDeprecate)[0]
      if (block.match(regexes.andGreaterThanVersionToDeprecate1)) return firstIf[0] === block.match(regexes.andGreaterThanVersionToDeprecate1)[0]
      if (block.match(regexes.andGreaterThanVersionToDeprecate2)) return firstIf[0] === block.match(regexes.andGreaterThanVersionToDeprecate2)[0]
      if (block.match(regexes.notEqualsVersionToDeprecate)) return firstIf[0] === block.match(regexes.notEqualsVersionToDeprecate)[0]
      if (block.match(regexes.andNotEqualsVersionToDeprecate)) return firstIf[0] === block.match(regexes.andNotEqualsVersionToDeprecate)[0]
      return false
    })

  blocksToUpdate.forEach(block => {
    let newBlock = block

    if (newBlock.match(regexes.andGreaterThanVersionToDeprecate1)) {
      newBlock = newBlock.replace(regexes.andGreaterThanVersionToDeprecate1, matchAndStatement1)
    }

    if (newBlock.match(regexes.andGreaterThanVersionToDeprecate2)) {
      newBlock = newBlock.replace(regexes.andGreaterThanVersionToDeprecate2, matchAndStatement2)
    }

    if (newBlock.match(regexes.notEqualsVersionToDeprecate)) {
      newBlock = newBlock.replace(regexes.notEqualsVersionToDeprecate, matchNotEqualsStatement)
    }

    if (newBlock.match(regexes.andNotEqualsVersionToDeprecate)) {
      newBlock = newBlock.replace(regexes.andNotEqualsVersionToDeprecate, matchAndNotEqualsStatement)
    }

    // replace else block with endif
    const elseBlock = getElseBlock(newBlock)

    if (elseBlock) {
      newBlock = newBlock.replace(`${elseBlock}`, '{% endif %}')
    }

    if (newBlock.match(regexes.greaterThanVersionToDeprecate) || newBlock.match(regexes.notEqualsVersionToDeprecate)) {
      newBlock = newBlock.replace(liquidBlockRegex, matchGreaterThan)
    }

    // we need more context to determine whether an if tag is inline or not
    const startIndex = content.indexOf(block)
    const endIndex = startIndex + block.length
    const leftIndex = startIndex - 10
    const blockWithLeftContext = content.slice(leftIndex, endIndex)

    // only remove newlines around non-inline tags
    if (blockWithLeftContext.match(inlineIfRegex) && block.match(inlineEndRegex)) {
      newBlock = removeLastNewline(newBlock)
    } else if (blockWithLeftContext.match(inlineIfRegex) && !block.match(inlineEndRegex)) {
      newBlock = removeLastNewline(newBlock)
    } else if (!blockWithLeftContext.match(inlineIfRegex) && block.match(inlineEndRegex)) {
      newBlock = removeFirstNewline(newBlock)
    } else {
      newBlock = removeFirstAndLastNewlines(newBlock)
    }

    // final replacement
    content = content.replace(block, newBlock)
  })

  return content
}

function matchGreaterThan (match, p1) {
  return p1
}

function matchAndStatement1 (match, p1, p2) {
  return p1 + p2
}

function matchAndStatement2 (match, p1, p2) {
  return p1 + p2
}

function matchNotEqualsStatement (match, p1, p2, p3) {
  if (!p2) return match
  return p1 + p2 + p3
}

function matchAndNotEqualsStatement (match, p1, p2) {
  return p1 + p2
}

function removeLiquidAndContent (content, allLiquidBlocks, regexes) {
  const blocksToRemove = allLiquidBlocks
    .filter(block => {
      const firstIf = block.match(firstIfRegex)
      if (block.match(regexes.lessThanNextOldestVersion)) return firstIf[0] === block.match(regexes.lessThanNextOldestVersion)[0]
      if (block.match(regexes.equalsVersionToDeprecate)) return firstIf[0] === block.match(regexes.equalsVersionToDeprecate)[0]
      return false
    })

  blocksToRemove.forEach(block => {
    const elseBlock = getElseBlock(block)

    // remove else conditionals but leave content
    if (elseBlock) {
      const numberOfEndTags = elseBlock[0].match(captureEndRegex)

      // remove the endif as part of removing the else block
      // if there are two endifs block, only remove the second one
      // this applies specifically to example8 in less-than-next-oldest fixture
      let newBlock
      if (numberOfEndTags.length > 1) {
        const blockWithFirstElseRemoved = elseBlock[0].replace(elseRegex, '')
        newBlock = blockWithFirstElseRemoved.replace(dropSecondEndIf, '$1')
      } else {
        newBlock = elseBlock[0].replace(elseRegex, '').replace(captureEndRegex, '')
      }
      content = content.replace(block, newBlock.trim())
    } else {
      content = content.replace(block, '')
    }
  })

  return content
}

function removeFirstNewline (block) {
  const lines = block.split(/\r?\n/)
  if (!first(lines).match(emptyString)) return block
  return drop(lines, 1).join('\n')
}

function removeLastNewline (block) {
  const lines = block.split(/\r?\n/)
  if (!last(lines).match(emptyString)) return block
  return dropRight(lines, 1).join('\n')
}

function removeFirstAndLastNewlines (block) {
  block = removeFirstNewline(block)
  return removeLastNewline(block)
}

function getElseBlock (block) {
  const firstIf = block.match(firstIfRegex)
  const elseBlock = block.match(elseBlockRegex)

  // if no else block, return the null result
  if (!elseBlock) return elseBlock

  // if there is an else block, check for an inner block
  const blockWithFirstIfRemoved = block.replace(firstIf[0], '')
  const innerBlock = blockWithFirstIfRemoved.match(liquidBlockRegex)

  // if no inner block, we can safely return the else block as is
  if (!innerBlock) return elseBlock

  // if there is an inner block, check if it is contained by the else block
  // if so, we can safely return the else block as is
  // see example8 in less-than-next-oldest
  if (elseBlock[0].includes(innerBlock[0])) return block.match(elseBlockRegex)

  // if the inner block contains the else block,
  // drop the inner block and return any other else blocks
  // see example7 in greater-than fixtures
  if (innerBlock[0].includes(elseBlock[0])) {
    const newBlock = block.replace(innerBlock[0], '')
    return newBlock.match(elseBlockRegex)
  }

  // otherwise, return any else matches on the original block
  return block.match(elseBlockRegex)
}

function removeExtraNewlines (content) {
  return content.replace(/(\r?\n){3,4}/gm, '\n\n')
}