import { Input } from "./input"
import interact from "interactjs"

/**
 * Maintains a map of pressed keys.
 *
 * Produces events while key is pressed with elapsed time
 */
export class Keyboard {
  pressed = {}
  released = {}
  private readonly flipX: boolean
  private readonly disabled: boolean

  getEvents() {
    const keys = Object.keys(this.pressed)
      .filter((key) => !/Shift/.test(key))
      .filter((key) => !/Control/.test(key))
    const shift = Object.keys(this.pressed).some((key) => /Shift/.test(key))
    const control = Object.keys(this.pressed).some((key) => /Control/.test(key))
    const result: Input[] = []

    keys.forEach((k) => {
      const t = performance.now() - this.pressed[k]
      result.push(new Input(control ? t / 3 : t, shift ? "Shift" + k : k))
      if (k != "Space") {
        this.pressed[k] = performance.now()
      }
    })

    Object.keys(this.released).forEach((key) =>
      result.push(new Input(this.released[key], key + "Up"))
    )

    this.released = {}
    return result
  }

  constructor(element: HTMLCanvasElement, opts: { disabled?: boolean } = {}) {
    this.flipX = new URLSearchParams(globalThis.location?.search).has("flip")
    this.disabled = opts.disabled ?? false
    this.addHandlers(element)
    if (!/Android|iPhone/i.test(navigator.userAgent)) {
      element.contentEditable = "true"
    }
  }

  /** True while the chat input is focused, so typing there never reaches the game. */
  private chatFocused() {
    const active = document.activeElement as HTMLElement | null
    return !!active?.closest("#inputTextDiv")
  }

  keydown = (e) => {
    if (this.disabled || this.chatFocused()) return
    this.pressed[e.code] ??= performance.now()
    e.stopImmediatePropagation()
    if (e.key !== "F12") {
      e.preventDefault()
    }
  }

  keyup = (e) => {
    if (this.disabled || this.chatFocused()) return
    this.released[e.code] = performance.now() - this.pressed[e.code]
    delete this.pressed[e.code]
    e.stopImmediatePropagation()
    if (e.key !== "F12") {
      e.preventDefault()
    }
  }

  mousetouch = (e) => {
    const k = this.released
    const topHalf = e.client.y < e.rect.height / 2
    const factor = topHalf || e.ctrlKey ? 0.5 : 1
    const dx = e.dx * factor * (this.flipX ? -1 : 1)
    const dy = e.dy * 0.8
    k["movementY"] = (k["movementY"] ?? 0) + dy
    k["movementX"] = (k["movementX"] ?? 0) + dx
    if (Math.abs(k["movementX"]) > Math.abs(k["movementY"])) {
      k["movementY"] = 0
    }
  }

  private addHandlers(element: HTMLCanvasElement) {
    element.addEventListener("keydown", this.keydown)
    element.addEventListener("keyup", this.keyup)
    // A key released while focus has moved elsewhere (e.g. into the chat input)
    // never reaches this canvas — clear held state so it isn't stuck.
    element.addEventListener("blur", () => {
      this.pressed = {}
      this.released = {}
    })
    element.addEventListener("dragstart", (e) => e.preventDefault())
    element.focus()

    interact(element).draggable({
      mouseButtons: 1,
      listeners: {
        move: (e) => {
          this.mousetouch(e)
        },
      },
    })
    interact(element).gesturable({
      onmove: (e) => {
        e.dx /= 3
        this.mousetouch(e)
      },
    })
  }
}
