// 2D pointer dispatch + track (drag) system. The host (createViewer2d canvas / Creator2DView) delivers
// raw pointer phases via _creator2d.setOnPointer; this module hit-tests in the active scene and routes
// to the struck node and the scene's listeners. Mirrors the 3D gl/touch.ts, but the hit-test is done
// here (screen→world via the camera, then a physics overlap), not by the host.
//
// What can be hit: a node with a Physics2D shape (precise) → its 'touchstart'/'click' events; the
// active Scene2D always gets the event too (target = the hit node or null). A node listener can call
// ev.track({ onMove, onEnd, onCancel }) to capture the rest of the gesture (drag/pan).

import { ClickEvent, TouchStartEvent, type TrackHandler } from "../runtime/touch"
import { Physics2D } from "./Physics2D"
import { Scene2D } from "./Scene2D"
import type { Node2D } from "./Node2D"

type Active = { tracker?: TrackHandler, lastX: number, lastY: number }
const active = new Map<number, Active>()

let registered = false
/** Register the host pointer callback once, on the first click/touchstart listener (node or scene). */
export const ensurePointerEvents = (): void => {
  if (registered) return
  registered = true
  _creator2d.setOnPointer((phase, id, x, y) => {
    if (phase === 0) onDown(id, x, y)
    else if (phase === 1) onMove(id, x, y)
    else if (phase === 2) onUp(id, x, y)
    else onCancel(id)
  })
}

// Hit-test: screen (logical px) → world → topmost physics body at the point.
const hitTest = (scene: Scene2D, sx: number, sy: number): { node: Node2D | null, wx: number, wy: number } => {
  const w = scene.camera.screenToWorld(sx, sy)
  return { node: Physics2D.overlapPoint(w), wx: w.x, wy: w.y }
}

const onDown = (id: number, sx: number, sy: number): void => {
  const scene = Scene2D.active
  if (!scene) return
  const { node, wx, wy } = hitTest(scene, sx, sy)
  const ev = new TouchStartEvent<Node2D | null>(sx, sy, id)
  ev.worldX = wx; ev.worldY = wy
  ev.target = node

  let tracker: TrackHandler | undefined
  if (node) { node._dispatchEvent("touchstart", ev); tracker = ev._trackHandler }
  if (!tracker) {
    for (const l of scene._touchStartListeners.slice()) {
      l(ev)
      if (ev._trackHandler) { tracker = ev._trackHandler; break }
    }
  }
  active.set(id, { tracker, lastX: sx, lastY: sy })
}

const onMove = (id: number, sx: number, sy: number): void => {
  const a = active.get(id)
  if (!a) return
  a.tracker?.onMove?.({ clientX: sx, clientY: sy, deltaX: sx - a.lastX, deltaY: sy - a.lastY })
  a.lastX = sx; a.lastY = sy
}

const onUp = (id: number, sx: number, sy: number): void => {
  const a = active.get(id)
  active.delete(id)
  a?.tracker?.onEnd?.({ clientX: sx, clientY: sy, deltaX: a ? sx - a.lastX : 0, deltaY: a ? sy - a.lastY : 0 })

  // A pointer-up is also a click, dispatched at the up location (independent of any track).
  const scene = Scene2D.active
  if (!scene) return
  const { node, wx, wy } = hitTest(scene, sx, sy)
  const ev = new ClickEvent<Node2D | null>(sx, sy, id)
  ev.worldX = wx; ev.worldY = wy
  ev.target = node
  if (node) node._dispatchEvent("click", ev)
  for (const l of scene._clickListeners.slice()) l(ev)
}

const onCancel = (id: number): void => {
  const a = active.get(id)
  active.delete(id)
  a?.tracker?.onCancel?.()
}
