import {
  parseUnits as parseUnitsViem,
  formatUnits as formatUnitsViem,
  isAddress,
  Hex,
  Address,
  isHex,
} from "viem"
import { v4, V4Options } from "uuid"
import { TAsset } from "@/types"
import { USER_REJECTED_REQUEST_CODE } from "@/constants"

export const formatUnits = (value: string | bigint, unitName = 18): string => {
  try {
    if (!value) return "0"
    if (typeof value === "bigint") return formatUnitsViem(value, unitName)
    return formatUnitsViem(BigInt(value), unitName)
  } catch (err) {
    console.error("[ERROR ON FORMAT UNITS]:", err)
    return "0"
  }
}

export const parseUnits = (value: string, unitName = 18): bigint => {
  return parseUnitsViem(value, unitName)
}

export const truncateString = (str: string, start = 6, end = 4) => {
  if (!str) return ""
  if (str.length <= start + end) {
    return str
  }
  const truncated = `${str.substring(0, start)}...${str.substring(
    str.length - end,
  )}`
  return truncated
}

export const formatNumberWithCommas = (
  _input: string | number,
): string | number => {
  if (!_input) return _input

  const result = _input
    .toString()
    .replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ",")
  return result
}

export const sliceDecimalString = (
  input: string,
  decimals = 6,
  addThousandsFormatting = false,
): string => {
  if (!input) return "0"
  const parts = input.split(".")

  if (parts.length === 1) {
    // Input string does not contain a decimal point
    return input
  }

  const integerPart = parts[0]

  let decimalPart = parts[1]

  decimalPart = decimalPart.slice(0, decimals)

  while (decimalPart.endsWith("0")) {
    // Remove trailing zeros
    decimalPart = decimalPart.slice(0, -1)
  }

  if (decimalPart.length === 0) {
    return addThousandsFormatting
      ? (formatNumberWithCommas(integerPart) as string)
      : integerPart
  }

  const result = `${integerPart}.${decimalPart}`

  if (addThousandsFormatting) {
    return formatNumberWithCommas(result) as string
  }

  return result
}

// This was defined by product team, order: verified, balanceOf, alphabetical
export function sortAssets(assets: TAsset[]): TAsset[] {
  return assets.sort((a, b) => {
    if (a.verified && !b.verified) {
      return -1
    } else if (!a.verified && b.verified) {
      return 1
    } else if (a.balanceOf && b.balanceOf) {
      const balanceA = parseFloat(a.balanceOf.formatted)
      const balanceB = parseFloat(b.balanceOf.formatted)
      return balanceB - balanceA
    } else if (a.balanceOf && !b.balanceOf) {
      return -1
    } else if (!a.balanceOf && b.balanceOf) {
      return 1
    } else {
      return a.name.localeCompare(b.name)
    }
  })
}

export const minifyCurrencyValue = (value: string | number) => {
  // remove $ and commas if the value is a string
  const numericValue =
    typeof value === "string" ? parseFloat(value.replace(/\$|,/g, "")) : value

  if (isNaN(numericValue)) {
    return "Invalid value"
  }

  // just format if 1e3 and minify if above 1k
  if (numericValue < 1e3) {
    return new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "USD",
      minimumFractionDigits: 2,
      maximumFractionDigits: 2,
    }).format(numericValue)
  } else if (numericValue < 1e6) {
    return "$" + (numericValue / 1e3).toFixed(1) + "k"
  } else if (numericValue < 1e9) {
    return "$" + (numericValue / 1e6).toFixed(1) + "M"
  } else if (numericValue < 1e12) {
    return "$" + (numericValue / 1e9).toFixed(1) + "B"
  } else {
    return "$" + (numericValue / 1e12).toFixed(1) + "T"
  }
}

export const openInNewTab = (href: string) => {
  window.open(href, "_blank", "noreferrer noopener")
}

export const openInSameTab = (href: string) => {
  window.location.href = href
}

export const copyToClipboard = async (text?: string) => {
  try {
    const toCopy = text || ""
    await navigator.clipboard.writeText(toCopy)
  } catch (err) {
    console.error("Failed to copy: ", err)
  }
}

export const generateUUID = (options?: V4Options) => {
  return v4(options)
}

export const formatRejectMetamaskErrorMessage = (err: any) => {
  return err?.code === USER_REJECTED_REQUEST_CODE ||
    err?.code === "ACTION_REJECTED"
    ? "Transaction not signed. If this was an error, please attempt to sign again."
    : err?.mesage
}

export const isNotZero = (value: string) => {
  const trimmedValue = value.trim()
  // Check if the string is empty or contains only zeros or decimal points
  const regex = /^0*\.?0*$/
  return !regex.test(trimmedValue)
}
export const isZero = (value: string) => !isNotZero(value)

function detectJsonObjects(input: string): object[] {
  const jsonObjects: object[] = []
  const jsonRegex = /\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}/g

  // Remove comments from JSON (single-line and multi-line)
  const cleanedInput = input.replace(/\/\/.*|\/\*[\s\S]*?\*\//g, "")

  let match: RegExpExecArray | null
  while ((match = jsonRegex.exec(cleanedInput)) !== null) {
    try {
      const jsonObject = JSON.parse(match[0])
      jsonObjects.push(jsonObject)
    } catch (error) {
      // Ignore invalid JSON matches
    }
  }

  return jsonObjects
}

export const extractObjectFromAiResponse = (
  aiMessage: string,
  keys: string[],
): Record<string, any> | null => {
  try {
    const jsonMatchesData = detectJsonObjects(aiMessage).map((obj) => ({
      json: obj,
      keys: Object.keys(obj),
    }))

    // Find the object whose keys exactly match the provided keys (order-independent)
    for (const jsonMatch of jsonMatchesData) {
      const objJson = jsonMatch?.json || ""
      const objKeys = jsonMatch?.keys || []

      if (
        objKeys.length === keys.length &&
        objKeys.every((key) => keys.includes(key))
      ) {
        return objJson
      }
    }
    return null // Return null if no matching object is found
  } catch (e) {
    console.error("Error parsing AI message:", e)
    return null
  }
}

export const removeMatchingJSON = (message: string, jsonKey: string[]) => {
  // Split the message into parts based on JSON objects
  const parts = message.split(/(\{[^}]+\})/g)

  // Filter out the parts that are JSON objects and match the keys to remove
  const filteredParts = parts.filter((part) => {
    try {
      const obj = JSON.parse(part)
      return !jsonKey.every((key) => key in obj)
    } catch (e) {
      return true // Keep non-JSON parts
    }
  })

  // Join the filtered parts back into a single message
  return filteredParts.join("")
}

export const formatAmountWithOptions = (
  amount: number | string,
  options?: Intl.NumberFormatOptions,
) => {
  const stringifiedAmount = amount.toString()
  const amountInNumber = parseFloat(stringifiedAmount)
  if (isNaN(amountInNumber)) return stringifiedAmount

  return amountInNumber.toLocaleString("en-US", {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
    // style: 'currency',
    // currency: 'USD',
    notation: "compact",
    compactDisplay: "short",
    signDisplay: "never",
    ...(options ? options : {}),
  })
}

export const isValidEthereumAddress = (address: string): boolean => {
  return isAddress(address)
}

export const removeSectionAndJson = (
  message: string,
  stringToRemove: string,
) => {
  const index = message.indexOf(stringToRemove)
  if (index !== -1) {
    return message.substring(0, index)
  }
  return message
}

export const formatHours = (hours: number) => {
  const days = Math.floor(hours / 24)
  const remainingHours = hours % 24
  return {
    day: days > 0 ? `${days}` : "",
    hour: `${remainingHours}`,
    seconds: `${hours * 60 * 60}`,
  }
}

export function convertToFullString(value: string | number): string {
  const numberValue = typeof value === "string" ? Number(value) : value
  return numberValue.toLocaleString("fullwide", {
    useGrouping: false,
  })
}

// Utility function to check if a string is a valid hex
const isValidHex = (value: string) => isHex(value, { strict: true })

export const isHexMatch = (
  hexOrAddress1: Hex | Address | undefined,
  hexOrAddress2: Hex | Address | undefined,
): boolean => {
  if (!hexOrAddress1 || !hexOrAddress2) return false

  // Trim whitespace and ensure lowercase for case-insensitive comparison
  const normalize = (value: string) => value.trim().toLowerCase()

  // Ensure both inputs are valid hex strings
  if (!isValidHex(hexOrAddress1) || !isValidHex(hexOrAddress2)) return false

  // Normalize inputs
  const normalized1 = normalize(hexOrAddress1)
  const normalized2 = normalize(hexOrAddress2)

  // If both are already 42-character Ethereum addresses, compare directly
  if (normalized1.length === 42 && normalized2.length === 42)
    return normalized1 === normalized2

  // Extract last 40 characters (20-byte address from hex) and normalize
  const extractAddress = (hex: string) =>
    hex.length >= 42 ? `0x${hex.slice(-40)}` : hex

  return (
    normalize(extractAddress(normalized1)) ===
    normalize(extractAddress(normalized2))
  )
}
