import { log } from '../debugLog'
import type {
  GestureSignal, GestureCallbacks, StepConfig, StepDirection,
  CalibrationStatus, Landmark2D, Landmark3D,
} from './types'
import { CalibrationMachine } from './CalibrationMachine'
import { createStepState, detectStep, checkStepReset, adaptBaseline, type StepState } from './detectors/stepDetector'
import { createCrossedArmsState, detectCrossedArms, type CrossedArmsState } from './detectors/crossedArmsDetector'

const BASELINE_ADAPT_ALPHA = 0.01
const SIGNAL_COOLDOWN_MS = 600

export class GestureEngine {
  private calibration = new CalibrationMachine()
  private step: StepState = createStepState()
  private crossedArms: CrossedArmsState = createCrossedArmsState()

  private _stepConfig: StepConfig
  private _callbacks: GestureCallbacks

  // Live baselines (adapted from calibration values during gameplay)
  private baselineLeftY: number | null = null
  private baselineRightY: number | null = null

  // Detection cooldown: suppress all detection + baseline adaptation after a signal
  private cooldownUntil = 0


  constructor(config: StepConfig, callbacks: GestureCallbacks = {}) {
    this._stepConfig = config
    this._callbacks = callbacks
  }

  get calibrationStatus(): CalibrationStatus { return this.calibration.status }
  get framingHint(): string | null { return this.calibration.framingHint }
  get activeStep(): GestureSignal { return this.step.activeStep }
  get stepFoot(): 'left' | 'right' | null { return this.step.steppedFoot }
  get crossedArmsActive(): boolean { return this.crossedArms.active }
  get crossedArmsDistances(): { left: number; right: number; shoulderWidth: number } {
    return { left: this.crossedArms.lDist, right: this.crossedArms.rDist, shoulderWidth: this.crossedArms.shoulderWidth }
  }
  get crossedArmsHolding(): boolean { return this.crossedArms.holdingSince !== null }
  get heelBaseL() { return this.step.heelBaseL }
  get heelBaseR() { return this.step.heelBaseR }
  get currentBaselineLeftY() { return this.baselineLeftY }
  get currentBaselineRightY() { return this.baselineRightY }

  setConfig(config: StepConfig): void {
    this._stepConfig = config
  }

  setCallbacks(callbacks: GestureCallbacks): void {
    this._callbacks = callbacks
  }

  /**
   * Call each frame with latest pose data. Returns the gesture signal (if any).
   * Fires callbacks immediately when gestures are detected.
   */
  update(landmarks2D: Landmark2D[], worldLandmarks: Landmark3D[]): GestureSignal {
    const now = performance.now()

    // Run calibration machine
    const prevStatus = this.calibration.status
    this.calibration.update(landmarks2D, worldLandmarks, now)
    const newStatus = this.calibration.status

    // Detect calibration transitions
    if (prevStatus !== newStatus) {
      this._callbacks.onCalibrationChange?.(newStatus)

      // Calibration just finished -> seed live baselines from calibration
      if (prevStatus === 'calibrating' && newStatus === 'pos-center') {
        const b = this.calibration.baselines!
        this.baselineLeftY = b.leftY
        this.baselineRightY = b.rightY
        this.step.heelBaseL = { ...b.heelBaseL }
        this.step.heelBaseR = { ...b.heelBaseR }
      }

      // pos-center -> ready: now handled by crossed-arms detection below
    }

    // Crossed arms detection (start game / toggle pause)
    const crossedArmsEdge = detectCrossedArms(this.crossedArms, landmarks2D, now)
    if (crossedArmsEdge === 'start') {
      log(`[gesture] crossed-arms START`)
      this._callbacks.onCrossedArmsStart?.()
      if (this.calibration.status === 'pos-center') {
        this.calibration.confirmReady()
        this.step.activeStep = null
        this.step.steppedFoot = null
        this._callbacks.onCalibrationChange?.('ready')
      } else if (this.calibration.status === 'ready') {
        this.calibration.pause()
        this.step.activeStep = null
        this.step.steppedFoot = null
        this._callbacks.onPause?.()
      } else if (this.calibration.status === 'paused') {
        this.calibration.resume()
        this.step.activeStep = null
        this.step.steppedFoot = null
        this._callbacks.onResume?.()
      }
    } else if (crossedArmsEdge === 'end') {
      log(`[gesture] crossed-arms END`)
      this._callbacks.onCrossedArmsEnd?.()
    }

    // Only detect gestures when ready
    if (this.calibration.status !== 'ready') return null
    if (this.baselineLeftY === null || this.baselineRightY === null) return null

    // Detection cooldown: skip all detection + baseline adaptation after a signal
    if (now < this.cooldownUntil) return null

    // Ankle Y for baseline adaptation
    const leftY = landmarks2D[27]?.y
    const rightY = landmarks2D[28]?.y
    if (leftY === undefined || rightY === undefined) return null

    if (Math.random() < 0.033) {
      log(`[gesture] L=${leftY.toFixed(4)} R=${rightY.toFixed(4)}`)
    }

    // Step detection
    if (this.step.heelBaseL && this.step.heelBaseR && worldLandmarks?.length >= 33) {
      const stepSignal = this.updateStepDetection(worldLandmarks)
      if (stepSignal) return stepSignal
    }

    // Adapt ankle baselines slowly when standing
    this.baselineLeftY = this.baselineLeftY * (1 - BASELINE_ADAPT_ALPHA) + leftY * BASELINE_ADAPT_ALPHA
    this.baselineRightY = this.baselineRightY * (1 - BASELINE_ADAPT_ALPHA) + rightY * BASELINE_ADAPT_ALPHA

    return null
  }

  private updateStepDetection(worldLandmarks: Landmark3D[]): GestureSignal {
    const cfg = this._stepConfig
    const { heelBaseL, heelBaseR } = this.step

    if (this.step.activeStep) {
      if (checkStepReset(worldLandmarks, heelBaseL!, heelBaseR!, cfg, this.step.activeStep, this.step.steppedFoot!)) {
        this.step.activeStep = null
        this.step.steppedFoot = null
      }
    } else {
      const result = detectStep(worldLandmarks, heelBaseL!, heelBaseR!, cfg)
      if (result) {
        this.step.activeStep = result.signal
        this.step.steppedFoot = result.foot
        const dir = result.signal.replace('step-', '') as StepDirection
        this._callbacks.onStep?.(dir, result.foot)
        return this.emit(result.signal)
      }

      // Adapt step baselines when standing still
      const lt = worldLandmarks[31], rt = worldLandmarks[32]
      this.step.heelBaseL = adaptBaseline(heelBaseL!, lt)
      this.step.heelBaseR = adaptBaseline(heelBaseR!, rt)
    }

    return null
  }

  private emit(signal: GestureSignal, extra?: () => void): GestureSignal {
    this.cooldownUntil = performance.now() + SIGNAL_COOLDOWN_MS
    log(`[gesture] ${signal} detected!`)
    extra?.()
    this._callbacks.onGesture?.(signal)
    return signal
  }
}
