'use client'

import React, { useState, type ReactElement, useEffect, useCallback, useRef } from 'react'
import * as Slider from '@radix-ui/react-slider'
import { type ISliderRangeValues, type ISliderRangeProps } from '@/Types'
import Label from './Label'
import Description from './Description'
import { makeClassName } from '@/utils'
import { getDefaultThumbClass } from '@/Components/Inputs/sliderHelpers'

const validateRange = (range: ISliderRangeValues | undefined, min: number, max: number, staticWidth: boolean = false): ISliderRangeValues => {
  let validRange: ISliderRangeValues = [
    Math.min.apply(null, [min, max]),
    Math.max.apply(null, [min, max])
  ]

  if (range !== undefined) {
    const minNewVal = Math.min.apply(null, range)
    const maxNewVal = Math.max.apply(null, range)
    const newValSpan = maxNewVal - minNewVal
    validRange = [minNewVal, maxNewVal]
    if (staticWidth && newValSpan <= (max - min)) {
      if (minNewVal < min) {
        validRange = [min, min + newValSpan]
      } else if (maxNewVal > max) {
        validRange = [max - newValSpan, max]
      }
    } else {
      validRange = [
        Math.max.apply(null, [min, minNewVal]),
        Math.min.apply(null, [max, maxNewVal])
      ]
    }
  }

  return validRange as ISliderRangeValues
}

const SliderRange = ({
  id,
  testId = 'sliderRange',
  error,
  size = 'med',
  disabled = false,
  label,
  description,
  before,
  after,
  max = 100,
  min = 1,
  step = 1,
  range,
  onChange,
  onChangeComplete,
  wrapperClassName,
  thumbClass,
  rangeClass,
  startThumbClass,
  endThumbClass,
  retainUndefinedOnLoad = false

}: ISliderRangeProps): ReactElement => {
  const thumbDefaultClassName = getDefaultThumbClass(size)
  const rangeDefaultClassName = 'absolute bg-blue-400 hover:bg-blue-600 rounded-full h-full cursor-pointer'

  const [rangeValue, setRangeValue] = useState<number[] | undefined>()
  const [dragging, setDragging] = useState(false)
  const [dragStartValue, setDragStartValue] = useState<number[] | null>(null)
  const [trackWidth, setTrackWidth] = useState<number>(0)
  const trackRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (trackRef.current) {
      const trackRect = trackRef.current.getBoundingClientRect()
      setTrackWidth(trackRect.width)
    }
  }, [trackRef.current])

  const onSliderChange = (value: number[], staticWidth: boolean = false): void => {
    const newVal = validateRange(
      [
        Math.min.apply(null, value),
        Math.max.apply(null, value)
      ],
      min,
      max,
      staticWidth
    )

    if (typeof onChange === 'function') {
      onChange(newVal)
    }
    setRangeValue(newVal.slice())
    setDragStartValue(newVal.slice())
  }

  const onSliderChangeCommit = (value: number[], staticWidth: boolean = false): void => {
    const newVal = validateRange([
      Math.min.apply(null, value),
      Math.max.apply(null, value)
    ], min, max, staticWidth)
    if (typeof onChangeComplete === 'function') {
      onChangeComplete(newVal)
    }
  }

  const handlePointerDown = useCallback((event: React.MouseEvent) => {
    event.stopPropagation()
    event.preventDefault()
    setDragging(true)
    if (rangeValue === undefined) return
    setDragStartValue(rangeValue.slice())
  }, [rangeValue])

  useEffect(() => {
    const handleDragMove = (event: MouseEvent): void => {
      if (!dragging || !dragStartValue) return

      event.stopPropagation()
      event.preventDefault()

      const dataSpan = max - min
      const mx = event.movementX
      const offset = mx < 0
        ? -1
        : mx > 0
          ? 1
          : 0
      const delta = ((event.movementX + offset) / trackWidth) * dataSpan

      const newRange: [number, number] = [
        dragStartValue[0] + delta,
        dragStartValue[1] + delta
      ]

      onSliderChange(newRange.slice(), true)
    }

    const handleDragEnd = (): void => {
      setDragging(false)
      if (rangeValue !== undefined && rangeValue.length === 2) {
        onSliderChangeCommit([rangeValue[0], rangeValue[1]], true)
      }
    }

    // Add global event listeners
    document.addEventListener('pointermove', handleDragMove)
    document.addEventListener('pointerup', handleDragEnd)

    return () => {
      // Remove global event listeners
      document.removeEventListener('pointermove', handleDragMove)
      document.removeEventListener('pointerup', handleDragEnd)
    }
  }, [dragging, dragStartValue])

  useEffect(() => {
    if (range === undefined && retainUndefinedOnLoad) {
      setRangeValue(validateRange(range, min, max))
    } else {
      const newVal = validateRange(range, min, max)
      setRangeValue(newVal)
      if (newVal.toString() !== range?.toString()) {
        onSliderChange(newVal)
        onSliderChangeCommit(newVal)
      }
    }
  }, [range])

  const initialRange = validateRange(range, min, max)

  return (
    <div className={wrapperClassName}>
      {
        typeof label === 'string'
          ? <Label disabled={disabled} label={label} />
          : label !== undefined ? label : ''
      }
      {
        typeof description === 'string'
          ? <Description disabled={disabled} description={description} />
          : description !== undefined ? description : ''
      }
      {before !== undefined ? before : ''}
      <Slider.Root
        className={
          makeClassName({
            defaultClassName: 'relative flex items-center select-none touch-none h-5',
            extras: [
              disabled ? 'cursor-not-allowed opacity-50' : undefined
            ]
          })}
        disabled={disabled}
        defaultValue={initialRange}
        value={rangeValue}
        min={min}
        max={max}
        step={step}
        minStepsBetweenThumbs={1}
        onValueChange={onSliderChange}
        onValueCommit={onSliderChangeCommit}
      >
        <Slider.Track
          ref={trackRef}
          className={
            makeClassName({
              defaultClassName: 'bg-slate-400 relative grow rounded-full h-[3px]',
              extras: [disabled ? 'cursor-not-allowed opacity-50' : '']
            })
          }
        >
          <Slider.Range
            className={
              makeClassName({
                defaultClassName: rangeDefaultClassName,
                className: rangeClass
              })
            }
            onPointerDown={handlePointerDown}
          />
        </Slider.Track>
        <Slider.Thumb
          className={makeClassName({
            defaultClassName: thumbDefaultClassName,
            className: thumbClass,
            extras: [startThumbClass, disabled ? 'cursor-not-allowed opacity-50' : undefined]
          })}
          aria-label="Start"
        />
        <Slider.Thumb
          className={makeClassName({
            defaultClassName: thumbDefaultClassName,
            className: thumbClass,
            extras: [endThumbClass, disabled ? 'cursor-not-allowed opacity-50' : undefined]
          })}
          aria-label="End"
        />

      </Slider.Root>
      {after !== undefined ? after : ''}
    </div>
  )
}

export default SliderRange
