import type { GameStartMessage } from "../shared/types"
import { GameContext, type InputState } from "../shared/game"
import { focusInput } from "./focus"
import { $$ } from "./dom"
import { randomId, importUrl } from "../shared/utils"
import { setStatus, addOutput, insert } from "./scrollback"
import { sessionId } from "./session"
import { mode } from "./commands"

const FPS = 30
const HEIGHT = 540
const WIDTH = 960

type Game = { init?: () => void, update?: (delta: number, input: InputState) => void, draw?: (ctx: GameContext) => void }

let oldMode = "cinema"
let running = false
let canvas: HTMLCanvasElement
const pressedStack = new Set<string>()

let pressed: InputState = {
  key: "",
  shift: false,
  ctrl: false,
  meta: false,
  pressed: pressedStack,
  prevPressed: new Set(),
  justPressed: new Set(),
  justReleased: new Set(),
}

export async function handleGameStart(msg: GameStartMessage) {
  const msgId = msg.id as string
  const name = msg.data as string

  let game
  try {
    game = await importUrl(`/source/${name}?session=${sessionId}}`)
  } catch (err: any) {
    setStatus(msgId, "error")
    addOutput(msgId, `Error: ${err.message ? err.message : err}`)
    return
  }

  if (document.body.dataset.mode === "tall") {
    mode()
    oldMode = "tall"
  }

  canvas = createCanvas()
  canvas.focus()
  setStatus(msgId, "ok")
  window.addEventListener("keydown", handleKeydown)
  window.addEventListener("keyup", handleKeyup)
  window.addEventListener("resize", resizeCanvas)
  resizeCanvas()
  gameLoop(new GameContext(canvas.getContext("2d")!), game)
}

function createCanvas(): HTMLCanvasElement {
  const canvas = $$("canvas.game.active") as HTMLCanvasElement
  canvas.id = randomId()
  canvas.height = HEIGHT
  canvas.width = WIDTH
  canvas.tabIndex = 0

  const main = document.querySelector("main")
  main?.classList.add("game")
  main?.parentNode?.insertBefore(canvas, main)

  return canvas
}

function handleKeydown(e: KeyboardEvent) {
  e.preventDefault()

  if (e.key === "Escape" || (e.ctrlKey && e.key === "c")) {
    endGame()
  } else {
    pressedStack.add(e.key)
    pressed.key = e.key
    pressed.ctrl = e.ctrlKey
    pressed.shift = e.shiftKey
    pressed.meta = e.metaKey
  }
}

function handleKeyup(e: KeyboardEvent) {
  pressedStack.delete(e.key)
  if (pressedStack.size === 0) {
    pressed.key = ""
    pressed.ctrl = false
    pressed.shift = false
    pressed.meta = false
  }
}

function updateInputState() {
  pressed.justPressed = new Set([...pressed.pressed].filter(k => !pressed.prevPressed.has(k)))
  pressed.justReleased = new Set([...pressed.prevPressed].filter(k => !pressed.pressed.has(k)))
  pressed.prevPressed = new Set(pressed.pressed)
}

function resizeCanvas() {
  const scale = Math.min(
    window.innerWidth / 960,
    window.innerHeight / 540
  )

  canvas.width = 960 * scale
  canvas.height = 540 * scale

  const ctx = canvas.getContext("2d")!
  ctx.setTransform(scale, 0, 0, scale, 0, 0)
}

function gameLoop(ctx: GameContext, game: Game) {
  running = true
  let last = 0

  if (game.init) game.init()

  function loop(ts: number) {
    if (!running) return

    const delta = ts - last
    if (delta >= 1000 / FPS) {
      updateInputState()
      if (game.update) game.update(delta, pressed)
      if (game.draw) game.draw(ctx)
      last = ts
    }
    requestAnimationFrame(loop)
  }

  requestAnimationFrame(loop)
}

function endGame() {
  running = false

  window.removeEventListener("keydown", handleKeydown)
  window.removeEventListener("keyup", handleKeyup)
  window.removeEventListener("resize", resizeCanvas)

  if (oldMode === "tall") mode()

  const main = document.querySelector("main")
  main?.classList.remove("game")

  canvas.classList.remove("active")
  canvas.style.height = HEIGHT / 2 + "px"
  canvas.style.width = WIDTH / 2 + "px"

  const output = $$("li.output")
  output.append(canvas)
  insert(output)

  focusInput()
}