/**
 * Touch and gesture utilities for web components
 * @countermeasure/web-components/utils/touch
 *
 * Provides swipe detection, touch-or-click handling,
 * and pinch-to-zoom support for mobile accessibility.
 */

// ============================================================================
// Types
// ============================================================================

export type SwipeDirection = 'left' | 'right' | 'up' | 'down'

export interface SwipeOptions {
  /** Minimum distance in pixels to trigger a swipe (default: 50) */
  threshold?: number
  /** Maximum time in ms for a swipe gesture (default: 300) */
  timeout?: number
  /** Callback when swipe is detected */
  onSwipe?: (direction: SwipeDirection, event: TouchEvent) => void
  /** Callback when swipe starts */
  onSwipeStart?: (event: TouchEvent) => void
  /** Callback when swipe ends without triggering */
  onSwipeEnd?: (event: TouchEvent) => void
  /** Whether to prevent default behavior during swipe */
  preventDefault?: boolean
}

export interface SwipeDetector {
  /** Enable swipe detection */
  enable: () => void
  /** Disable swipe detection */
  disable: () => void
  /** Check if detection is enabled */
  isEnabled: () => boolean
  /** Clean up event listeners */
  destroy: () => void
}

export interface PinchOptions {
  /** Minimum scale change to trigger callback (default: 0.1) */
  threshold?: number
  /** Callback when pinch is detected */
  onPinch?: (scale: number, event: TouchEvent) => void
  /** Callback when pinch starts */
  onPinchStart?: (event: TouchEvent) => void
  /** Callback when pinch ends */
  onPinchEnd?: (event: TouchEvent) => void
}

export interface PinchDetector {
  /** Enable pinch detection */
  enable: () => void
  /** Disable pinch detection */
  disable: () => void
  /** Get the current pinch scale */
  getScale: () => number
  /** Reset scale to 1 */
  resetScale: () => void
  /** Clean up event listeners */
  destroy: () => void
}

export interface PanOptions {
  /** Callback when pan occurs */
  onPan?: (deltaX: number, deltaY: number, event: TouchEvent) => void
  /** Callback when pan starts */
  onPanStart?: (event: TouchEvent) => void
  /** Callback when pan ends */
  onPanEnd?: (event: TouchEvent) => void
  /** Whether to prevent default behavior during pan */
  preventDefault?: boolean
}

export interface PanDetector {
  /** Enable pan detection */
  enable: () => void
  /** Disable pan detection */
  disable: () => void
  /** Clean up event listeners */
  destroy: () => void
}

// ============================================================================
// Swipe Detection
// ============================================================================

/**
 * Create a swipe gesture detector for an element.
 * Detects horizontal and vertical swipes with configurable threshold.
 */
export function detectSwipe(element: HTMLElement, options: SwipeOptions = {}): SwipeDetector {
  const {
    threshold = 50,
    timeout = 300,
    onSwipe,
    onSwipeStart,
    onSwipeEnd,
    preventDefault = false,
  } = options

  let enabled = true
  let startX = 0
  let startY = 0
  let startTime = 0

  const handleTouchStart = (event: TouchEvent): void => {
    if (!enabled) {
      return
    }

    const touch = event.touches[0]
    if (touch === undefined) {
      return
    }

    startX = touch.clientX
    startY = touch.clientY
    startTime = Date.now()

    onSwipeStart?.(event)
  }

  const handleTouchEnd = (event: TouchEvent): void => {
    if (!enabled || startTime === 0) {
      return
    }

    const touch = event.changedTouches[0]
    if (touch === undefined) {
      return
    }

    const endX = touch.clientX
    const endY = touch.clientY
    const endTime = Date.now()

    const deltaX = endX - startX
    const deltaY = endY - startY
    const elapsed = endTime - startTime

    // Reset start time
    startTime = 0

    // Check if gesture was within timeout
    if (elapsed > timeout) {
      onSwipeEnd?.(event)
      return
    }

    const absX = Math.abs(deltaX)
    const absY = Math.abs(deltaY)

    // Determine swipe direction
    if (absX >= threshold || absY >= threshold) {
      if (preventDefault) {
        event.preventDefault()
      }

      let direction: SwipeDirection
      if (absX > absY) {
        direction = deltaX > 0 ? 'right' : 'left'
      } else {
        direction = deltaY > 0 ? 'down' : 'up'
      }

      onSwipe?.(direction, event)
    } else {
      onSwipeEnd?.(event)
    }
  }

  const handleTouchMove = (event: TouchEvent): void => {
    if (!enabled || !preventDefault) {
      return
    }

    const touch = event.touches[0]
    if (touch === undefined) {
      return
    }

    const deltaX = Math.abs(touch.clientX - startX)
    const deltaY = Math.abs(touch.clientY - startY)

    // Prevent scrolling if swipe seems intentional
    if (deltaX > 10 || deltaY > 10) {
      event.preventDefault()
    }
  }

  element.addEventListener('touchstart', handleTouchStart, { passive: true })
  element.addEventListener('touchend', handleTouchEnd, { passive: !preventDefault })
  if (preventDefault) {
    element.addEventListener('touchmove', handleTouchMove, { passive: false })
  }

  return {
    enable: () => {
      enabled = true
    },
    disable: () => {
      enabled = false
    },
    isEnabled: () => enabled,
    destroy: () => {
      element.removeEventListener('touchstart', handleTouchStart)
      element.removeEventListener('touchend', handleTouchEnd)
      element.removeEventListener('touchmove', handleTouchMove)
    },
  }
}

// ============================================================================
// Touch or Click Handler
// ============================================================================

/**
 * Handle both touch and click events without double-firing.
 * Uses touch events on touch devices, click events otherwise.
 */
export function handleTouchOrClick(
  element: HTMLElement,
  callback: (event: TouchEvent | MouseEvent) => void
): () => void {
  let touchHandled = false

  const handleTouchStart = (event: TouchEvent): void => {
    touchHandled = true
    callback(event)
  }

  const handleClick = (event: MouseEvent): void => {
    // Ignore if touch already handled (prevents double-firing on touch devices)
    if (touchHandled) {
      touchHandled = false
      return
    }
    callback(event)
  }

  element.addEventListener('touchstart', handleTouchStart, { passive: true })
  element.addEventListener('click', handleClick)

  return () => {
    element.removeEventListener('touchstart', handleTouchStart)
    element.removeEventListener('click', handleClick)
  }
}

/**
 * Add a touchstart handler that also works with mouse for accessibility.
 * Returns a cleanup function.
 */
export function onPointerDown(
  element: HTMLElement,
  callback: (event: PointerEvent | TouchEvent | MouseEvent) => void
): () => void {
  // Prefer PointerEvent if supported
  if ('PointerEvent' in window) {
    const handler = (event: PointerEvent): void => {
      callback(event)
    }
    element.addEventListener('pointerdown', handler)
    return () => {
      element.removeEventListener('pointerdown', handler)
    }
  }

  // Fallback for older browsers
  let touchHandled = false

  const handleTouchStart = (event: TouchEvent): void => {
    touchHandled = true
    callback(event)
  }

  const handleMouseDown = (event: MouseEvent): void => {
    if (touchHandled) {
      touchHandled = false
      return
    }
    callback(event)
  }

  element.addEventListener('touchstart', handleTouchStart, { passive: true })
  element.addEventListener('mousedown', handleMouseDown)

  return () => {
    element.removeEventListener('touchstart', handleTouchStart)
    element.removeEventListener('mousedown', handleMouseDown)
  }
}

// ============================================================================
// Pinch-to-Zoom Detection
// ============================================================================

/**
 * Get the distance between two touch points.
 */
function getTouchDistance(touches: TouchList): number {
  if (touches.length < 2) {
    return 0
  }
  const touch1 = touches[0]
  const touch2 = touches[1]
  if (touch1 === undefined || touch2 === undefined) {
    return 0
  }
  const dx = touch1.clientX - touch2.clientX
  const dy = touch1.clientY - touch2.clientY
  return Math.sqrt(dx * dx + dy * dy)
}

/**
 * Create a pinch-to-zoom detector for an element.
 */
export function detectPinch(element: HTMLElement, options: PinchOptions = {}): PinchDetector {
  const { threshold = 0.1, onPinch, onPinchStart, onPinchEnd } = options

  let enabled = true
  let isPinching = false
  let initialDistance = 0
  let currentScale = 1

  const handleTouchStart = (event: TouchEvent): void => {
    if (!enabled || event.touches.length < 2) {
      return
    }

    isPinching = true
    initialDistance = getTouchDistance(event.touches)
    currentScale = 1

    onPinchStart?.(event)
  }

  const handleTouchMove = (event: TouchEvent): void => {
    if (!enabled || !isPinching || event.touches.length < 2) {
      return
    }

    const currentDistance = getTouchDistance(event.touches)
    if (initialDistance === 0) {
      return
    }

    const newScale = currentDistance / initialDistance
    const scaleDelta = Math.abs(newScale - currentScale)

    if (scaleDelta >= threshold) {
      currentScale = newScale
      onPinch?.(currentScale, event)
    }
  }

  const handleTouchEnd = (event: TouchEvent): void => {
    if (!enabled || !isPinching) {
      return
    }

    if (event.touches.length < 2) {
      isPinching = false
      onPinchEnd?.(event)
    }
  }

  element.addEventListener('touchstart', handleTouchStart, { passive: true })
  element.addEventListener('touchmove', handleTouchMove, { passive: true })
  element.addEventListener('touchend', handleTouchEnd, { passive: true })
  element.addEventListener('touchcancel', handleTouchEnd, { passive: true })

  return {
    enable: () => {
      enabled = true
    },
    disable: () => {
      enabled = false
      isPinching = false
    },
    getScale: () => currentScale,
    resetScale: () => {
      currentScale = 1
    },
    destroy: () => {
      element.removeEventListener('touchstart', handleTouchStart)
      element.removeEventListener('touchmove', handleTouchMove)
      element.removeEventListener('touchend', handleTouchEnd)
      element.removeEventListener('touchcancel', handleTouchEnd)
    },
  }
}

// ============================================================================
// Pan Detection
// ============================================================================

/**
 * Create a pan (single-finger drag) detector for an element.
 */
export function detectPan(element: HTMLElement, options: PanOptions = {}): PanDetector {
  const { onPan, onPanStart, onPanEnd, preventDefault = false } = options

  let enabled = true
  let isPanning = false
  let lastX = 0
  let lastY = 0

  const handleTouchStart = (event: TouchEvent): void => {
    if (!enabled || event.touches.length !== 1) {
      return
    }

    const touch = event.touches[0]
    if (touch === undefined) {
      return
    }

    isPanning = true
    lastX = touch.clientX
    lastY = touch.clientY

    onPanStart?.(event)
  }

  const handleTouchMove = (event: TouchEvent): void => {
    if (!enabled || !isPanning || event.touches.length !== 1) {
      return
    }

    const touch = event.touches[0]
    if (touch === undefined) {
      return
    }

    if (preventDefault) {
      event.preventDefault()
    }

    const deltaX = touch.clientX - lastX
    const deltaY = touch.clientY - lastY

    lastX = touch.clientX
    lastY = touch.clientY

    onPan?.(deltaX, deltaY, event)
  }

  const handleTouchEnd = (event: TouchEvent): void => {
    if (!enabled || !isPanning) {
      return
    }

    isPanning = false
    onPanEnd?.(event)
  }

  element.addEventListener('touchstart', handleTouchStart, { passive: true })
  element.addEventListener('touchmove', handleTouchMove, { passive: !preventDefault })
  element.addEventListener('touchend', handleTouchEnd, { passive: true })
  element.addEventListener('touchcancel', handleTouchEnd, { passive: true })

  return {
    enable: () => {
      enabled = true
    },
    disable: () => {
      enabled = false
      isPanning = false
    },
    destroy: () => {
      element.removeEventListener('touchstart', handleTouchStart)
      element.removeEventListener('touchmove', handleTouchMove)
      element.removeEventListener('touchend', handleTouchEnd)
      element.removeEventListener('touchcancel', handleTouchEnd)
    },
  }
}

// ============================================================================
// Long Press Detection
// ============================================================================

export interface LongPressOptions {
  /** Duration in ms to trigger long press (default: 500) */
  duration?: number
  /** Callback when long press is triggered */
  onLongPress?: (event: TouchEvent) => void
  /** Movement threshold in pixels to cancel long press (default: 10) */
  moveThreshold?: number
}

export interface LongPressDetector {
  /** Enable detection */
  enable: () => void
  /** Disable detection */
  disable: () => void
  /** Clean up */
  destroy: () => void
}

/**
 * Create a long press detector for context menu or similar actions.
 */
export function detectLongPress(
  element: HTMLElement,
  options: LongPressOptions = {}
): LongPressDetector {
  const { duration = 500, onLongPress, moveThreshold = 10 } = options

  let enabled = true
  let timeoutId: ReturnType<typeof setTimeout> | null = null
  let startX = 0
  let startY = 0

  const handleTouchStart = (event: TouchEvent): void => {
    if (!enabled) {
      return
    }

    const touch = event.touches[0]
    if (touch === undefined) {
      return
    }

    startX = touch.clientX
    startY = touch.clientY

    timeoutId = setTimeout(() => {
      onLongPress?.(event)
    }, duration)
  }

  const handleTouchMove = (event: TouchEvent): void => {
    if (timeoutId === null) {
      return
    }

    const touch = event.touches[0]
    if (touch === undefined) {
      return
    }

    const deltaX = Math.abs(touch.clientX - startX)
    const deltaY = Math.abs(touch.clientY - startY)

    if (deltaX > moveThreshold || deltaY > moveThreshold) {
      clearTimeout(timeoutId)
      timeoutId = null
    }
  }

  const handleTouchEnd = (): void => {
    if (timeoutId !== null) {
      clearTimeout(timeoutId)
      timeoutId = null
    }
  }

  element.addEventListener('touchstart', handleTouchStart, { passive: true })
  element.addEventListener('touchmove', handleTouchMove, { passive: true })
  element.addEventListener('touchend', handleTouchEnd, { passive: true })
  element.addEventListener('touchcancel', handleTouchEnd, { passive: true })

  return {
    enable: () => {
      enabled = true
    },
    disable: () => {
      enabled = false
      if (timeoutId !== null) {
        clearTimeout(timeoutId)
        timeoutId = null
      }
    },
    destroy: () => {
      if (timeoutId !== null) {
        clearTimeout(timeoutId)
      }
      element.removeEventListener('touchstart', handleTouchStart)
      element.removeEventListener('touchmove', handleTouchMove)
      element.removeEventListener('touchend', handleTouchEnd)
      element.removeEventListener('touchcancel', handleTouchEnd)
    },
  }
}

// ============================================================================
// Device Detection Helpers
// ============================================================================

/**
 * Check if the device supports touch events.
 */
export function isTouchDevice(): boolean {
  return 'ontouchstart' in window || navigator.maxTouchPoints > 0
}

/**
 * Check if the device is likely a mobile device based on screen size.
 */
export function isMobileViewport(): boolean {
  return window.innerWidth < 768
}

/**
 * Check if the device is in portrait orientation.
 */
export function isPortrait(): boolean {
  return window.innerHeight > window.innerWidth
}

/**
 * Get the safe area insets for notched devices.
 */
export function getSafeAreaInsets(): {
  top: number
  right: number
  bottom: number
  left: number
} {
  const style = getComputedStyle(document.documentElement)

  const parseValue = (prop: string): number => {
    const value = style.getPropertyValue(prop)
    const parsed = parseInt(value, 10)
    return isNaN(parsed) ? 0 : parsed
  }

  const getValue = (customProp: string, envProp: string): number => {
    const custom = parseValue(customProp)
    return custom !== 0 ? custom : parseValue(envProp)
  }

  return {
    top: getValue('--safe-area-inset-top', 'env(safe-area-inset-top)'),
    right: getValue('--safe-area-inset-right', 'env(safe-area-inset-right)'),
    bottom: getValue('--safe-area-inset-bottom', 'env(safe-area-inset-bottom)'),
    left: getValue('--safe-area-inset-left', 'env(safe-area-inset-left)'),
  }
}
