import styled from '@emotion/styled';
import { useAnimationFrame } from 'framer-motion';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';

import useClickDrag from '@/hooks/useClickDrag';
import snap from '@/utils/snap';

const Wrapper = styled.div`
  position: relative;
  width: 100%;
  cursor: ew-resize;
  height: 20px;
  &:hover .threshold-label {
    opacity: 1;
  }
`;

const Tick = styled.div`
  position: absolute;
  width: 2px;
  margin-left: -1px;
  height: 100%;
  background-color: var(--color-foreground-tertiary);
  border-radius: 12px;
`;

const Knob = styled.div`
  position: absolute;
  width: 10px;
  top: -4px;
  bottom: -4px;
  background-color: var(--color-accent-brand);
  border-radius: 1000px;
  margin-left: -5px;
`;

const ThresholdLabel = styled.div<{ dragging: boolean }>`
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  font-size: 14px;
  font-weight: 500;
  color: var(--color-foreground-primary);
  background-color: var(--color-background-fog-thin);
  backdrop-filter: blur(50px);
  padding: 8px 12px;
  border-radius: 8px;
  white-space: nowrap;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  margin-bottom: 8px;
  pointer-events: none;
  opacity: ${({ dragging }) => (dragging ? 1 : 0)};
  transition: opacity 0.2s ease-in-out;
`;

export default memo(function CreateSlider({
  value,
  onChange,
  onCommit,
  thresholds,
  defaultValue,
  disabled = false,
  showUpsellIfRestricted,
  ariaLabel = 'Slider',
}: {
  value: number;
  onChange: (value: number) => void;
  onCommit: (value: number) => void;
  thresholds?: Record<number, string>;
  defaultValue: number;
  disabled?: boolean;
  showUpsellIfRestricted?: () => boolean;
  ariaLabel?: string;
}) {
  const [localValue, setLocalValue] = useState(value);
  const [isDragging, setIsDragging] = useState(false);
  const [tickCount, setTickCount] = useState(10);
  const wrapperRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    setLocalValue(value);
  }, [value]);

  const setTickCountForWidth = useCallback((width: number) => {
    const maxTickDistance = 30; // Maximum distance between ticks in pixels

    // Start with 10 ticks and double until we have enough ticks
    let calculatedTickCount = 10;
    while (width / calculatedTickCount > maxTickDistance) {
      calculatedTickCount *= 2;
    }

    setTickCount(calculatedTickCount);
  }, []);

  const resizeObserverRef = useRef<ResizeObserver | null>(null);

  const clickDragRef = useClickDrag(
    useCallback(
      ({ event }) => {
        const target = event.currentTarget as HTMLDivElement;
        let currentValue = value;
        setIsDragging(true);
        return {
          onMouseMove: ({ deltaXFromStart }) => {
            if (showUpsellIfRestricted?.() || disabled) return;
            const targetRect = target.getBoundingClientRect();
            currentValue = snap(
              Math.min(
                1,
                Math.max(0, value + deltaXFromStart / targetRect.width)
              ),
              0.01
            );
            onChange(currentValue);
            setLocalValue(currentValue);
          },
          onMouseUp: () => {
            onCommit(currentValue);
            setIsDragging(false);
          },
        };
      },
      [value, onChange, onCommit, disabled, showUpsellIfRestricted]
    )
  );

  const sortedThresholdEntries = useMemo(() => {
    if (!thresholds) return [];
    return Object.entries(thresholds).sort(
      (a, b) => Number(a[0]) - Number(b[0])
    );
  }, [thresholds]);

  const currentThresholdLabel = (() => {
    if (!thresholds) return null;
    const currentThreshold = sortedThresholdEntries.findLast(
      ([key]) => Number(key) <= localValue
    );
    return currentThreshold ? currentThreshold[1] : null;
  })();

  const ticks = useMemo(() => {
    const ticks = [];
    for (let i = 0; i <= tickCount; i++) {
      ticks.push(i / tickCount);
    }
    return ticks;
  }, [tickCount]);

  const receiveWrapperRef = useCallback(
    (node: HTMLDivElement) => {
      wrapperRef.current = node;
      clickDragRef(node);
      if (resizeObserverRef.current) {
        resizeObserverRef.current.disconnect();
      }
      if (node) {
        resizeObserverRef.current = new ResizeObserver((entries) => {
          for (const entry of entries) {
            setTickCountForWidth(entry.contentRect.width);
          }
        });
        resizeObserverRef.current.observe(node);
        setTickCountForWidth(node.getBoundingClientRect().width);
      }
    },
    [clickDragRef, disabled]
  );

  const lastValueRef = useRef(NaN);
  const lastTickCountRef = useRef(0);

  useAnimationFrame(() => {
    if (!wrapperRef.current) return;
    if (
      lastValueRef.current === localValue &&
      lastTickCountRef.current === tickCount
    )
      return;
    lastTickCountRef.current = tickCount;
    lastValueRef.current = localValue;
    const ticks = Array.from(
      wrapperRef.current.querySelectorAll('div[data-tick-value]')
    ) as HTMLDivElement[];
    ticks.forEach((tick) => {
      const tickValue = Number(tick.getAttribute('data-tick-value'));
      const delta = Math.abs(tickValue - localValue);
      tick.style.opacity = `${Math.max(0.1, Math.min(1, Math.pow(1 - delta, 6)))}`;
    });
  });

  return (
    <Wrapper
      ref={receiveWrapperRef}
      role='slider'
      aria-label={ariaLabel}
      aria-valuenow={Math.round(localValue * 100)}
      aria-valuemin={0}
      aria-valuemax={100}
      aria-disabled={disabled}
      tabIndex={disabled ? -1 : 0}
      onMouseDown={(e) => {
        if (disabled || showUpsellIfRestricted?.()) {
          e.preventDefault();
          e.stopPropagation();
        }
      }}
      onDoubleClick={(e) => {
        if (disabled) {
          e.preventDefault();
          return;
        }
        setLocalValue(defaultValue);
        onChange(defaultValue);
        onCommit(defaultValue);
      }}
      onKeyDown={(e) => {
        if (['ArrowLeft', 'ArrowRight'].includes(e.key)) {
          const isLeft = e.key === 'ArrowLeft';
          e.preventDefault();
          const step = 0.01;
          const newValue = Math.min(
            1,
            Math.max(0, localValue + step * (isLeft ? -1 : 1))
          );
          setLocalValue(newValue);
          onChange(newValue);
          onCommit(newValue);
        }
      }}
    >
      {ticks.map((tick) => (
        <Tick
          key={tick}
          data-tick-value={tick}
          style={{
            left: `${tick * 100}%`,
          }}
        />
      ))}
      <Knob style={{ left: `${localValue * 100}%` }}>
        {currentThresholdLabel && (
          <ThresholdLabel dragging={isDragging} className='threshold-label'>
            {currentThresholdLabel}
          </ThresholdLabel>
        )}
      </Knob>
    </Wrapper>
  );
});
