/*
    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 { data, keys_down, send } from "./main.ts";
import { MapLayer } from "./map/mod.ts";
import { PlayerData, SPlayers } from "./map/players.ts";
import { ItemSlot, SMapTiles, TileData } from "./map/tiles.ts";
import { PacketC } from "./protocol.ts";
import { System } from "./system.ts";
import { lerp_exp, lerp_exp_v2_mut, normalize, V2 } from "./util.ts";

const KEY_INTERACT_LEFT = "KeyJ"
const KEY_INTERACT_RIGHT = "KeyL"
const KEY_BOOST = "KeyK"
const KEY_UP = "KeyW"
const KEY_DOWN = "KeyS"
const KEY_LEFT = "KeyA"
const KEY_RIGHT = "KeyD"

export class SPlayerController extends System implements MapLayer {
    my_id?: number = undefined
    last_direction: V2 = { x: 0, y: 0 }
    last_boost: boolean = false
    interacting?: V2;
    last_server_sent_position: V2 = { x: 0, y: 0 }
    paused = false

    interact_target_anim: V2 = { x: 0, y: 0 }
    interact_possible_anim: number = 0
    interact_active_anim: number = 0

    constructor(
        public ts: SMapTiles,
        public ps: SPlayers
    ) { super() }

    override input(ev: KeyboardEvent, down: boolean): boolean {
        if (!keys_down.has(KEY_INTERACT_LEFT) && ev.code == KEY_INTERACT_LEFT && down) this.set_interact(true, 0)
        else if (keys_down.has(KEY_INTERACT_LEFT) && ev.code == KEY_INTERACT_LEFT && !down) this.set_interact(false, 0)
        else if (!keys_down.has(KEY_INTERACT_RIGHT) && ev.code == KEY_INTERACT_RIGHT && down) this.set_interact(true, 1)
        else if (keys_down.has(KEY_INTERACT_RIGHT) && ev.code == KEY_INTERACT_RIGHT && !down) this.set_interact(false, 1)
        else return false
        return true
    }
    override packet(p: PacketC): void {
        if (p.type == "joined") {
            this.my_id = p.id
        } else if (p.type == "movement" && p.player == this.my_id && p.sync) {
            const me = this.ps.players.get(this.my_id!)
            if (me) { // ensuring object has same ref
                me.position.x = p.pos[0]
                me.position.y = p.pos[1]
            }
        } else if (p.type == "pause") {
            this.paused = p.state
        }
    }

    override tick(dt: number): void {
        if (this.my_id === undefined) return
        const p = this.ps.players.get(this.my_id)
        if (!p) return

        const direction = normalize({
            x: (+keys_down.has(KEY_RIGHT) - +keys_down.has(KEY_LEFT)),
            y: (+keys_down.has(KEY_DOWN) - +keys_down.has(KEY_UP))
        })
        if (this.interacting || this.paused) direction.x *= 0, direction.y *= 0
        p.input_direction = direction
        p.input_boost = keys_down.has(KEY_BOOST)
        if (this.last_boost != p.input_boost || this.last_direction.x != p.input_direction.x || this.last_direction.y != p.input_direction.y)
            this.send_movement(p)
        this.last_direction = p.input_direction
        this.last_boost = p.input_boost


        const it = this.get_interact_target() ?? { x: 0, y: 0 };
        const target_tile = this.ts.tiles.get([it.x, it.y].toString())
        const me = this.ps.players.get(this.my_id!)
        const possible = target_tile && me && me.hands.find(h => can_interact_with_tile(target_tile, h))
        lerp_exp_v2_mut(this.interact_target_anim, it, dt * 15.)
        this.interact_possible_anim = lerp_exp(this.interact_possible_anim, +!!possible, dt * 18.)
        this.interact_active_anim = lerp_exp(this.interact_active_anim, +!!this.interacting, dt * 15.)
    }

    get_interact_target(): V2 | undefined {
        if (this.interacting) return this.interacting
        const me = this.ps.players.get(this.my_id!)
        if (!me) return

        if (location.hash.search("oldinteract") != -1)
            return {
                x: Math.floor(me.position.x + Math.sin(me.rotation)),
                y: Math.floor(me.position.y + Math.cos(me.rotation))
            }

        const rx = me.position.x + Math.sin(me.rotation) * 0.7
        const ry = me.position.y + Math.cos(me.rotation) * 0.7
        const bx = Math.floor(rx)
        const by = Math.floor(ry)
        let best = { x: bx, y: by }
        let best_d = 100
        for (let ox = -1; ox <= 1; ox++) for (let oy = -1; oy <= 1; oy++) {
            const t = this.ts.tiles.get([bx + ox, by + oy] + "")
            if (!t) continue
            for (const hand of me.hands) {
                if (!can_interact_with_tile(t, hand)) continue
                const cx = (bx + ox + 0.5)
                const cy = (by + oy + 0.5)
                const dx = rx - cx
                const dy = ry - cy
                const pdx = me.position.x - cx
                const pdy = me.position.y - cy
                const d = Math.sqrt(dx * dx + dy * dy)
                const pd = Math.sqrt(pdx * pdx + pdy * pdy)
                if (pd < 2 && d < best_d) {
                    best_d = d
                    best = { x: bx + ox, y: by + oy }
                }
            }
        }
        return best
    }

    send_movement(p: PlayerData) {
        send({ player: this.my_id!, type: "movement", pos: [p.position.x, p.position.y], dir: [p.input_direction.x, p.input_direction.y], boost: p.input_boost })
    }

    set_interact(edge: boolean, hand: number) {
        if (edge) this.interacting = this.get_interact_target()
        if (this.interacting) send({ player: this.my_id!, type: "interact", target: edge ? { tile: [this.interacting.x, this.interacting.y] } : undefined, hand })
        if (!edge) this.interacting = undefined
    }

    draw_map_layer(ctx: CanvasRenderingContext2D): void {
        ctx.save()
        ctx.translate(this.interact_target_anim.x, this.interact_target_anim.y)
        ctx.lineCap = "round"
        ctx.lineJoin = "round"
        ctx.lineWidth = 0.06 + 0.03 * Math.sin(Date.now() / 100) * this.interact_possible_anim
        ctx.strokeStyle = `hsla(${(1 - this.interact_active_anim) * 225 + this.interact_active_anim * 125}deg, ${this.interact_possible_anim * 100}%, 62.70%, ${this.interact_possible_anim * 0.7 + 0.3})`
        ctx.strokeRect(0, 0, 1, 1)
        ctx.restore()
    }
}

function can_interact_with_tile(t: TileData, hand: ItemSlot) {
    if (t.item) return true
    let can_interact = false
    for (const part of t.parts) {
        if (hand.item && data.tile_placeable_items[part])
            can_interact = data.tile_placeable_items[part].includes(hand.item.kind)
        if (hand.item && data.tile_placeable_any.includes(part))
            can_interact = true
        if (!hand.item && data.tile_interactable_empty.includes(part))
            can_interact = true
    }
    return can_interact
}
