// Head tracker — MediaPipe Face Landmarker → { yaw, pitch }.
//
// Extracted from the original offscreen.js. Detection only; the cursor
// integration / smoothing / dispatch all live in pipeline.js.

import { FaceLandmarker, FilesetResolver } from "../vision_bundle.js";
import { log, errlog } from "./log.js";

const DETECT_INTERVAL_MS = 33; // ~30 Hz — offscreen documents don't tick rAF.

export function createHeadTracker({ video }) {
  let faceLandmarker = null;
  let intervalId = null;
  let running = false;
  let poseCb = null;

  // Diagnostics — log loop health every N frames so we can tell where it's stuck.
  const DIAG_EVERY = 60;
  let diagCount = 0;
  let framesWithFace = 0;
  let framesNoFace = 0;
  let posesEmitted = 0;

  async function init() {
    const wasmFolder = chrome.runtime.getURL("mediapipe");
    const modelPath = chrome.runtime.getURL("mediapipe/face_landmarker.task");
    const vision = await FilesetResolver.forVisionTasks(wasmFolder);
    faceLandmarker = await FaceLandmarker.createFromOptions(vision, {
      baseOptions: {
        modelAssetPath: modelPath,
        delegate: "GPU",
      },
      outputFaceBlendshapes: false,
      runningMode: "VIDEO",
      numFaces: 1,
    });
    log("HeadTracker: MediaPipe initialized");
  }

  function detect() {
    if (!running || !faceLandmarker) return;
    const now = performance.now();
    const results = faceLandmarker.detectForVideo(video, now);

    diagCount += 1;
    if (results.faceLandmarks.length > 0) framesWithFace += 1; else framesNoFace += 1;

    if (diagCount >= DIAG_EVERY) {
      log("head loop diag", {
        framesWithFace,
        framesNoFace,
        posesEmitted,
        videoReady: !!(video.videoWidth && video.videoHeight),
        videoSize: `${video.videoWidth}x${video.videoHeight}`,
      });
      diagCount = 0;
      framesWithFace = 0;
      framesNoFace = 0;
      posesEmitted = 0;
    }

    if (results.faceLandmarks.length === 0) {
      // Face lost — emit a sentinel so the pipeline can reset its prev-pose state
      // and avoid a velocity spike on re-acquire.
      poseCb?.(null);
      return;
    }

    const lm = results.faceLandmarks[0];
    const left_eye = lm[33];
    const right_eye = lm[263];
    const nose_tip = lm[1];
    const nose_bridge = lm[168];

    const eye_width = right_eye.x - left_eye.x;
    if (eye_width <= 0.03) return;

    // Negate yaw to compensate for the un-mirrored camera feed (the Python
    // reference flipped the frame buffer; we sign-flip the yaw so a rightward
    // head turn produces a rightward cursor delta).
    const yaw = -(nose_tip.x - 0.5) / eye_width;
    const pitch = (nose_tip.y - nose_bridge.y) / eye_width;

    poseCb?.({ yaw, pitch });
    posesEmitted += 1;
  }

  return {
    init,
    start() {
      if (running) return;
      running = true;
      intervalId = setInterval(detect, DETECT_INTERVAL_MS);
      log("HeadTracker: started");
    },
    stop() {
      running = false;
      if (intervalId !== null) {
        clearInterval(intervalId);
        intervalId = null;
      }
      log("HeadTracker: stopped");
    },
    onPose(cb) {
      poseCb = cb;
    },
  };
}
