/// 
import { player_movement_update } from "./movement.ts";
import { Gamedata, ItemIndex, Message, PacketC, PacketS, PlayerID, TileIndex } from "./protocol.ts";
import { V2, add_v2, lerp_exp_v2_mut, normalize, lerp_exp, sub_v2, length } from "./util.ts";
import { draw_ingame, draw_wait } from "./visual.ts";
export let ctx: CanvasRenderingContext2D;
export let canvas: HTMLCanvasElement;
let ws: WebSocket;
document.addEventListener("DOMContentLoaded", () => {
    const ws_uri = window.location.protocol.endsWith("s:")
        ? `wss://${window.location.host}/`
        : `ws://${window.location.hostname}:27032/`
    ws = new WebSocket(ws_uri)
    ws.onerror = console.error
    ws.onmessage = m => {
        packet(JSON.parse(m.data) as PacketC);
    }
    ws.onclose = () => console.log("close")
    ws.onopen = () => {
        console.log("open")
        send({ type: "join", name: "test", character: Math.floor(Math.random() * 255) })
    }
    canvas = document.createElement("canvas");
    document.body.append(canvas)
    ctx = canvas.getContext("2d")!
    resize()
    globalThis.addEventListener("resize", resize)
    draw()
    document.addEventListener("keydown", ev => keyboard(ev, true))
    document.addEventListener("keyup", ev => keyboard(ev, false))
    document.addEventListener("contextmenu", ev => ev.preventDefault())
    setInterval(tick_update, 1000 / 25);
})
export interface ItemData {
    kind: ItemIndex,
    x: number,
    y: number,
    tracking?: V2,
    progress?: number
    progress_warn?: boolean
    remove_anim?: number
}
export interface PlayerData {
    x: number,
    y: number,
    name: string,
    rot: number,
    item?: ItemData,
    facing: V2,
    character: number,
    anim_position: V2,
    vel: V2,
    message?: MessageData,
}
export interface TileData {
    x: number
    y: number
    kind: TileIndex
    item?: ItemData
}
export interface MessageData {
    inner: Message
    anim_position: V2,
    anim_size: number,
}
export const players = new Map()
export const tiles = new Map()
export const items_removed = new Set()
export let data: Gamedata = { item_names: [], tile_names: [], spawn: [0, 0], tile_collide: [], tile_interact: [] }
export let my_id: PlayerID = -1
export const camera: V2 = { x: 0, y: 0 }
export const interact_target_anim: V2 = { x: 0, y: 0 }
export let interact_possible_anim: number = 0
export let interact_active_anim: number = 0
let interacting: V2 | undefined;
function send(p: PacketS) { ws.send(JSON.stringify(p)) }
function packet(p: PacketC) {
    if (!["position", "set_active", "update_map"].includes(p.type))
        console.log(p);
    switch (p.type) {
        case "init":
            my_id = p.id
            data = p.data
            break;
        case "add_player": {
            players.set(p.id, {
                x: p.position[0],
                y: p.position[1],
                character: p.character,
                name: p.name,
                rot: 0,
                anim_position: { x: 0, y: 1 },
                facing: { x: 0, y: 1 },
                vel: { x: 0, y: 0 },
            })
            break;
        }
        case "remove_player":
            players.delete(p.id)
            break;
        case "position": {
            const pl = players.get(p.player)!
            const pos = { x: p.pos[0], y: p.pos[1] }
            const dist = length(sub_v2(pl, pos));
            if (p.player == my_id && dist < 3) return; // we know better where we are
            pl.x = pos.x
            pl.y = pos.y
            pl.rot = p.rot
            break;
        }
        case "take_item": {
            const player = players.get(p.player)!
            const tile = tiles.get(p.tile.toString())!
            player.item = tile.item;
            player.item!.tracking = player
            tile.item = undefined
            break;
        }
        case "put_item": {
            const player = players.get(p.player)!
            const tile = tiles.get(p.tile.toString())!
            tile.item = player.item
            tile.item!.tracking = add_v2(tile, 0.5)
            player.item = undefined
            break;
        }
        case "set_tile_item": {
            const tile = tiles.get(p.tile.toString())!
            if (tile.item !== undefined && tile.item !== null) items_removed.add(tile.item)
            tile.item = undefined
            if (p.item !== undefined && p.item !== null) tile.item = { kind: p.item, x: p.tile[0] + 0.5, y: p.tile[1] + 0.5 }
            break;
        }
        case "set_player_item": {
            const player = players.get(p.player)!
            if (player.item !== undefined && player.item !== null) items_removed.add(player.item)
            player.item = undefined
            if (p.item !== undefined && p.item !== null) player.item = { kind: p.item, x: player.x + 0.5, y: player.y + 0.5 }
            break;
        }
        case "set_active": {
            const item = tiles.get(p.tile.toString())!.item!;
            item.progress = p.progress
            item.progress_warn = p.warn
            break;
        }
        case "update_map":
            tiles.set(p.pos.toString(), { x: p.pos[0], y: p.pos[1], kind: p.tile })
            break;
        case "communicate": {
            const player = players.get(p.player)!
            if (p.message) player.message = { inner: p.message, anim_size: 0., anim_position: player.anim_position }
            else player.message = undefined
            break;
        }
        case "error":
            console.warn(p.message)
            break;
        default:
            console.warn("unknown packet", p);
    }
}
export const keys_down = new Set();
const HANDLED_KEYS = ["KeyW", "KeyA", "KeyS", "KeyD", "Space"]
function keyboard(ev: KeyboardEvent, down: boolean) {
    if (HANDLED_KEYS.includes(ev.code)) ev.preventDefault()
    if (!keys_down.has("Space") && ev.code == "Space" && down) set_interact(true)
    if (keys_down.has("Space") && ev.code == "Space" && !down) set_interact(false)
    if (down) keys_down.add(ev.code)
    else keys_down.delete(ev.code)
}
export function get_interact_target(): V2 | undefined {
    if (interacting) return interacting
    const me = players.get(my_id)
    if (!me) return
    return {
        x: Math.floor(me.x + Math.sin(me.rot)),
        y: Math.floor(me.y + Math.cos(me.rot))
    }
}
function set_interact(edge: boolean) {
    if (edge) interacting = get_interact_target()
    if (interacting) send({ type: "interact", pos: [interacting.x, interacting.y], edge })
    if (!edge) interacting = undefined
}
function tick_update() {
    const p = players.get(my_id)
    if (!p) return
    send({ type: "position", pos: [p.x, p.y], rot: p.rot })
}
function frame_update(dt: number) {
    const p = players.get(my_id)
    if (!p) return
    const input = normalize({
        x: (+keys_down.has("KeyD") - +keys_down.has("KeyA")),
        y: (+keys_down.has("KeyS") - +keys_down.has("KeyW"))
    })
    if (interacting) input.x *= 0, input.y *= 0
    player_movement_update(p, dt, input)
    const update_item = (item: ItemData) => {
        if (item.tracking) lerp_exp_v2_mut(item, item.tracking, dt * 10.)
    }
    for (const [pid, player] of players) {
        if (pid == my_id) player.anim_position.x = player.x, player.anim_position.y = player.y
        else lerp_exp_v2_mut(player.anim_position, player, dt * 15)
        if (player.item !== undefined && player.item !== null) update_item(player.item)
        if (player.message) player.message.anim_size = lerp_exp(player.message.anim_size, 1, dt * 3)
    }
    for (const [_, tile] of tiles) {
        if (tile.item !== undefined && tile.item !== null) update_item(tile.item)
    }
    const remove = []
    for (const item of items_removed) {
        update_item(item)
        if (item.remove_anim === undefined) item.remove_anim = 0
        item.remove_anim += dt * 4.
        if (item.remove_anim > 1.) remove.push(item)
    }
    remove.forEach(i => items_removed.delete(i))
    lerp_exp_v2_mut(camera, p, dt * 10.)
    const it = get_interact_target() ?? { x: 0, y: 0 };
    const possible = data.tile_interact[tiles.get([it.x, it.y].toString())?.kind ?? 0] ?? false
    lerp_exp_v2_mut(interact_target_anim, it, dt * 15.)
    interact_possible_anim = lerp_exp(interact_possible_anim, +possible, dt * 18.)
    interact_active_anim = lerp_exp(interact_active_anim, +!!interacting, dt * 15.)
}
function resize() {
    canvas.width = globalThis.innerWidth
    canvas.height = globalThis.innerHeight
}
let last_frame = performance.now()
function draw() {
    const now = performance.now()
    frame_update((now - last_frame) / 1000)
    last_frame = now;
    if (ws.readyState == ws.CONNECTING) draw_wait("Connecting...")
    else if (ws.readyState == ws.CLOSING) draw_wait("Closing...")
    else if (ws.readyState == ws.CLOSED) draw_wait("Disconnected")
    else if (ws.readyState == ws.OPEN) draw_ingame()
    else throw new Error(`ws state invalid`);
    requestAnimationFrame(draw)
}