import React from 'react'
import type { Label, ThumbSize } from './types'

export const calculateClickValue = (
  event: React.MouseEvent,
  container: HTMLDivElement,
  min: number,
  max: number,
  step: number,
  vertical: boolean,
  rtl: boolean,
) => {
  const clickPosition = getClickPosition(event, container, vertical, rtl)
  const value = min + clickPosition * (max - min)
  return roundToStep(value, step)
}

export const calculateMoveValue = (
  event: MouseEvent,
  container: HTMLDivElement,
  min: number,
  max: number,
  step: number,
  vertical: boolean,
  rtl: boolean,
) => {
  const rect = container.getBoundingClientRect()
  const position = vertical
    ? calculateVerticalPosition(event.clientY, rect)
    : calculateHorizontalPosition(event.clientX, rect, rtl)

  if (typeof position === 'string') {
    return position === 'max' ? max : min
  }

  const value = min + position * (max - min)

  return roundToStep(value, step)
}

export const calculateVerticalPosition = (mouseY: number, rect: DOMRect) => {
  if (mouseY < rect.top) {
    return 'max'
  }

  if (mouseY > rect.bottom) {
    return 'min'
  }

  return Math.min(Math.max((rect.bottom - mouseY) / rect.height, 0), 1)
}

export const calculateHorizontalPosition = (mouseX: number, rect: DOMRect, rtl: boolean) => {
  if (mouseX < rect.left) {
    return rtl ? 'max' : 'min'
  }

  if (mouseX > rect.right) {
    return rtl ? 'min' : 'max'
  }

  const relativeX = rtl ? rect.right - mouseX : mouseX - rect.left
  return Math.min(Math.max(relativeX / rect.width, 0), 1)
}

export const calculateLabelPosition = (
  min: number,
  max: number,
  labels: Label[],
  label: Label,
  index: number,
) => {
  if (typeof label === 'object' && 'value' in label) {
    return `${((label.value - min) / (max - min)) * 100}%`
  }

  return `${(index / (labels.length - 1)) * 100}%`
}

export const calculateTooltipPosition = (
  min: number,
  max: number,
  value: number,
  thumbSize: ThumbSize,
  vertical: boolean,
  rtl: boolean,
) => {
  const percent = (value - min) / (max - min)
  const margin =
    percent > 0.5
      ? `-${(percent - 0.5) * thumbSize.value}${thumbSize.unit}`
      : `${(0.5 - percent) * thumbSize.value}${thumbSize.unit}`

  if (vertical) {
    return {
      bottom: `${percent * 100}%`,
      marginBottom: margin,
    }
  }

  return rtl
    ? { right: `${percent * 100}%`, marginRight: margin }
    : { left: `${percent * 100}%`, marginLeft: margin }
}

export const getClickPosition = (
  event: React.MouseEvent,
  container: HTMLDivElement,
  vertical: boolean,
  rtl: boolean,
) => {
  // MouseEvent doesn't have `offsetX`/`offsetY`, so we need to use `event.nativeEvent` instead
  const { offsetX, offsetY } = event.nativeEvent
  const { offsetWidth, offsetHeight } = container

  if (vertical) {
    return 1 - offsetY / offsetHeight
  }

  return rtl ? 1 - offsetX / offsetWidth : offsetX / offsetWidth
}

export const getLabelValue = (
  min: number,
  max: number,
  labels: Label[],
  label: Label,
  index: number,
) =>
  typeof label === 'object' && 'value' in label
    ? label.value
    : min + (index / (labels.length - 1)) * (max - min)

export const getNearestValueIndex = (value: number, values: number[]) => {
  const valuesLength = values.length

  if (value < values[0]) {
    return 0
  }

  if (value > values[valuesLength - 1]) {
    return valuesLength - 1
  }

  const distances = values.map((v) => Math.abs(v - value))
  const min = Math.min(...distances)
  const firstIndex = distances.indexOf(min)

  return value < values[firstIndex] ? firstIndex : distances.lastIndexOf(min)
}

export const getThumbSize = (element: HTMLDivElement, vertical: boolean): ThumbSize | null => {
  const value = globalThis
    .getComputedStyle(element, null)
    .getPropertyValue(
      vertical ? '--cui-range-slider-thumb-height' : '--cui-range-slider-thumb-width',
    )

  const regex = /^(\d+\.?\d*)([%a-z]*)$/i
  const match = value.match(regex)

  if (match) {
    return {
      value: Number.parseFloat(match[1]),
      unit: match[2] || null,
    }
  }

  return null
}

export const roundToStep = (number: number, step: number) => {
  const _step = step === 0 ? 1 : step
  return Math.round(number / _step) * _step
}

export const updateGradient = (
  min: number,
  max: number,
  values: number[],
  vertical: boolean,
  rtl: boolean,
) => {
  const minVal = Math.min(...values)
  const maxVal = Math.max(...values)

  const from = ((minVal - min) / (max - min)) * 100
  const to = ((maxVal - min) / (max - min)) * 100

  const direction = vertical ? 'to top' : rtl ? 'to left' : 'to right'

  return {
    backgroundImage:
      values.length === 1
        ? `linear-gradient(
    ${direction},
    var(--cui-range-slider-track-in-range-bg) 0%,
    var(--cui-range-slider-track-in-range-bg) ${to}%,
    transparent ${to}%,
    transparent 100%
  )`
        : `linear-gradient(
    ${direction},
    transparent 0%,
    transparent ${from}%,
    var(--cui-range-slider-track-in-range-bg) ${from}%,
    var(--cui-range-slider-track-in-range-bg) ${to}%,
    transparent ${to}%,
    transparent 100%
  )`,
  }
}

export const updateValue = (value: number, values: number[], distance: number, index: number) => {
  const newValue = [...values]
  newValue[index] = validateValue(value, values, distance, index)
  return newValue
}

export const validateValue = (value: number, values: number[], distance: number, index: number) => {
  // If there's only one value, return it as is
  if (values.length === 1) {
    return value
  }

  // Determine previous and next values if they exist
  const prevValue: number | undefined = index > 0 ? values[index - 1] : undefined
  const nextValue: number | undefined = index < values.length - 1 ? values[index + 1] : undefined

  // If it's the first element, ensure it's not too close to the next value
  if (index === 0 && nextValue !== undefined) {
    return Math.min(value, nextValue - distance)
  }

  // If it's the last element, ensure it's not too close to the previous value
  if (index === values.length - 1 && prevValue !== undefined) {
    return Math.max(value, prevValue + distance)
  }

  // For middle elements, ensure the value is within the allowed distance from both neighbors
  if (prevValue !== undefined && nextValue !== undefined) {
    const minVal = prevValue + distance
    const maxVal = nextValue - distance
    return Math.min(Math.max(value, minVal), maxVal)
  }

  // Fallback: If for some reason prevValue or nextValue is undefined, return the original value
  return value
}
