// tracky-host.js — runs Tracky Mouse in a visible context (side-panel iframe).
// We tried running Tracky in an offscreen document (offscreen.js) but that
// path leaves clmtrackr's tracking loop dormant for unclear reasons —
// despite frames flowing into the video element, clmTrackingStarted never
// flips and onPointerMove fires only the (centered) init coordinates.
// Running in a side-panel-side page sidesteps all of that.

const VIRTUAL_W = 1920;
const VIRTUAL_H = 1080;

const statusEl = document.getElementById("status");
function setStatus(text) { if (statusEl) statusEl.textContent = text; }
function log(...args) {
  console.log("[tracky-host]", ...args);
  chrome.runtime.sendMessage({ type: "HEADTRACK_LOG", level: "info", text: args.join(" ") }).catch(() => {});
}
function errlog(...args) {
  console.error("[tracky-host]", ...args);
  chrome.runtime.sendMessage({ type: "HEADTRACK_LOG", level: "error", text: args.join(" ") }).catch(() => {});
}

function postCursor(nx, ny) {
  chrome.runtime.sendMessage({ type: "CURSOR_UPDATE", x: nx, y: ny }).catch(() => {});
}

(async function bootstrap() {
  log("module loaded");
  if (typeof TrackyMouse === "undefined") {
    errlog("TrackyMouse global missing");
    setStatus("error: tracky-mouse.js failed to load");
    return;
  }

  TrackyMouse.dependenciesRoot = chrome.runtime.getURL("vendor/tracky-mouse").replace(/\/+$/, "");

  TrackyMouse.onPointerMove = (x, y) => {
    const nx = Math.max(0, Math.min(1, x / VIRTUAL_W));
    const ny = Math.max(0, Math.min(1, y / VIRTUAL_H));
    postCursor(nx, ny);
  };

  // Stub face-landmarks-detection (TFJS) — MV3 CSP forbids 'unsafe-eval' in
  // extension pages, and TFJS uses new Function() to compile shaders.
  //
  // CRITICAL: estimateFaces must NEVER resolve. If it resolves with `[]`,
  // Tracky's then-handler (tracky-mouse.js:~3389) reads `predictions[0]`
  // → undefined → and unconditionally sets `useClmTracking = false`,
  // disabling clmtrackr forever. By returning a forever-pending promise we
  // keep `facemeshEstimating` stuck on true (so the path doesn't re-fire)
  // and leave `useClmTracking` at its default `true` so clmtrackr stays on.
  window.faceLandmarksDetection = {
    SupportedModels: { MediaPipeFaceMesh: "mediapipe-face-mesh" },
    createDetector: async () => ({
      estimateFaces: () => new Promise(() => {}),
      dispose: () => {},
    }),
  };

  // Skip face-landmarks-detection.min.js (drags in TFJS) and face_mesh.js
  // (only needed by TFJS path). Load only the eval-free clmtrackr pipeline.
  TrackyMouse.loadDependencies = function ({ statsJs = false } = {}) {
    TrackyMouse.dependenciesRoot = TrackyMouse.dependenciesRoot.replace(/\/+$/, "");
    const loadScript = (src) => new Promise((resolve, reject) => {
      const s = document.createElement("script");
      s.type = "text/javascript";
      s.onload = resolve; s.onerror = reject; s.src = src;
      document.head.append(s);
    });
    const files = [
      `${TrackyMouse.dependenciesRoot}/lib/no-eval.js`,
      `${TrackyMouse.dependenciesRoot}/lib/clmtrackr.js`,
      `${TrackyMouse.dependenciesRoot}/lib/OneEuroFilter.js`,
    ];
    if (statsJs) files.push(`${TrackyMouse.dependenciesRoot}/lib/stats.js`);
    return Promise.all(files.map(loadScript));
  };

  // Tracky derives screenWidth/screenHeight from window.innerWidth/innerHeight.
  // The side-panel iframe may be tiny; pin to a sane virtual screen.
  try {
    Object.defineProperty(window, "innerWidth", { get: () => VIRTUAL_W, configurable: true });
    Object.defineProperty(window, "innerHeight", { get: () => VIRTUAL_H, configurable: true });
  } catch (_) {}

  // Patch document.createElement once so Tracky's <video> element is muted +
  // autoplay + appended (sandboxed iframes can be picky about autoplay).
  const _origCreate = document.createElement.bind(document);
  let _video = null;
  document.createElement = function (tag, ...rest) {
    const el = _origCreate(tag, ...rest);
    if (typeof tag === "string" && tag.toLowerCase() === "video" && !_video) {
      _video = el;
      el.muted = true;
      el.autoplay = true;
      el.setAttribute("playsinline", "");
      Promise.resolve().then(() => { if (!el.isConnected) document.body.appendChild(el); });
      ["loadstart", "loadedmetadata", "loadeddata", "canplay", "play", "playing", "pause", "stalled", "error"]
        .forEach(evt => el.addEventListener(evt, () => log(`video[${evt}] rs=${el.readyState} paused=${el.paused}`)));
      document.createElement = _origCreate;
    }
    return el;
  };

  try {
    await TrackyMouse.loadDependencies();
    log("dependencies loaded");

    // Wrap clm.tracker constructor so we can see whether face detection runs.
    if (typeof clm !== "undefined" && typeof clm.tracker === "function") {
      const Orig = clm.tracker;
      let trackCalls = 0, posReturns = { array: 0, false: 0, other: 0 }, lastPoint = "n/a";
      clm.tracker = function (...args) {
        const inst = new Orig(...args);
        log(`clm.tracker instance created; methods=${Object.keys(inst).filter(k=>typeof inst[k]==="function").join(",")}`);
        const oTrack = inst.track, oPos = inst.getCurrentPosition, oInit = inst.init, oIFD = inst.initFaceDetector, oReset = inst.reset;
        if (oTrack) inst.track = function (...a) {
          trackCalls++;
          try { return oTrack.apply(this, a); }
          catch (e) { errlog(`clmTracker.track threw: ${e?.message || e}`); throw e; }
        };
        if (oPos) inst.getCurrentPosition = function () {
          const r = oPos.apply(this, arguments);
          if (Array.isArray(r)) { posReturns.array++; const p = r[62]; if (p) lastPoint = `(${p[0]|0},${p[1]|0})`; }
          else if (r === false) posReturns.false++; else posReturns.other++;
          return r;
        };
        if (oInit) inst.init = function (...a) {
          log(`clmTracker.init() with model=${a[0]?"provided":"DEFAULT"}`);
          try { return oInit.apply(this, a); } catch (e) { errlog(`clmTracker.init threw: ${e?.message || e}`); throw e; }
        };
        if (oIFD) inst.initFaceDetector = function (...a) {
          log(`clmTracker.initFaceDetector(${a[0]?.tagName}) videoSize=${a[0]?.videoWidth}x${a[0]?.videoHeight}`);
          try { const r = oIFD.apply(this, a); log(`initFaceDetector returned ${r}`); return r; }
          catch (e) { errlog(`initFaceDetector threw: ${e?.message || e}`); throw e; }
        };
        if (oReset) inst.reset = function (...a) {
          log("clmTracker.reset() called");
          try { return oReset.apply(this, a); } catch (e) { errlog(`reset threw: ${e?.message || e}`); throw e; }
        };
        return inst;
      };
      Object.assign(clm.tracker, Orig);
      setInterval(() => {
        log(`clmtrackr: track/s=${trackCalls} pos: array=${posReturns.array} false=${posReturns.false} other=${posReturns.other} | nose=${lastPoint}`);
        trackCalls = 0; posReturns = { array: 0, false: 0, other: 0 };
      }, 1000);
    } else {
      errlog("clm.tracker not found");
    }
    window.addEventListener("error", (e) => errlog(`window.onerror: ${e?.message} @ ${e?.filename}:${e?.lineno}`));
    window.addEventListener("unhandledrejection", (e) => errlog(`unhandledrejection: ${e?.reason?.message || e?.reason}`));
  } catch (e) {
    errlog("loadDependencies failed:", e?.message || e);
    setStatus("error: dependency load failed");
    return;
  }

  setStatus("starting camera…");
  let trackyHandle;
  try {
    const root = document.getElementById("tracky-root");
    trackyHandle = TrackyMouse.init(root);
    log("init returned");
  } catch (e) {
    errlog("init failed:", e?.message || e);
    setStatus("error: init failed");
    return;
  }

  if (trackyHandle && typeof trackyHandle._waitForSettingsLoaded === "function") {
    try { await trackyHandle._waitForSettingsLoaded(); } catch (_) {}
  }

  if (typeof TrackyMouse.useCamera === "function") {
    try {
      await TrackyMouse.useCamera();
      log("useCamera resolved");
    } catch (e) {
      errlog("useCamera failed:", e?.message || e);
      setStatus("error: camera denied");
      return;
    }
  }

  if (trackyHandle && typeof trackyHandle._setPaused === "function") {
    trackyHandle._setPaused(false);
    log("tracking unpaused");
  } else {
    const startBtn = document.querySelector(".tracky-mouse-start-stop-button");
    if (startBtn) { startBtn.click(); log("clicked start button (fallback)"); }
  }

  setStatus("tracking — move your head");

  // Show a downscaled preview of the camera so the user can confirm their
  // face is in frame. Tracky already draws to its hidden canvas; we mirror
  // it to the preview canvas at 10 fps.
  const previewCanvas = document.getElementById("preview-canvas");
  const previewCtx = previewCanvas.getContext("2d");
  setInterval(() => {
    const trackyCanvas = document.querySelector(".tracky-mouse-canvas");
    if (trackyCanvas && trackyCanvas.width > 0) {
      try {
        previewCtx.drawImage(trackyCanvas, 0, 0, previewCanvas.width, previewCanvas.height);
      } catch (_) {}
    }
  }, 100);

  // Diagnostic counter
  let omCount = 0, lastX = 0, lastY = 0;
  const origOM = TrackyMouse.onPointerMove;
  TrackyMouse.onPointerMove = (x, y) => {
    omCount++; lastX = x; lastY = y;
    if (origOM) origOM(x, y);
  };
  setInterval(() => {
    log(`rate=${omCount}/s xy=(${lastX|0},${lastY|0})`);
    omCount = 0;
  }, 1000);

  // When parent (popup) is unloading, dispose tracky.
  window.addEventListener("pagehide", () => {
    try { trackyHandle?.dispose?.(); } catch (_) {}
  });
})();
