local wait = require("src/wait")

-- Define the animation handler entry points.
local animationHandler = {
    config = {},
    -- Define the animations playing.
    playing = {
        main = {},
        loop = {},
    },
    id = {},
    disabled = {},
    items = {},
}

-- Define the animation conditions temp values.
local animationTemps = {
    afkTiming = nil,
    afk = false,
    oldFlying = false,
    flying = false,
    isWet = false,
}

-- Run on init.
function events.entity_init()
    -- Run the idle animation.
    -- Check if the animation has an action defined.
    if animationHandler.config.idle then
        animationHandler.config.idle[1].start:play()
    end
end

-- Runs every tick.
function events.TICK()
    -- Get the values of the direction the player is moving.
    local pv = player:getVelocity():mul(1, 0, 1):normalize()
    local pl = models:partToWorldMatrix():applyDir(0, 0, -1):mul(1, 0, 1):normalize()
    local fwd = pv:dot(pl)

    -- Define the poses that can be used to execute animations.
    local actionTypes = {
        none = {
            condition = player:getPose() == "STANDING" and player:getVelocity().xyz:length() < .01,
            intensity = 1,
        },
        walking = {
            condition = player:getVelocity().xz:length() > .01 and player:isSprinting() == false and
                player:getPose() ~= "CROUCHING",
            intensity = 1 + player:getVelocity().xz:length(),
        },
        walking_forward = {
            condition = player:getVelocity().xz:length() > .01 and player:isSprinting() == false and
                player:getPose() ~= "CROUCHING" and
                not (fwd < -.8),
            intensity = 1 + player:getVelocity().xz:length(),
        },
        walking_backward = {
            condition = player:getVelocity().xz:length() > .01 and player:isSprinting() == false and
                player:getPose() ~= "CROUCHING" and
                fwd < -.8,
            intensity = 1 + player:getVelocity().xz:length(),
        },
        sprinting = {
            condition = player:isSprinting() and not animationTemps.flying,
            intensity = player:getVelocity().xz:length(),
        },
        crouching = {
            condition = player:getPose() == "CROUCHING",
            intensity = 1,
        },
        fall_flying = {
            condition = player:getPose() == "FALL_FLYING",
            intensity = 1,
        },
        sleeping = {
            condition = player:getPose() == "SLEEPING",
            intensity = 1,
        },
        swimming = {
            condition = player:getPose() == "SWIMMING",
            intensity = 1,
        },
        riptide = {
            condition = player:getPose() == "SPIN_ATTACK",
            intensity = 1,
        },
        dying = {
            condition = player:getPose() == "DYING",
            intensity = 1,
        },
        falling = {
            condition = player:getVelocity().y < -0.6,
            intensity = math.abs(player:getVelocity().y),
        },
        jumping = {
            condition = player:getVelocity().y > 0,
            intensity = 1 + player:getVelocity().y,
        },
        afk = {
            condition = animationTemps.afk,
            intensity = 1,
        },
        sitting = {
            condition = player:getVehicle(),
            intensity = 1,
        },
        flying = {
            condition = animationTemps.flying,
            intensity = 1,
        },
        flying_forward = {
            condition = player:getVelocity().xz:length() > .01 and animationTemps.flying and
                not (fwd < -.8),
            intensity = 1,
        },
        flying_backward = {
            condition = player:getVelocity().xz:length() > .01 and animationTemps.flying and
                fwd < -.8,
            intensity = 1,
        },
    }
    -- Loop through the events and make the defined animations play upon events.
    for key, details in pairs(actionTypes) do
        if not animationHandler.disabled[key] then
            -- Check if the animation has an action defined.
            if animationHandler.config[key] then
                -- Choose a random animation to play.
                local animationID = math.random(1, #animationHandler.config[key])
                -- Adjust the speed of the animation.
                animationHandler.config[key][animationID].start:setSpeed(details.intensity)
                -- If the animation is playing then play it and log that it's playing, if it's not then stop it.
                if details.condition then
                    -- If the animation isn't playing then play it.
                    if animationHandler.playing.main[key] ~= true then
                        -- Log that the animation is playing.
                        animationHandler.playing.main[key] = true
                        -- Log the animation id.
                        animationHandler.id[key] = animationID
                        -- Play the animation.
                        animationHandler.config[key][animationID].start:play()

                        -- If a stop animation is defined then stop it, this prevents the stop animation from not playing later.
                        if animationHandler.config[key][animationHandler.id[key]].stop then
                            animationHandler.config[key][animationHandler.id[key]].stop:stop()
                        end
                    else
                        -- Check if a loop animation is defined.
                        if animationHandler.config[key][animationID].loop then
                            -- Adjust the speed of the animation.
                            animationHandler.config[key][animationID].loop:setSpeed(details
                                .intensity)
                            -- If the start animation is done playing then start looping.
                            if not animationHandler.config[key][animationID].start:isPlaying() and animationHandler.playing.loop[key] ~= true then
                                -- Stop the start animation.
                                animationHandler.config[key][animationHandler.id[key]].start:stop()
                                -- Play the loop animation and log it as played.
                                animationHandler.playing.loop[key] = true
                                animationHandler.config[key][animationID].loop:play()
                            end
                        end
                    end
                else
                    -- If the animation is playing then run the stop animation logic.
                    if animationHandler.playing.main[key] == true then
                        -- If a stop animation is defined then play it.
                        if animationHandler.config[key][animationHandler.id[key]].stop then
                            animationHandler.config[key][animationHandler.id[key]].stop:play()
                        end
                        -- Stop the animations.
                        animationHandler.playing.main[key] = false
                        animationHandler.config[key][animationHandler.id[key]].start:stop()
                        if animationHandler.config[key][animationID].loop then
                            animationHandler.playing.loop[key] = false
                            animationHandler.config[key][animationHandler.id[key]].loop:stop()
                        end
                        -- Remove the animation id.
                        animationHandler.id[key] = nil
                    end
                end
            end
        end
    end

    -- AFK system.
    if player:getVelocity().xyz:length() < .01 and next(actionHandler.currentAnimations) == nil then
        if animationTemps.afkTiming == nil then
            -- Set afkTiming to true if the player isn't moving.
            animationTemps.afkTiming = wait.schedule(1200, goAFK)
        end
    else
        if animationTemps.afkTiming ~= nil then
            -- Cancel the event.
            wait.cancel(animationTemps.afkTiming)
            -- Set afkTiming to false if the player is moving.
            animationTemps.afkTiming = nil
            -- Set afking to false.
            animationTemps.afk = false
            -- Start back the idle animation.
            if animationHandler.config.idle then
                animationHandler.config.idle[1].start:play()
            end
        end
    end

    -- Flying system.
    if host:isHost() then
        -- If the flying status changed then reping.
        if host:isFlying() ~= animationTemps.oldFlying then
            pings.fly(host:isFlying())
        end
        -- Update the flying status.
        animationTemps.oldFlying = host:isFlying()
    end

    -- Wet system.
    if animationHandler.config.wet then
        -- Check if the player is currently wet.
        if animationTemps.isWet == true then
            -- If the player is no longer wet but was recently, start a wait timer.
            if not player:isWet() then
                if not animationTemps.wetCooldownEvent then
                    -- Schedule the animation to play after 10 ticks (example: 0.5 seconds).
                    animationTemps.wetCooldownEvent = wait.schedule(10, function()
                        -- Play the animation after the cooldown has ended.
                        animationHandler.config["wet"][1].start:play()
                        -- Set isWet to false, meaning the player is no longer wet.
                        animationTemps.isWet = false
                        -- Clear the event.
                        animationTemps.wetCooldownEvent = nil
                    end)
                end
            else
                -- If the player re-enters the water, cancel the scheduled animation and reset.
                if animationTemps.wetCooldownEvent then
                    wait.cancel(animationTemps.wetCooldownEvent)
                    animationTemps.wetCooldownEvent = nil
                end
            end
        else
            -- If the player is wet, set the isWet flag.
            if player:isWet() then
                animationTemps.isWet = true
            end
        end
    end
end

-- AFK activation function.
function goAFK()
    animationTemps.afk = true
    -- Disable the idle animation.
    if animationHandler.config.idle then
        animationHandler.config.idle[1].start:stop()
    end
end

-- Flying activation system.
function pings.fly(value)
    animationTemps.flying = value;
end

-- Eye movement system.
function animationHandler.setEye(eye, leftLimit, rightLimit, upLimit, downLimit)
    local left = leftLimit or 0
    local right = rightLimit or 0
    local up = upLimit or 0
    local down = downLimit or 0

    function events.render()
        local rot = (vanilla_model.HEAD:getOriginRot() + 180) % 360 - 180
        if rot[2] > 50 then rot[2] = 50 end
        if rot[2] < -50 then rot[2] = -50 end
        local x = -rot[2] / 60
        local y = rot[1] / 120
        -- Make the eyes snap.
        if false then
            x = math.round(x / 1.6)
            y = math.round(y / 1.7)
        end

        if x > left then x = left end
        if x < -right then x = -right end
        if y > up then y = up end
        if y < -down then y = -down end

        -- Make the eyes snap.
        if false then
            eye:setPos(x, 0, 0)
        else
            eye:setPos(x, y, 0)
        end
    end
end

-- Item model handler.
function animationHandler.items.add(itemList)
    -- Add the render event for each item.
    for key, value in pairs(itemList) do
        function events.item_render(item)
            if item:getName() == key then
                return value
            end
        end
    end
end

-- Export the entry points.
return animationHandler
