import { useEffect, useRef, useState } from 'react'
import type { PoseSettings } from '../types'
import type { GestureCallbacks, StepConfig, Landmark2D, Landmark3D, CalibrationStatus, GestureSignal, StepGraphSample } from '../gesture/types'
import { PoseDetector } from './PoseDetector'
import { VideoSource } from './VideoSource'
import { GestureEngine } from '../gesture/GestureEngine'
import { GestureTelemetry } from '../gesture/GestureTelemetry'
import { createARScene, updateARScene, disposeARScene, type ARState } from '../arScene'
import { DrawingUtils, PoseLandmarker } from '@mediapipe/tasks-vision'

export type PoseLoopCallbacks = GestureCallbacks

export interface PoseLoopState {
  loading: boolean
  error: string | null
  fps: number
  gesture: GestureSignal
  calibrationStatus: CalibrationStatus
  framingHint: string | null
  stepGraphSamples: StepGraphSample[]
  landmarks2D: Landmark2D[]
  worldLandmarks: Landmark3D[]
  engine: GestureEngine | null
  telemetry: GestureTelemetry | null
  videoSource: VideoSource | null
}

export type DrawMode = 'full' | 'toes-only' | 'nose'

export type FrameCallback = (
  landmarks: Landmark2D[],
  worldLandmarks: Landmark3D[],
  timestamp: number,
) => void

export function usePoseLoop(
  videoRef: React.RefObject<HTMLVideoElement | null>,
  canvasRef: React.RefObject<HTMLCanvasElement | null>,
  arCanvasRef: React.RefObject<HTMLCanvasElement | null>,
  settings: PoseSettings,
  fileUrl: string | null,
  callbacks: PoseLoopCallbacks,
  drawMode: DrawMode = 'full',
  onFrame?: FrameCallback,
): PoseLoopState {
  const drawModeRef = useRef(drawMode)
  drawModeRef.current = drawMode

  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [fps, setFps] = useState(0)
  const [gesture, setGesture] = useState<GestureSignal>(null)
  const [calibrationStatus, setCalibrationStatus] = useState<CalibrationStatus>('framing')
  const [framingHint, setFramingHint] = useState<string | null>(null)
  const [stepGraphSamples, setStepGraphSamples] = useState<StepGraphSample[]>([])
  const [landmarks2DState, setLandmarks2DState] = useState<Landmark2D[]>([])
  const [worldLandmarksState, setWorldLandmarksState] = useState<Landmark3D[]>([])

  const engineRef = useRef<GestureEngine | null>(null)
  const telemetryRef = useRef<GestureTelemetry | null>(null)
  const videoSourceRef = useRef<VideoSource | null>(null)
  const callbacksRef = useRef(callbacks)
  callbacksRef.current = callbacks

  const onFrameRef = useRef(onFrame)
  onFrameRef.current = onFrame

  const settingsRef = useRef(settings)
  settingsRef.current = settings

  useEffect(() => {
    let stopped = false
    let animationFrameId: number
    let arState: ARState | null = null
    let detector: PoseDetector | null = null
    let videoSource: VideoSource | null = null

    async function init() {
      try {
        setLoading(true)
        setError(null)

        const videoMode = fileUrl ? 'file' : 'webcam'
        if (videoMode === 'file' && !fileUrl) {
          setLoading(false)
          setError('No video file provided')
          return
        }

        const video = videoRef.current!

        // Init PoseDetector + VideoSource in parallel
        const [det, vs] = await Promise.all([
          PoseDetector.create(settings),
          VideoSource.create(video, videoMode, fileUrl ?? undefined),
        ])

        if (stopped) {
          det.dispose()
          vs.dispose()
          return
        }

        detector = det
        videoSource = vs
        videoSourceRef.current = vs

        setLoading(false)

        // Init AR overlay
        const arCanvas = arCanvasRef.current!
        arState = createARScene(arCanvas, video.videoWidth, video.videoHeight, 63, 0.25)

        // Init gesture engine + telemetry
        const s = settingsRef.current
        const stepCfg: StepConfig = { thresholdX: s.stepThresholdX, thresholdZ: s.stepThresholdZ, reset: s.stepReset }
        const engine = new GestureEngine(stepCfg, callbacksRef.current)
        const telemetry = new GestureTelemetry()
        engineRef.current = engine
        telemetryRef.current = telemetry

        // Canvas setup
        const canvas = canvasRef.current!
        const ctx = canvas.getContext('2d')!
        const drawingUtils = new DrawingUtils(ctx)

        let lastTimestamp = -1
        let frameCount = 0
        let lastFpsTime = performance.now()
        let gestureDisplayUntil = 0
        let displayedGesture: GestureSignal = null
        const SIGNAL_DISPLAY_MS = 600

        function detect() {
          if (stopped) return

          const video = videoRef.current!
          const canvas = canvasRef.current!

          if (video.currentTime !== lastTimestamp && video.videoWidth > 0 && video.videoHeight > 0) {
            canvas.width = video.videoWidth
            canvas.height = video.videoHeight
            lastTimestamp = video.currentTime

            const result = detector!.detectFrame(video, performance.now())

            // FPS tracking
            frameCount++
            const now = performance.now()
            if (now - lastFpsTime >= 1000) {
              setFps(Math.round(frameCount * 1000 / (now - lastFpsTime)))
              frameCount = 0
              lastFpsTime = now
            }

            // Update engine config from latest settings (cheap, just object assignment)
            const s = settingsRef.current
            engine.setConfig({ thresholdX: s.stepThresholdX, thresholdZ: s.stepThresholdZ, reset: s.stepReset })
            engine.setCallbacks(callbacksRef.current)

            // Clear canvas
            ctx.clearRect(0, 0, canvas.width, canvas.height)

            // (framing line drawn via HTML element in DebugApp)

            // Draw pose landmarks
            for (const landmarks of result.landmarks) {
              if (drawModeRef.current === 'toes-only') {
                const toes = [31, 32].map(i => landmarks[i]).filter(Boolean)
                drawingUtils.drawLandmarks(toes, {
                  radius: 10,
                  color: '#00FFCC',
                  fillColor: '#00FFCC',
                })
              } else if (drawModeRef.current === 'full') {
                drawingUtils.drawLandmarks(landmarks, {
                  radius: 4,
                  color: '#00FF00',
                  fillColor: '#00FF0080',
                })
                drawingUtils.drawConnectors(landmarks, PoseLandmarker.POSE_CONNECTIONS, {
                  color: '#00FFFF',
                  lineWidth: 2,
                })
              } else if (drawModeRef.current === 'nose') {
                // Show tracking dot at centroid of visible body landmarks
                // Use nose (0) for head instead of all 11 head landmarks to avoid bias
                const bodyIndices = [0, 11, 12, 13, 14, 15, 16, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]
                let sx = 0, sy = 0, n = 0
                for (const i of bodyIndices) {
                  const lm = landmarks[i]
                  if (lm && (lm.visibility === undefined || lm.visibility >= 0.5)) {
                    sx += lm.x; sy += lm.y; n++
                  }
                }
                if (n > 0) {
                  drawingUtils.drawLandmarks([{ x: sx / n, y: sy / n }], {
                    radius: 12,
                    color: '#FF44FF',
                    fillColor: '#FF44FF',
                  })
                }
              }
            }

            // Gesture detection (directly in the loop — no React in hot path)
            const landmarks2D = (result.landmarks[0] ?? []) as Landmark2D[]
            const worldLandmarks = (result.worldLandmarks[0] ?? []) as Landmark3D[]

            // Frame callback for recording
            if (onFrameRef.current && landmarks2D.length > 0) {
              onFrameRef.current(landmarks2D, worldLandmarks, performance.now())
            }

            const signal = engine.update(landmarks2D, worldLandmarks)

            // Telemetry sampling
            telemetry.sample(engine, worldLandmarks)

            // Display timer: hold gesture signal for 600ms
            if (signal) {
              displayedGesture = signal
              gestureDisplayUntil = now + SIGNAL_DISPLAY_MS
            } else if (now >= gestureDisplayUntil) {
              displayedGesture = null
            }

            // Batch React state updates
            setGesture(displayedGesture)
            setCalibrationStatus(engine.calibrationStatus)
            setFramingHint(engine.framingHint)
            if (frameCount % 3 === 0) {
              setStepGraphSamples([...telemetry.stepGraph])
              if (landmarks2D.length >= 33) setLandmarks2DState([...landmarks2D])
              if (worldLandmarks.length >= 33) setWorldLandmarksState([...worldLandmarks])
            }

            // Render AR
            if (arState) updateARScene(arState)
          }

          animationFrameId = requestAnimationFrame(detect)
        }

        detect()
      } catch (e) {
        if (!stopped) {
          setError(e instanceof Error ? e.message : 'Failed to initialize')
          setLoading(false)
        }
      }
    }

    init()

    return () => {
      stopped = true
      cancelAnimationFrame(animationFrameId)
      if (arState) disposeARScene(arState)
      if (detector) detector.dispose()
      if (videoSource) videoSource.dispose()
      engineRef.current = null
      telemetryRef.current = null
      videoSourceRef.current = null
    }
  }, [settings, fileUrl]) // eslint-disable-line react-hooks/exhaustive-deps

  return {
    loading, error, fps, gesture, calibrationStatus, framingHint, stepGraphSamples, landmarks2D: landmarks2DState, worldLandmarks: worldLandmarksState,
    engine: engineRef.current,
    telemetry: telemetryRef.current,
    videoSource: videoSourceRef.current,
  }
}

