import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';

interface IDPadContainerProps {
  opacity?: number;
  showBackground?: boolean;
  size?: number;
  disabled?: boolean;
}

interface IDPadButtonProps {
  size?: number;
  isPressed?: boolean;
  disabled?: boolean;
}

interface IDPadOptions {
  /** Opacity of the entire component (0-1) */
  opacity?: number;
  /** Show the silver background behind the controller (default: false) */
  showBackground?: boolean;
  /** Size in pixels (default: 100) */
  size?: number;
  /** Interval in ms for continuous press events (default: 500) */
  pressInterval?: number;
}

interface IDPadProps {
  /** Callback fired when a direction is pressed */
  onDirectionPress?: (direction: 'up' | 'down' | 'left' | 'right') => void;
  /** Callback fired when a direction is released */
  onDirectionRelease?: (direction: 'up' | 'down' | 'left' | 'right') => void;
  /** Whether the component is disabled */
  disabled?: boolean;
  /** Additional options for customizing the D-pad */
  options?: IDPadOptions;
}

// Memoize the styled components since they don't depend on props that change frequently
const DPadButton = memo(styled.div<IDPadButtonProps>`
  position: absolute;
  background: ${props => (props.isPressed ? '#363636' : '#424242')};
  box-shadow: ${props =>
    props.isPressed
      ? 'inset 0 0 12px rgba(0, 0, 0, 0.8), 0 0 2px rgba(0, 0, 0, 0.3)'
      : 'inset 0 0 5px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.2)'};
  cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
  user-select: none;
  transition: all 0.08s cubic-bezier(0.4, 0, 0.2, 1);
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  transform-origin: center center;

  &:hover:not(:active) {
    @media (hover: hover) {
      filter: ${props =>
        !props.disabled && !props.isPressed ? 'brightness(1.1)' : 'none'};
    }
  }

  &::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    border: ${props => (props.size ?? 100) * 0.05}px solid transparent;
    pointer-events: none;
    opacity: ${props => (props.isPressed ? 0.7 : 1)};
    transition: all 0.08s cubic-bezier(0.4, 0, 0.2, 1);
  }

  &.up,
  &.down {
    width: ${props => (props.size ?? 100) * 0.3}px;
    height: ${props => (props.size ?? 100) * 0.4}px;
    left: 50%;
    transform: translateX(-50%) scale(${props => (props.isPressed ? 0.95 : 1)});
  }

  &.left,
  &.right {
    width: ${props => (props.size ?? 100) * 0.4}px;
    height: ${props => (props.size ?? 100) * 0.3}px;
    top: 50%;
    transform: translateY(-50%) scale(${props => (props.isPressed ? 0.95 : 1)});
  }

  &.up {
    top: 0;
    border-radius: 5px 5px 0 0;
    &::after {
      border-bottom-color: #2a2a2a;
      top: 45%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
  }

  &.down {
    bottom: 0;
    border-radius: 0 0 5px 5px;
    &::after {
      border-top-color: #2a2a2a;
      bottom: 45%;
      left: 50%;
      transform: translate(-50%, 50%);
    }
  }

  &.left {
    left: 0;
    border-radius: 5px 0 0 5px;
    &::after {
      border-right-color: #2a2a2a;
      left: 45%;
      top: 50%;
      transform: translate(-50%, -50%);
    }
  }

  &.right {
    right: 0;
    border-radius: 0 5px 5px 0;
    &::after {
      border-left-color: #2a2a2a;
      right: 45%;
      top: 50%;
      transform: translate(50%, -50%);
    }
  }
`);

const DPadCenter = memo(styled.div<IDPadButtonProps>`
  position: absolute;
  width: ${props => (props.size ?? 100) * 0.3}px;
  height: ${props => (props.size ?? 100) * 0.3}px;
  background: #424242;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.6);
  border-radius: 50%;
  user-select: none;
  cursor: ${props => (props.disabled ? 'not-allowed' : 'default')};
  touch-action: none;
  -webkit-tap-highlight-color: transparent;

  &::after {
    content: '';
    position: absolute;
    width: ${props => (props.size ?? 100) * 0.08}px;
    height: ${props => (props.size ?? 100) * 0.08}px;
    background: #2a2a2a;
    border-radius: 50%;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    pointer-events: none;
    box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.8);
  }
`);

const DPadContainer = memo(styled.div<IDPadContainerProps>`
  width: ${props => props.size ?? 100}px;
  height: ${props => props.size ?? 100}px;
  position: relative;
  background: ${props => (props.showBackground ? '#b8b8b8' : 'transparent')};
  border-radius: 50%;
  box-shadow: ${props =>
    props.showBackground
      ? 'inset 0 0 10px rgba(0, 0, 0, 0.3), 0 4px 8px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.2)'
      : 'none'};
  opacity: ${props => (props.disabled ? 0.5 : props.opacity ?? 1)};
  user-select: none;
  cursor: ${props => (props.disabled ? 'not-allowed' : 'default')};
  transition: opacity 0.2s ease;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
`);

export const JoystickDPad = memo(
  ({
    onDirectionPress,
    onDirectionRelease,
    disabled = false,
    options = {},
  }: IDPadProps): JSX.Element => {
    const {
      opacity = 1,
      showBackground = false,
      size = 100,
      pressInterval = 500,
    } = options;

    // Use refs for values that don't need to trigger re-renders
    const [pressedButtons, setPressedButtons] = useState<Set<string>>(
      new Set()
    );
    const intervalRef = useRef<number | null>(null);
    const activeDirectionRef = useRef<'up' | 'down' | 'left' | 'right' | null>(
      null
    );
    const touchStartRef = useRef<{ x: number; y: number } | null>(null);
    const isPressedRef = useRef<boolean>(false);

    const clearPressInterval = useCallback(() => {
      if (intervalRef.current !== null) {
        window.clearInterval(intervalRef.current);
        intervalRef.current = null;
      }
      activeDirectionRef.current = null;
    }, []);

    const clearAllPresses = useCallback(() => {
      if (activeDirectionRef.current && onDirectionRelease) {
        onDirectionRelease(activeDirectionRef.current);
      }
      clearPressInterval();
      setPressedButtons(new Set());
      activeDirectionRef.current = null;
      isPressedRef.current = false;
    }, [clearPressInterval, onDirectionRelease]);

    const handleDirectionPress = useCallback(
      (direction: 'up' | 'down' | 'left' | 'right') => {
        if (disabled) return;

        // Clear any existing presses first
        clearAllPresses();

        // Set new direction
        activeDirectionRef.current = direction;
        isPressedRef.current = true;
        setPressedButtons(new Set([direction]));
        onDirectionPress?.(direction);

        intervalRef.current = window.setInterval(() => {
          if (activeDirectionRef.current === direction) {
            onDirectionPress?.(direction);
          } else {
            clearPressInterval();
          }
        }, pressInterval);
      },
      [
        disabled,
        onDirectionPress,
        pressInterval,
        clearPressInterval,
        clearAllPresses,
      ]
    );

    const handleDirectionRelease = useCallback(
      (direction: 'up' | 'down' | 'left' | 'right') => {
        if (activeDirectionRef.current === direction) {
          clearAllPresses();
        }
      },
      [clearAllPresses]
    );

    const handleTouchStart = useCallback(
      (e: React.TouchEvent, direction: 'up' | 'down' | 'left' | 'right') => {
        const touch = e.touches[0];
        if (touch) {
          touchStartRef.current = { x: touch.clientX, y: touch.clientY };
          handleDirectionPress(direction);
        }
      },
      [handleDirectionPress]
    );

    const handleTouchMove = useCallback(
      (e: React.TouchEvent) => {
        const touch = e.touches[0];
        if (!touch || !touchStartRef.current) return;

        const { x: startX, y: startY } = touchStartRef.current;
        const deltaX = touch.clientX - startX;
        const deltaY = touch.clientY - startY;

        // Calculate angle and distance
        const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
        const angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);

        // Only trigger if we've moved enough
        const threshold = size * 0.15; // Adaptive threshold based on d-pad size
        if (distance < threshold) return;

        let newDirection: 'up' | 'down' | 'left' | 'right' | null = null;

        // Determine direction based on angle
        if (angle > -45 && angle <= 45) newDirection = 'right';
        else if (angle > 45 && angle <= 135) newDirection = 'down';
        else if (angle > 135 || angle <= -135) newDirection = 'left';
        else if (angle > -135 && angle <= -45) newDirection = 'up';

        if (newDirection && newDirection !== activeDirectionRef.current) {
          handleDirectionPress(newDirection);
          // Update touch start to current position to prevent jitter
          touchStartRef.current = { x: touch.clientX, y: touch.clientY };
        }
      },
      [handleDirectionPress, size]
    );

    // Add a new cleanup function for touch events
    const cleanupTouchEvents = useCallback(() => {
      touchStartRef.current = null;
      if (activeDirectionRef.current) {
        handleDirectionRelease(activeDirectionRef.current);
      }
    }, [handleDirectionRelease]);

    // Enhance the touch end handler
    const handleTouchEnd = useCallback(() => {
      cleanupTouchEvents();
    }, [cleanupTouchEvents]);

    // Add touch cancel handler
    const handleTouchCancel = useCallback(() => {
      cleanupTouchEvents();
    }, [cleanupTouchEvents]);

    // Enhance cleanup effect
    useEffect(() => {
      if (disabled) {
        clearAllPresses();
      }

      const handleBlur = () => {
        clearAllPresses();
      };

      const handleVisibilityChange = () => {
        if (document.hidden) {
          clearAllPresses();
        }
      };

      const handlePointerUp = () => {
        // Global pointer up as fallback for stuck buttons
        if (isPressedRef.current) {
          clearAllPresses();
        }
      };

      window.addEventListener('blur', handleBlur);
      window.addEventListener('pointerup', handlePointerUp);
      document.addEventListener('visibilitychange', handleVisibilityChange);

      return () => {
        clearAllPresses();
        window.removeEventListener('blur', handleBlur);
        window.removeEventListener('pointerup', handlePointerUp);
        document.removeEventListener(
          'visibilitychange',
          handleVisibilityChange
        );
      };
    }, [disabled, clearAllPresses]);

    // Memoize the preventDefault handler
    const preventDefault = useCallback(
      (e: React.MouseEvent | React.TouchEvent) => {
        e.preventDefault();
        e.stopPropagation();
      },
      []
    );

    // Memoize button props to prevent unnecessary re-renders
    const buttonProps = useCallback(
      (direction: 'up' | 'down' | 'left' | 'right') => ({
        onMouseDown: () => handleDirectionPress(direction),
        onMouseUp: () => handleDirectionRelease(direction),
        onMouseLeave: () => handleDirectionRelease(direction),
        onTouchStart: (e: React.TouchEvent) => handleTouchStart(e, direction),
        onTouchMove: handleTouchMove,
        onTouchEnd: handleTouchEnd,
        onContextMenu: preventDefault,
        size,
        isPressed: pressedButtons.has(direction),
        disabled,
      }),
      [
        handleDirectionPress,
        handleDirectionRelease,
        handleTouchStart,
        handleTouchMove,
        handleTouchEnd,
        preventDefault,
        size,
        pressedButtons,
        disabled,
      ]
    );

    return (
      <DPadContainer
        opacity={opacity}
        showBackground={showBackground}
        size={size}
        disabled={disabled}
        onContextMenu={preventDefault}
        onTouchCancel={handleTouchCancel}
      >
        <DPadButton className="up" {...buttonProps('up')} />
        <DPadButton className="right" {...buttonProps('right')} />
        <DPadButton className="down" {...buttonProps('down')} />
        <DPadButton className="left" {...buttonProps('left')} />
        <DPadCenter
          size={size}
          disabled={disabled}
          onContextMenu={preventDefault}
        />
      </DPadContainer>
    );
  }
);

JoystickDPad.displayName = 'JoystickDPad';
