import * as XLSX from "xlsx"
import type { CellObject } from "xlsx"
import type { RawData } from "../types"

const SCIENTIFIC_NOTATION_REGEX = /^[+-]?(?:\d+\.?\d*|\d*\.?\d+)e[+-]?\d+$/i

const convertScientificToDecimalString = (input: string): string => {
  const match = input.toLowerCase().match(/^([+-]?)(\d+)(?:\.(\d+))?e([+-]?\d+)$/)
  if (!match) return input

  const [, sign, integerPart, fractionalPart = "", exponentPart] = match
  const exponent = parseInt(exponentPart, 10)

  if (Number.isNaN(exponent)) return input

  const digits = `${integerPart}${fractionalPart}`

  if (exponent >= 0) {
    const decimalIndex = integerPart.length + exponent
    if (decimalIndex >= digits.length) {
      const zerosToAppend = decimalIndex - digits.length
      return `${sign}${digits}${"0".repeat(zerosToAppend)}`
    }

    const whole = digits.slice(0, decimalIndex) || "0"
    const fraction = digits.slice(decimalIndex).replace(/0+$/, "")
    return fraction ? `${sign}${whole}.${fraction}` : `${sign}${whole}`
  }

  const decimalIndex = integerPart.length + exponent
  if (decimalIndex <= 0) {
    const zerosToPrepend = Math.abs(decimalIndex)
    const fractionDigits = `${"0".repeat(zerosToPrepend)}${digits}`.replace(/0+$/, "")
    return fractionDigits ? `${sign}0.${fractionDigits}` : "0"
  }

  const whole = digits.slice(0, decimalIndex) || "0"
  const fractionDigits = digits.slice(decimalIndex).replace(/0+$/, "")
  return fractionDigits ? `${sign}${whole}.${fractionDigits}` : `${sign}${whole}`
}

const numberToPlainString = (value: number): string => {
  if (!Number.isFinite(value)) return ""
  const stringified = value.toString()
  return SCIENTIFIC_NOTATION_REGEX.test(stringified) ? convertScientificToDecimalString(stringified) : stringified
}

const normalizeFromCell = (cell: CellObject | undefined): string | undefined => {
  if (!cell) return undefined

  const { v, w } = cell
  const cellType = (cell.t as string) || ""

  if (typeof w === "string" && w.trim() !== "") {
    const trimmed = w.trim()
    return SCIENTIFIC_NOTATION_REGEX.test(trimmed) ? convertScientificToDecimalString(trimmed) : trimmed
  }

  switch (cellType) {
    case "n":
      if (typeof v === "number") return numberToPlainString(v)
      if (typeof v === "string") {
        const trimmed = v.trim()
        return SCIENTIFIC_NOTATION_REGEX.test(trimmed) ? convertScientificToDecimalString(trimmed) : trimmed
      }
      return v === undefined || v === null ? "" : String(v)
    case "s":
    case "str":
      return typeof v === "string" ? v : v === undefined || v === null ? "" : String(v)
    case "b":
      return v ? "TRUE" : "FALSE"
    case "d":
      if (v instanceof Date) return v.toISOString()
      if (typeof v === "number") {
        const date = XLSX.SSF.parse_date_code(v)
        if (date) {
          const year = date.y.toString().padStart(4, "0")
          const month = date.m.toString().padStart(2, "0")
          const day = date.d.toString().padStart(2, "0")
          return `${year}-${month}-${day}`
        }
      }
      return v === undefined || v === null ? "" : String(v)
    default:
      return v === undefined || v === null ? "" : String(v)
  }
}

const normalizeCellValue = (value: unknown, cell: CellObject | undefined): string => {
  if (value === undefined || value === null || value === "") {
    const fallback = normalizeFromCell(cell)
    return fallback !== undefined ? fallback : ""
  }

  if (typeof value === "number") {
    return numberToPlainString(value)
  }

  if (typeof value === "string") {
    const trimmed = value.trim()
    if (trimmed === "") {
      const fallback = normalizeFromCell(cell)
      return fallback !== undefined ? fallback : ""
    }
    return SCIENTIFIC_NOTATION_REGEX.test(trimmed) ? convertScientificToDecimalString(trimmed) : value
  }

  if (typeof value === "boolean") {
    return value ? "TRUE" : "FALSE"
  }

  if (value instanceof Date) {
    return value.toISOString()
  }

  const fallback = normalizeFromCell(cell)
  return fallback !== undefined ? fallback : String(value)
}

export const mapWorkbook = (workbook: XLSX.WorkBook, sheetName?: string): RawData[] => {
  const sheetToUse = sheetName || workbook.SheetNames[0]
  const worksheet = workbook.Sheets[sheetToUse]

  const rangeRef = worksheet["!ref"] || "A1"
  const sheetRange = XLSX.utils.decode_range(rangeRef)

  const sheetData = XLSX.utils.sheet_to_json<unknown[]>(worksheet, {
    header: 1,
    raw: true,
    defval: "",
    blankrows: true,
  })

  const columnCount = Math.max(
    sheetRange.e.c - sheetRange.s.c + 1,
    ...sheetData.map((row) => row.length),
  )

  return sheetData.map((row, rowIndex) => {
    const sheetRow = sheetRange.s.r + rowIndex
    const normalizedRow: string[] = []

    for (let columnOffset = 0; columnOffset < columnCount; columnOffset++) {
      const sheetColumn = sheetRange.s.c + columnOffset
      const cellAddress = XLSX.utils.encode_cell({ r: sheetRow, c: sheetColumn })
      const cell = worksheet[cellAddress] as CellObject | undefined
      const value = row[columnOffset]
      normalizedRow.push(normalizeCellValue(value, cell))
    }

    return normalizedRow as RawData
  })
}
