import * as XLSX from '@sheet/crypto'

/**
 * Checks if an excel row is blank or not
 *
 * @param row object is of shape {[key: string]: any}
 */
export const isBlankRow = (row: any) => {
  for (const key in row) {
    if (key !== '__rowNum__') {
      return false
    }
  }
  return true
}

/**
 * Extracts row and column number from xlmap rule.
 *
 * Input string should be in form of
 * either "MATCH F R[2]C[0]: CASH BALANCE" or "RELATIVE R[10]C[6]"
 */
export const extractRowAndCol = (str: string) => {
  // Regular expression to match and capture the values inside square brackets
  const regex = /R\[(\d+)\]C\[(\d+)\]/

  // Match the regular expression against the input string
  const match = str.match(regex)

  if (!match) {
    return null
  }

  // Extract values from the match groups
  const row = parseInt(match[1], 10)
  const column = parseInt(match[2], 10)

  return {
    row,
    column
  }
}

/**
 * Generate an A1-Style excel cell address from xlmap rule.
 *
 * Expect "ABSOLUTE D8" or "RELATIVE R[10]C[6]" or
 * "MATCH C R[0]C[4]:Common Equity Tier 1 (CET1)" kinds of string as rule input
 */
export const getCellAddress = (rule: string, arrayOfObjects: any[]) => {
  if (rule.startsWith('ABSOLUTE ')) {
    rule = rule.replace('ABSOLUTE ', '')
  }

  if (rule.startsWith('RELATIVE ')) {
    const rowAndCol = extractRowAndCol(rule)

    if (rowAndCol) {
      const { row, column } = rowAndCol

      // Generate an A1-Style address string from a SheetJS cell address
      // Spreadsheet applications typically display ordinal row numbers,
      // where 1 is the first row, 2 is the second row, etc. The numbering starts at 1.
      // SheetJS follows JavaScript counting conventions,
      // where 0 is the first row, 1 is the second row, etc. The numbering starts at 0.
      // Therefore, we have to subtract 1 from row and column to match SheetJS indexing convention
      rule = XLSX.utils.encode_cell({ r: row - 1, c: column - 1 })
    }
  }

  if (rule.startsWith('MATCH ')) {
    let targetValue = ''

    // using a regular expression to match "C[x]:" and extract the value after it
    const match = rule.match(/C\[\d+\]:(.+)/)

    // Check if there is a match
    if (match) {
      // Extract the value after "C[x]:"
      targetValue = match[1]
    }

    // Split the string by spaces to get target row/column
    const splittedArray = rule.split(' ')

    // Extract the second word
    const secondWord = splittedArray[1]

    let targetColumn = ''
    let targetRow = -1
    let cellAddress = ''

    // Check if the secondWord is a number
    if (!isNaN(Number(secondWord))) {
      targetRow = parseInt(secondWord)
    } else {
      targetColumn = secondWord
    }

    if (targetRow !== -1) {
      // sheetJS index starts from 0,
      // therefore, decremented 1 to make it correct row address for js array
      const row = arrayOfObjects[targetRow - 1]
      for (const col in row) {
        if (col !== '__rowNum__' && row[col] === targetValue) {
          cellAddress = col + targetRow
          break
        }
      }
    } else {
      for (let i = 0; i < arrayOfObjects.length; i++) {
        const row = arrayOfObjects[i]
        if (row[targetColumn] === targetValue) {
          // sheetJS index starts from 0,
          // therefore, incremented 1 to make it correct row address
          const rowIndex = i + 1
          cellAddress = targetColumn + rowIndex
          break
        }
      }
    }

    // Converts A1 cell address to 0-indexed form
    const matchedCellAddress = XLSX.utils.decode_cell(cellAddress)

    // extract number of rows and columns that we have to move
    // from matched cell to reach target cell
    const rowAndCol = extractRowAndCol(rule)

    if (rowAndCol) {
      const { row, column } = rowAndCol

      // Converts 0-indexed cell address to A1 form
      rule = XLSX.utils.encode_cell({
        r: matchedCellAddress.r + row,
        c: matchedCellAddress.c + column
      })
    }
  }

  return rule
}

/**
 * Generate an A1-Style excel cell address for last cell
 *
 * @param start A1 style excel cell address
 * @param finish XLMAP_FINISH attribute of xlmap rule
 * @param arrayOfObjects an array of row objects
 * @returns
 */
export const getFinishingCell = (
  start: string,
  finish: string,
  arrayOfObjects: any[]
) => {
  // in this case an individual cell would be extracted
  if (finish === '') {
    return start
  }

  if (finish.startsWith('ABSOLUTE ')) {
    finish = finish.replace('ABSOLUTE ', '')
  }

  if (finish.startsWith('RELATIVE ')) {
    const rowAndCol = extractRowAndCol(finish)
    if (rowAndCol) {
      const { row, column } = rowAndCol

      const { r, c } = XLSX.utils.decode_cell(start)

      // finish is relative to starting point
      // therefore, we need to add extracted row and columns
      // in starting cell address to get actual finishing cell
      finish = XLSX.utils.encode_cell({ r: r + row, c: c + column })
    }
  }

  if (finish.startsWith('MATCH ')) {
    finish = getCellAddress(finish, arrayOfObjects)
  }

  if (finish === 'LASTDOWN') {
    const { r, c } = XLSX.utils.decode_cell(start)
    const colName = XLSX.utils.encode_col(c)
    let lastNonBlank = r
    for (let i = r + 1; i < arrayOfObjects.length; i++) {
      const row = arrayOfObjects[i]
      if (!row[colName]) {
        break
      }
      lastNonBlank = i
    }
    finish = colName + (lastNonBlank + 1) // excel numbering starts from 1. So incremented 1 to 0 based index
  }

  if (finish === 'BLANKROW') {
    const { r } = XLSX.utils.decode_cell(start)
    let lastNonBlankRow = r
    for (let i = r + 1; i < arrayOfObjects.length; i++) {
      const row = arrayOfObjects[i]
      if (isBlankRow(row)) {
        break
      }
      lastNonBlankRow = i
    }
    const row = arrayOfObjects[lastNonBlankRow]

    // Get the keys of the object (excluding '__rowNum__')
    const keys = Object.keys(row).filter((key) => key !== '__rowNum__')

    // Finding last column in a row
    // Find the key with the highest alphanumeric value (assumes keys are letters)
    const lastColumn = keys.reduce(
      (maxKey, currentKey) => (currentKey > maxKey ? currentKey : maxKey),
      ''
    )

    // make finishing cell address in A1 style
    finish = lastColumn + (lastNonBlankRow + 1) // excel numbering starts from 1. So incremented 1 to 0 based index
  }

  return finish
}
