/**
 * Converts the given JSON object array to a CSV string.
 * @param data - the array of JSON objects to convert.
 */
export const convertToCSV = (data: any) => {
  const replacer = (key: any, value: any) => (value === null ? '' : value)
  const headerFields = Object.keys(data[0])
  let csvTest
  let invalidString = false
  const headers = headerFields.map((field) => {
    let firstFoundType: string | null = null
    let hasMixedTypes: boolean = false
    let rowNumError: number = -1

    const longestValueForField = data
      .map((row: any, index: number) => {
        if (row[field] || row[field] === '') {
          if (firstFoundType) {
            let currentFieldType =
              row[field] === '' || typeof row[field] === 'string'
                ? 'chars'
                : 'number'

            if (!hasMixedTypes) {
              hasMixedTypes = currentFieldType !== firstFoundType
              rowNumError = hasMixedTypes ? index + 1 : -1
            }
          } else {
            if (row[field] === '') {
              firstFoundType = 'chars'
            } else {
              firstFoundType =
                typeof row[field] === 'string' ? 'chars' : 'number'
            }
          }

          let byteSize

          if (typeof row[field] === 'string') {
            byteSize = getByteSize(row[field])
          }

          return byteSize
        }
      })
      .sort((a: number, b: number) => b - a)[0]
    if (longestValueForField && longestValueForField > 32765) {
      invalidString = true
    }
    if (hasMixedTypes) {
      console.error(
        `Row (${rowNumError}), Column (${field}) has mixed types: ERROR`
      )
    }

    return `${field}:${firstFoundType === 'chars' ? '$char' : ''}${
      longestValueForField
        ? longestValueForField
        : firstFoundType === 'chars'
        ? '1'
        : 'best'
    }.`
  })

  if (invalidString) {
    return 'ERROR: LARGE STRING LENGTH'
  }

  csvTest = data.map((row: any) => {
    const fields = Object.keys(row).map((fieldName, index) => {
      let value
      const currentCell = row[fieldName]

      if (typeof currentCell === 'number') return currentCell

      // stringify with replacer converts null values to empty strings
      value = currentCell === null ? '' : currentCell

      // if there any present, it should have preceding (") for escaping
      value = value.replace(/"/g, `""`)

      // also wraps the value in double quotes
      value = `"${value}"`

      if (
        value.substring(1, value.length - 1).search(/(\t|\n|\r|,|\'|\")/gm) < 0
      ) {
        // Remove wrapping quotes for values that don't contain special characters
        value = value.substring(1, value.length - 1)
      }

      value = value.replace(/\r\n/gm, '\n')

      if (value === '' && headers[index].includes('best')) {
        value = '.'
      }

      return value
    })
    return fields.join(',')
  })

  let finalCSV =
    headers.join(',').replace(/,/g, ' ') + '\r\n' + csvTest.join('\r\n')

  return finalCSV
}

const getByteSize = (str: string) => {
  let byteSize = str.length
  for (let i = str.length - 1; i >= 0; i--) {
    const code = str.charCodeAt(i)
    if (code > 0x7f && code <= 0x7ff) byteSize++
    else if (code > 0x7ff && code <= 0xffff) byteSize += 2
    if (code >= 0xdc00 && code <= 0xdfff) i-- //trail surrogate
  }
  return byteSize
}
