/*
    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/>.

*/

import { camera, data, player_controller } from "../main.ts";
import { collide_player_player, MovementBase, PLAYER_SIZE, update_movement } from "../movement.ts";
import { PacketC, PlayerClass, PlayerID } from "../protocol.ts";
import { System } from "../system.ts";
import { lerp_exp_v2_mut, V2 } from "../util.ts";
import { draw_item } from "./item.ts";
import { MessageRenderer } from "./message.ts";
import { MapLayer } from "./mod.ts";
import { SSprites } from "./sprites.ts";
import { ItemSlot, SMapTiles } from "./tiles.ts";

export interface PlayerData extends MovementBase {
    id: number,
    name: string,
    hands: ItemSlot[],
    class: PlayerClass,
    character: number,
    anim_position: V2,
    message_persist?: MessageRenderer,
    message_pinned?: MessageRenderer,
    message?: MessageRenderer,
}

export class SPlayers extends System implements MapLayer {
    players: Map<PlayerID, PlayerData> = new Map()

    constructor(
        public ss: SSprites,
        public ts: SMapTiles
    ) { super() }

    override tick(dt: number): void {
        this.players.forEach(p => update_movement(this.ts, p, dt))
        for (const [_, a] of this.players)
            for (const [_, b] of this.players)
                collide_player_player(a, b, dt)

        for (const [pid, player] of this.players) {
            if (pid == player_controller.my_id) player.anim_position.x = player.position.x, player.anim_position.y = player.position.y
            else lerp_exp_v2_mut(player.anim_position, player.position, dt * 15)
            player.hands.forEach(h => {
                if (h.item) {
                    if (h.item.tracking) lerp_exp_v2_mut(h.item, h.item.tracking, dt * 10.)
                    if (h.item.active) h.item.active.position += h.item.active.speed * dt
                }
            })
            if (player.message && player.message.tick(dt)) delete player.message
            if (player.message_persist && player.message_persist.tick(dt)) delete player.message_persist
        }
    }

    override packet(p: PacketC): void {
        if (p.type == "add_player") {
            this.players.set(p.id, {
                id: p.id,
                hands: new Array(data.hand_count).fill(null).map(() => ({})),
                position: { x: p.position[0], y: p.position[1], },
                anim_position: { x: p.position[0], y: p.position[1] },
                character: p.character.color,
                class: p.class,
                name: p.name,
                rotation: 0,
                facing: { x: 0, y: 1 },
                velocity: { x: 0, y: 0 },
                stamina: 0,
                boosting: false,
                input_boost: false,
                input_direction: { x: 0, y: 0 },
            })
        } else if (p.type == "remove_player") {
            this.players.delete(p.id)
        } else if (p.type == "movement") {
            const pl = this.players.get(p.player)!
            if (p.player == player_controller.my_id) return
            pl.position.x = p.pos[0]
            pl.position.y = p.pos[1]
            pl.rotation = p.rot
            pl.input_direction.x = p.dir[0]
            pl.input_direction.y = p.dir[1]
            pl.input_boost = p.boost
        } else if (p.type == "communicate") {
            const player = this.players.get(p.player)!
            if (p.message) {
                const message = new MessageRenderer(
                    "normal",
                    p.message,
                    player.anim_position,
                    player.message_persist?.anim_size ?? 0,
                    p.timeout ?? { initial: 5, remaining: 5, pinned: false },
                )
                if (p.timeout) player.message_persist = message
                else player.message = message
                if (p.timeout?.pinned) player.message_pinned = new MessageRenderer(
                    "pinned",
                    p.message,
                    { x: 20, y: 0 },
                    player.message_pinned?.anim_size ?? 0,
                    { ...p.timeout }
                )
            } else {
                delete player.message_persist
                delete player.message_pinned
            }
        }
    }

    draw_map_layer(ctx: CanvasRenderingContext2D): void {
        for (const [_, player] of this.players)
            draw_player(ctx, this.ss, player)
    }
}

export class SPlayersOverlay extends System implements MapLayer {
    constructor(
        public ss: SSprites,
        public ps: SPlayers,
    ) { super() }
    draw_map_layer(ctx: CanvasRenderingContext2D): void {
        for (const [_, player] of this.ps.players)
            if (player.message) player.message.draw(ctx, this.ss, true)

        for (const [_, player] of this.ps.players)
            if (player.message_persist) player.message_persist.draw(ctx, this.ss, true)

        for (const [_, player] of this.ps.players)
            draw_player_nametag(ctx, player)

    }
}

function draw_player(ctx: CanvasRenderingContext2D, sprites: SSprites, player: PlayerData) {
    ctx.save()
    ctx.translate(player.anim_position.x, player.anim_position.y)
    ctx.rotate(-player.rotation)
    if (player.boosting) ctx.scale(1.3, 1.3)
    draw_character(ctx, player.class, player.character)
    // // show moving players
    // if (length(player.input_direction) > 0.1) {
    //     ctx.fillStyle = "white"
    //     ctx.fillRect(-0.1, -0.1, 0.2, 0.2)
    // }
    ctx.restore()
    ctx.save()
    for (const h of player.hands) {
        if (h.item) draw_item(ctx, sprites, h.item)
        ctx.translate(0.2, 0.0)
    }
    ctx.restore()
}

function draw_character(ctx: CanvasRenderingContext2D, pclass: PlayerClass, character: number) {
    ctx.fillStyle = `hsl(${character}rad, 50%, 50%)`
    ctx.beginPath()
    ctx.arc(0, 0, PLAYER_SIZE, 0, Math.PI * 2)
    ctx.fill()

    if (pclass != "customer") {
        ctx.fillStyle = `hsl(${character}rad, 80%, 10%)`
        ctx.beginPath()
        ctx.arc(0, -0.2, PLAYER_SIZE, 0, Math.PI * 2)
        ctx.fill()
    }
    if (pclass != "bot") {
        ctx.fillStyle = `hsl(${character}rad, 80%, 70%)`
        ctx.beginPath()
        ctx.moveTo(-0.04, 0.25)
        ctx.lineTo(0.04, 0.25)
        ctx.lineTo(0, 0.4)
    }
    ctx.fill()
}

function draw_player_nametag(ctx: CanvasRenderingContext2D, player: PlayerData) {
    if (camera.overlay_vis_anim > 0.01 && player.name.length) {
        ctx.save()
        ctx.translate(player.anim_position.x, player.anim_position.y)
        ctx.translate(0, -1)
        ctx.textAlign = "center"
        ctx.font = "15px sans-serif"
        ctx.scale(camera.overlay_vis_anim / camera.scale, camera.overlay_vis_anim / camera.scale)
        const w = ctx.measureText(player.name).width + 20
        ctx.fillStyle = "#fffa"
        ctx.beginPath()
        ctx.roundRect(-w / 2, -10, w, 20, 5)
        ctx.fill()
        ctx.fillStyle = "black"
        ctx.textBaseline = "middle"
        ctx.fillText(player.name, 0, 0)
        ctx.restore()
    }
}
