/*
    Hurry Curry! - a game about cooking
    Copyright (C) 2026 Hurry Curry! Contributors

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, version 3 of the License only.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

*/
/// <reference lib="dom" />

import { SChat } from "./chat.ts";
import { SCamera } from "./map/camera.ts";
import { SDebugEvents } from "./map/debug_events.ts";
import { SMapItems } from "./map/item.ts";
import { SPlayers, SPlayersOverlay } from "./map/players.ts";
import { SServerHints } from "./map/server_hints.ts";
import { SMapTiles } from "./map/tiles.ts";
import { SParticles } from "./map/particles.ts";
import { SPlayerController } from "./player_controller.ts";
import { Gamedata, PacketC, PacketS } from "./protocol.ts";
import { SQuickCommands } from "./quick_commands.ts";
import { System } from "./system.ts";
import { SVote } from "./vote.ts";
import { SServerMessage } from "./server_message.ts";
import { SScoreDisplay } from "./score_display.ts";
import { SPinnedMessages } from "./pinned_messages.ts";
import { GridLayer } from "./map/mod.ts";
import { SConnectScreen } from "./connect_screen.ts";
import { SAnnouncement } from "./announcement.ts";
import { SSprites } from "./map/sprites.ts";
import { SEffect } from "./map/effect.ts";
import { init_locale } from "./locale.ts";

let ctx: CanvasRenderingContext2D;
export let canvas: HTMLCanvasElement;
export let ws: WebSocket;
document.addEventListener("DOMContentLoaded", async () => {
    await init_locale()
    const ws_uri = globalThis.location.protocol.endsWith("s:")
        ? `wss://${globalThis.location.host}/`
        : `ws://${globalThis.location.hostname}:27032/`

    const open = () => {
        console.log("open")
        send({ type: "join", name: "test", character: { color: Math.floor(Math.random() * 100), hairstyle: 0, headwear: 0 }, class: "chef" })
        keepalive_interval = setInterval(() => {
            send({ type: "keepalive" })
        }, 1000)
    }
    const resize = () => {
        canvas.width = globalThis.innerWidth
        canvas.height = globalThis.innerHeight
    }

    ws = new WebSocket(ws_uri)
    let keepalive_interval: number
    canvas = document.createElement("canvas");
    ctx = canvas.getContext("2d")!
    resize()
    draw()

    ws.addEventListener("open", open)
    ws.addEventListener("error", console.error)
    ws.addEventListener("message", m => packet(JSON.parse(m.data) as PacketC))
    ws.addEventListener("close", () => clearInterval(keepalive_interval))
    globalThis.addEventListener("resize", resize)
    document.addEventListener("keydown", ev => keyboard(ev, true))
    document.addEventListener("keyup", ev => keyboard(ev, false))
    canvas.addEventListener("contextmenu", ev => ev.preventDefault())
    document.body.append(canvas)
})

export let data: Gamedata = { item_names: [], tile_names: [], tile_interactable_empty: [], tile_placeable_any: [], tile_placeable_items: {}, hand_count: 0, metadata: { demand_items: [], difficulty: 0, display_name: "", name: "", players: 0, hand_count: 0 }, tile_collide: [] }

const sprites = new SSprites()
const vote = new SVote()
const chat = new SChat()
const map_tiles = new SMapTiles(sprites)
const players = new SPlayers(sprites, map_tiles)
const players_overlay = new SPlayersOverlay(sprites, players)
const map_items = new SMapItems(sprites, map_tiles, players)
const quick_commands = new SQuickCommands()
const server_hints = new SServerHints(sprites, players)
export const player_controller = new SPlayerController(map_tiles, players)
const particles = new SParticles()
const server_message = new SServerMessage()
const score_display = new SScoreDisplay()
const pinned_messages = new SPinnedMessages(sprites, players)
const connect_screen = new SConnectScreen()
export const camera = new SCamera(player_controller)
const announcement = new SAnnouncement()
const effect = new SEffect(map_items)
const debug_events = new SDebugEvents(map_items)

const systems: System[] = [
    vote, chat, debug_events, server_hints, map_tiles,
    map_items, quick_commands, players, player_controller,
    camera, particles, server_message, score_display,
    pinned_messages, connect_screen, players_overlay,
    announcement, sprites, effect,
]
const map_layers = [
    new GridLayer(),
    map_tiles,
    players,
    map_items,
    particles,
    player_controller,
    server_hints,
    players_overlay,
    effect,
    debug_events,
]
const ui_layers = [
    pinned_messages,
    server_message,
    score_display,
    announcement,
    vote,
    connect_screen,
]

export function send(p: PacketS) {
    if (p.type != "movement" && p.type != "keepalive") console.log("send", p);
    ws.send(JSON.stringify(p))
}

function packet(p: PacketC) {
    if (!["movement", "debug"].includes(p.type))
        console.log(p);

    if (p.type == "version") console.log(`Protocol version: ${p.major}.${p.minor}`)
    else if (p.type == "game_data") data = p
    else if (p.type == "set_ingame" && p.state) send({ type: "ready" })

    for (const s of systems)
        s.packet(p)
}

export const keys_down = new Set();
function keyboard(ev: KeyboardEvent, down: boolean) {
    if (document.activeElement instanceof HTMLInputElement) return
    for (const s of systems)
        if (s.input(ev, down)) {
            ev.preventDefault()
            break
        }

    if (down) keys_down.add(ev.code)
    else keys_down.delete(ev.code)
}

let last_frame = performance.now()
function draw() {
    const now = performance.now()
    let dt = (now - last_frame) / 1000
    while (dt > 0) { // subdivide into multiple ticks of at most 40ms
        const step_dt = Math.min(0.04, dt)
        for (const s of systems) s.tick(step_dt)
        dt -= step_dt
    }

    ctx.fillStyle = "#111"
    ctx.fillRect(0, 0, canvas.width, canvas.height)
    ctx.save()
    camera.apply_map_transform(ctx)
    for (const l of map_layers) l.draw_map_layer(ctx)
    ctx.restore()
    for (const l of ui_layers) l.draw(ctx)

    last_frame = now;
    requestAnimationFrame(draw)
}
