// flight/input.js (Lane B) — raw devices -> one normalized intent object. No game logic
// here; player.js reads `intent` and never touches an event listener.
//
// The scheme is twin-stick, which is what makes the charter's "mouse-aim + WASD" and
// "gamepad twin-stick" the same game rather than two:
//   left hand  (WASD / left stick)   -> move the ship inside the cross-section disc
//   right hand (mouse / right stick) -> aim the reticle (the ship's nose follows it, lagged)
//   Shift/Ctrl (or LT/RT)            -> throttle, springs back to neutral when released
//   Space / A / RB                   -> boost
//   LMB / RT / X                     -> fire
// WASD is therefore NOT throttle. Throttle is a modifier because it lives in [0.6,1.4] and
// spends most of its life at 1.0 — it's a lean, not a stick.

const DEADZONE = 0.18;
const dz = (v) => (Math.abs(v) < DEADZONE ? 0 : (v - Math.sign(v) * DEADZONE) / (1 - DEADZONE));

export function createInput(dom = document.body) {
  const keys = new Set();

  const intent = {
    disc: { x: 0, y: 0 },      // −1..1 desired disc movement (x = lateral/bin, y = up/nor)
    aimDelta: { x: 0, y: 0 },  // mouse pixels accumulated since the last read (consumed)
    aimStick: { x: 0, y: 0 },  // −1..1 right stick
    throttle: 0,               // −1..1 (Ctrl .. Shift)
    boost: false,              // edge-triggered: true for exactly one read
    fire: false,               // held
    fire2: false,              // edge-triggered
    locked: false,             // pointer lock state (the "am I flying" signal)
  };

  // Held inputs are tracked per-device and OR'd each sample. Sharing one flag would latch:
  // the trigger sets it true, and only mouse-up ever sets it false again.
  let mouseFire = false, padFire = false;
  let boostEdge = false, fire2Edge = false, padBoostWas = false, padFire2Was = false;

  const onKeyDown = (e) => {
    keys.add(e.code);
    if (e.code === 'Space') { boostEdge = true; e.preventDefault(); }
    if (e.code === 'KeyF') fire2Edge = true;
  };
  const onKeyUp = (e) => keys.delete(e.code);
  const onBlur = () => keys.clear();   // alt-tab must not leave a key stuck down

  const onMouseMove = (e) => {
    if (!intent.locked) return;
    intent.aimDelta.x += e.movementX;
    intent.aimDelta.y += e.movementY;
  };
  const onMouseDown = (e) => {
    if (!intent.locked) { dom.requestPointerLock?.(); return; }
    if (e.button === 0) mouseFire = true;
    if (e.button === 2) fire2Edge = true;
  };
  const onMouseUp = (e) => { if (e.button === 0) mouseFire = false; };
  const onLockChange = () => {
    intent.locked = document.pointerLockElement === dom;
    if (!intent.locked) { mouseFire = false; keys.clear(); }
  };
  const onContext = (e) => e.preventDefault();   // RMB is the torpedo, not a menu

  addEventListener('keydown', onKeyDown);
  addEventListener('keyup', onKeyUp);
  addEventListener('blur', onBlur);
  addEventListener('mousemove', onMouseMove);
  dom.addEventListener('mousedown', onMouseDown);
  addEventListener('mouseup', onMouseUp);
  document.addEventListener('pointerlockchange', onLockChange);
  dom.addEventListener('contextmenu', onContext);

  function readPad() {
    padFire = false;
    const pads = navigator.getGamepads?.() || [];
    for (const p of pads) {
      if (!p?.connected) continue;
      const [lx, ly, rx, ry] = p.axes;
      // left stick wins over WASD only when actually deflected, so both can coexist
      const dx = dz(lx ?? 0), dy = dz(ly ?? 0);
      if (dx || dy) { intent.disc.x = dx; intent.disc.y = -dy; }
      intent.aimStick.x = dz(rx ?? 0);
      intent.aimStick.y = -dz(ry ?? 0);

      const lt = p.buttons[6]?.value ?? 0, rt = p.buttons[7]?.value ?? 0;
      if (lt > 0.05 || rt > 0.05) intent.throttle = rt - lt;
      padFire = rt > 0.5;

      const padBoost = !!(p.buttons[0]?.pressed || p.buttons[5]?.pressed);
      if (padBoost && !padBoostWas) boostEdge = true;
      padBoostWas = padBoost;

      const padFire2 = !!p.buttons[2]?.pressed;
      if (padFire2 && !padFire2Was) fire2Edge = true;
      padFire2Was = padFire2;
      return;
    }
  }

  return {
    intent,

    // Called once per frame by player.js, before it reads `intent`.
    sample() {
      intent.disc.x = (keys.has('KeyD') ? 1 : 0) - (keys.has('KeyA') ? 1 : 0);
      intent.disc.y = (keys.has('KeyW') ? 1 : 0) - (keys.has('KeyS') ? 1 : 0);
      intent.aimStick.x = 0; intent.aimStick.y = 0;
      intent.throttle = (keys.has('ShiftLeft') || keys.has('ShiftRight') ? 1 : 0) -
                        (keys.has('ControlLeft') || keys.has('ControlRight') ? 1 : 0);
      readPad();
      intent.fire = mouseFire || padFire;

      // normalize diagonal so corner-dodging isn't 41% faster than a cardinal one
      const m = Math.hypot(intent.disc.x, intent.disc.y);
      if (m > 1) { intent.disc.x /= m; intent.disc.y /= m; }

      intent.boost = boostEdge; boostEdge = false;
      intent.fire2 = fire2Edge; fire2Edge = false;
      return intent;
    },

    // player.js consumes the accumulated mouse delta exactly once per frame
    takeAim() {
      const d = { x: intent.aimDelta.x, y: intent.aimDelta.y };
      intent.aimDelta.x = 0; intent.aimDelta.y = 0;
      return d;
    },

    // Harness/test hook (flight/dev.html). Keyboard intents can be driven with real
    // KeyboardEvents, but firing is gated behind pointer lock, which an automated session
    // can't be granted — so the trigger needs a door. Dev-only; nothing ships against it.
    _debug: {
      setFire(v) { mouseFire = !!v; },
      pulseFire2() { fire2Edge = true; },
      pulseBoost() { boostEdge = true; },
      key(code, down = true) { down ? keys.add(code) : keys.delete(code); },
    },

    dispose() {
      removeEventListener('keydown', onKeyDown);
      removeEventListener('keyup', onKeyUp);
      removeEventListener('blur', onBlur);
      removeEventListener('mousemove', onMouseMove);
      dom.removeEventListener('mousedown', onMouseDown);
      removeEventListener('mouseup', onMouseUp);
      document.removeEventListener('pointerlockchange', onLockChange);
      dom.removeEventListener('contextmenu', onContext);
      if (document.pointerLockElement === dom) document.exitPointerLock?.();
    },
  };
}
