// Cursor pipeline — shared post-detection logic.
//
// Takes per-frame { yaw, pitch } poses from any tracker and integrates them
// into a viewport-normalized cursor position via velocity-based control.
// Sends CURSOR_UPDATE messages to background.js, which relays to the active
// tab's accessibility script.
//
// Algorithm: ported 1:1 from apps/harness/hands_free_host.py (run_cv_loop).
// Rate-based — cursor displacement per frame is proportional to head/gaze
// angular velocity, NOT to absolute pose. Stop moving and the cursor stops;
// no need to return to a calibrated neutral.

import { log } from "./log.js";

// Tuning — ported from hands_free_host.py. The Python code drove the OS cursor
// across the full desktop. We drive a virtual cursor across the browser
// viewport (smaller, more often the area of interest), so relative sensitivity
// is a bit higher to match the responsive feel of the original.
const SENS_X = 1.0;            // normalized-viewport units per yaw-velocity-sec
const SENS_Y = 1.0;
const ACCEL_FACTOR = 1.2;      // matches Python ACCEL_FACTOR
const VEL_SMOOTH = 0.15;       // EMA alpha for cursor velocity
const MAX_SPEED = 0.8;         // max normalized-viewport speed
const DEADZONE = 0.015;        // matches Python DEADZONE

export function createCursorPipeline() {
  // Cursor lives in [0, 1]^2 (normalized viewport space).
  let cursorX = 0.5;
  let cursorY = 0.5;

  let prevYaw = null;
  let prevPitch = null;
  let vx_s = 0.0;
  let vy_s = 0.0;
  let lastTime = performance.now();

  let cursorUpdatesSent = 0;
  let diagCount = 0;
  const DIAG_EVERY = 60;

  return {
    feed(pose) {
      const now = performance.now();
      const dt = Math.max((now - lastTime) / 1000.0, 0.001);
      lastTime = now;

      // Pose loss — reset prev so we don't see a huge velocity jump on re-acquire.
      if (!pose) {
        prevYaw = null;
        prevPitch = null;
        return;
      }

      const { yaw, pitch } = pose;

      if (prevYaw !== null) {
        // Angular velocity (per-frame head-pose delta).
        let vyaw = (yaw - prevYaw) / dt;
        let vpitch = (pitch - prevPitch) / dt;

        // Velocity deadzone — only move when actively turning.
        if (Math.abs(vyaw) < DEADZONE) vyaw = 0;
        if (Math.abs(vpitch) < DEADZONE) vpitch = 0;

        // Non-linear acceleration: small movements precise, large ones fast.
        const vyaw_accel = vyaw * (1.0 + ACCEL_FACTOR * Math.abs(vyaw));
        const vpitch_accel = vpitch * (1.0 + ACCEL_FACTOR * Math.abs(vpitch));

        // EMA smoothing + clamp.
        vx_s = VEL_SMOOTH * vyaw_accel + (1 - VEL_SMOOTH) * vx_s;
        vy_s = VEL_SMOOTH * vpitch_accel + (1 - VEL_SMOOTH) * vy_s;
        vx_s = Math.max(-MAX_SPEED, Math.min(MAX_SPEED, vx_s));
        vy_s = Math.max(-MAX_SPEED, Math.min(MAX_SPEED, vy_s));

        cursorX += vx_s * SENS_X * dt;
        cursorY += vy_s * SENS_Y * dt;
        cursorX = Math.max(0, Math.min(1, cursorX));
        cursorY = Math.max(0, Math.min(1, cursorY));

        chrome.runtime.sendMessage({
          type: "CURSOR_UPDATE",
          x: cursorX,
          y: cursorY,
        }).catch(() => {});
        cursorUpdatesSent += 1;
      }

      prevYaw = yaw;
      prevPitch = pitch;

      diagCount += 1;
      if (diagCount >= DIAG_EVERY) {
        log("pipeline diag", {
          cursorUpdatesSent,
          vx_s: Number(vx_s.toFixed(4)),
          vy_s: Number(vy_s.toFixed(4)),
        });
        diagCount = 0;
        cursorUpdatesSent = 0;
      }
    },

    reset() {
      prevYaw = null;
      prevPitch = null;
      vx_s = 0.0;
      vy_s = 0.0;
    },
  };
}
