/**
 * Touch-first input: left virtual stick = move/strafe, drag on the view =
 * turn, FIRE button. Desktop: WASD/arrows, mouse drag to turn, click/space
 * to fire.
 */

export interface InputState {
  /** strafe -1..1 (right positive) */
  moveX: number;
  /** forward -1..1 */
  moveY: number;
  /** accumulated turn in radians since last consume */
  turnDelta: number;
  /** fire is held */
  firing: boolean;
}

export class Input {
  state: InputState = { moveX: 0, moveY: 0, turnDelta: 0, firing: false };
  /** set by main on first interaction (unlocks audio) */
  onFirstInteract: (() => void) | null = null;

  private keys = new Set<string>();
  private stickId: number | null = null;
  private stickBaseX = 0;
  private stickBaseY = 0;
  private turnId: number | null = null;
  private turnLastX = 0;
  private turnStartX = 0;
  private turnStartT = 0;
  private turnMoved = 0;
  private fireTouch = false;
  private interacted = false;
  /** quick tap on the view fires too */
  onTapFire: (() => void) | null = null;

  constructor(
    view: HTMLElement,
    stick: HTMLElement,
    private nub: HTMLElement,
    fireBtn: HTMLElement,
  ) {
    window.addEventListener('keydown', (e) => {
      if (e.repeat) return;
      this.keys.add(e.code);
      this.firstInteract();
      if (e.code === 'Space') {
        e.preventDefault();
        this.onTapFire?.(); // fire immediately, don't wait for a frame
      }
      this.updateKeys();
    });
    window.addEventListener('keyup', (e) => {
      if (e.code === 'Space') e.preventDefault();
      this.keys.delete(e.code);
      this.updateKeys();
    });

    // --- virtual stick ---
    const stickRect = () => stick.getBoundingClientRect();
    stick.addEventListener('pointerdown', (e) => {
      e.preventDefault();
      this.firstInteract();
      this.stickId = e.pointerId;
      const r = stickRect();
      this.stickBaseX = r.left + r.width / 2;
      this.stickBaseY = r.top + r.height / 2;
      stick.setPointerCapture(e.pointerId);
      this.moveStick(e.clientX, e.clientY);
    });
    stick.addEventListener('pointermove', (e) => {
      if (e.pointerId === this.stickId) this.moveStick(e.clientX, e.clientY);
    });
    const stickEnd = (e: PointerEvent) => {
      if (e.pointerId !== this.stickId) return;
      this.stickId = null;
      this.state.moveX = 0;
      this.state.moveY = 0;
      this.nub.style.transform = 'translate(0px, 0px)';
      this.updateKeys();
    };
    stick.addEventListener('pointerup', stickEnd);
    stick.addEventListener('pointercancel', stickEnd);

    // --- turn drag on the view (mouse + touch via pointer events) ---
    view.addEventListener('pointerdown', (e) => {
      e.preventDefault();
      this.firstInteract();
      if (this.turnId === null) {
        this.turnId = e.pointerId;
        this.turnLastX = e.clientX;
        this.turnStartX = e.clientX;
        this.turnStartT = performance.now();
        this.turnMoved = 0;
        view.setPointerCapture(e.pointerId);
      }
    });
    view.addEventListener('pointermove', (e) => {
      if (e.pointerId === this.turnId) {
        const dx = e.clientX - this.turnLastX;
        this.turnLastX = e.clientX;
        this.turnMoved += Math.abs(dx);
        this.state.turnDelta += dx * 0.009;
      }
    });
    const turnEnd = (e: PointerEvent) => {
      if (e.pointerId !== this.turnId) return;
      this.turnId = null;
      // quick, still tap = fire
      const dt = performance.now() - this.turnStartT;
      if (dt < 250 && this.turnMoved < 12 && Math.abs(e.clientX - this.turnStartX) < 12) {
        this.onTapFire?.();
      }
    };
    view.addEventListener('pointerup', turnEnd);
    view.addEventListener('pointercancel', turnEnd);

    // --- fire button ---
    fireBtn.addEventListener('pointerdown', (e) => {
      e.preventDefault();
      this.firstInteract();
      this.fireTouch = true;
      this.updateKeys();
      fireBtn.classList.add('held');
    });
    const fireEnd = () => {
      this.fireTouch = false;
      this.updateKeys();
      fireBtn.classList.remove('held');
    };
    fireBtn.addEventListener('pointerup', fireEnd);
    fireBtn.addEventListener('pointercancel', fireEnd);
    fireBtn.addEventListener('pointerleave', fireEnd);

    // suppress context menu / scroll on game surfaces
    for (const el of [view, stick, fireBtn]) {
      el.addEventListener('contextmenu', (e) => e.preventDefault());
      el.style.touchAction = 'none';
    }
  }

  private firstInteract(): void {
    if (!this.interacted) {
      this.interacted = true;
      this.onFirstInteract?.();
    }
  }

  private moveStick(cx: number, cy: number): void {
    const dx = cx - this.stickBaseX;
    const dy = cy - this.stickBaseY;
    const max = 42;
    const len = Math.hypot(dx, dy);
    const k = len > max ? max / len : 1;
    const nx = (dx * k) / max;
    const ny = (dy * k) / max;
    this.nub.style.transform = `translate(${dx * k}px, ${dy * k}px)`;
    // deadzone
    this.state.moveX = Math.abs(nx) > 0.18 ? nx : 0;
    this.state.moveY = Math.abs(ny) > 0.18 ? -ny : 0;
  }

  private updateKeys(): void {
    const k = this.keys;
    let mx = 0, my = 0;
    if (k.has('KeyW') || k.has('ArrowUp')) my += 1;
    if (k.has('KeyS') || k.has('ArrowDown')) my -= 1;
    if (k.has('KeyA')) mx -= 1;
    if (k.has('KeyD')) mx += 1;
    // only override stick when keys are doing something or stick idle
    if (this.stickId === null) {
      this.state.moveX = mx;
      this.state.moveY = my;
    }
    this.state.firing = this.fireTouch || k.has('Space');
  }

  /** call once per frame: keyboard arrow turning */
  keyboardTurn(dt: number): void {
    if (this.keys.has('ArrowLeft')) this.state.turnDelta -= 2.6 * dt;
    if (this.keys.has('ArrowRight')) this.state.turnDelta += 2.6 * dt;
  }

  /** consume accumulated turn */
  takeTurn(): number {
    const t = this.state.turnDelta;
    this.state.turnDelta = 0;
    return t;
  }
}
