import { useRef, useState, useMemo, useCallback, useEffect } from 'react'
import { cx } from '@emotion/css'

const Slider = ({
  value,
  min,
  max,
  onChange,
  onlyCallOnChangeAfterDragEnded = false,
  orientation = 'horizontal',
  alwaysShowTrack = false,
  alwaysShowThumb = false,
}: {
  value: number
  min: number
  max: number
  onChange: (value: number) => void
  onlyCallOnChangeAfterDragEnded?: boolean
  orientation?: 'horizontal' | 'vertical'
  alwaysShowTrack?: boolean
  alwaysShowThumb?: boolean
}) => {
  const sliderRef = useRef<HTMLInputElement>(null)
  const [isDragging, setIsDragging] = useState(false)
  const [draggingValue, setDraggingValue] = useState(value)
  const memoedValue = useMemo(
    () =>
      isDragging && onlyCallOnChangeAfterDragEnded ? draggingValue : value,
    [isDragging, draggingValue, value, onlyCallOnChangeAfterDragEnded]
  )

  /**
   * Get the value of the slider based on the position of the pointer
   */
  const getNewValue = useCallback(
    (pointer: { x: number; y: number }) => {
      if (!sliderRef?.current) return 0
      const slider = sliderRef.current.getBoundingClientRect()
      const newValue =
        orientation === 'horizontal'
          ? ((pointer.x - slider.x) / slider.width) * max
          : ((slider.height - (pointer.y - slider.y)) / slider.height) * max
      if (newValue < min) return min
      if (newValue > max) return max
      return newValue
    },
    [sliderRef, max, min, orientation]
  )

  /**
   * Handle slider click event
   */
  const handleClick = useCallback(
    (e: React.MouseEvent<HTMLDivElement>) =>
      onChange(getNewValue({ x: e.clientX, y: e.clientY })),
    [getNewValue, onChange]
  )

  /**
   * Handle pointer down event
   */
  const handlePointerDown = () => {
    setIsDragging(true)
  }

  /**
   * Handle pointer move events
   */
  useEffect(() => {
    const handlePointerMove = (e: { clientX: number; clientY: number }) => {
      if (!isDragging) return
      const newValue = getNewValue({ x: e.clientX, y: e.clientY })
      onlyCallOnChangeAfterDragEnded
        ? setDraggingValue(newValue)
        : onChange(newValue)
    }
    document.addEventListener('pointermove', handlePointerMove)

    return () => {
      document.removeEventListener('pointermove', handlePointerMove)
    }
  }, [
    isDragging,
    onChange,
    setDraggingValue,
    onlyCallOnChangeAfterDragEnded,
    getNewValue,
  ])

  /**
   * Handle pointer up events
   */
  useEffect(() => {
    const handlePointerUp = () => {
      if (!isDragging) return
      setIsDragging(false)
      if (onlyCallOnChangeAfterDragEnded) {
        console.log('draggingValue', draggingValue)
        onChange(draggingValue)
      }
    }
    document.addEventListener('pointerup', handlePointerUp)

    return () => {
      document.removeEventListener('pointerup', handlePointerUp)
    }
  }, [
    isDragging,
    setIsDragging,
    onlyCallOnChangeAfterDragEnded,
    draggingValue,
    onChange,
  ])

  /**
   * Track and thumb styles
   */
  const usedTrackStyle = useMemo(() => {
    const percentage = `${(memoedValue / max) * 100}%`
    return orientation === 'horizontal'
      ? { width: percentage }
      : { height: percentage }
  }, [max, memoedValue, orientation])
  const thumbStyle = useMemo(() => {
    const percentage = `${(memoedValue / max) * 100}%`
    return orientation === 'horizontal'
      ? { left: percentage }
      : { bottom: percentage }
  }, [max, memoedValue, orientation])

  return (
    <div
      className={cx(
        'group relative flex items-center',
        orientation === 'horizontal' && 'h-2',
        orientation === 'vertical' && 'h-full w-2 flex-col'
      )}
      ref={sliderRef}
      onClick={handleClick}
    >
      {/* Track */}
      <div
        className={cx(
          'absolute bg-gray-500 bg-opacity-10',
          orientation === 'horizontal' && 'h-[2px] w-full',
          orientation === 'vertical' && 'h-full w-[2px]'
        )}
      ></div>

      {/* Passed track */}
      <div
        className={cx(
          'absolute  group-hover:bg-brand-500',
          isDragging || alwaysShowTrack
            ? 'bg-brand-500'
            : 'bg-gray-300 dark:bg-gray-500',
          orientation === 'horizontal' && 'h-[2px]',
          orientation === 'vertical' && 'bottom-0 w-[2px]'
        )}
        style={usedTrackStyle}
      ></div>

      {/* Thumb */}
      <div
        className={cx(
          'absolute flex h-5 w-5 items-center justify-center rounded-full bg-brand-500 bg-opacity-20 transition-opacity',
          isDragging || alwaysShowThumb
            ? 'opacity-100'
            : 'opacity-0 group-hover:opacity-100',
          orientation === 'horizontal' && '-translate-x-2.5',
          orientation === 'vertical' && 'translate-y-2.5'
        )}
        style={thumbStyle}
        onClick={e => e.stopPropagation()}
        onPointerDown={handlePointerDown}
      >
        <div className='absolute h-2 w-2 rounded-full bg-brand-500'></div>
      </div>
    </div>
  )
}

export default Slider
