---@class Ball
---@field x number
---@field y number
---@field z number
---@field size number
---@field heldBy Fielder | nil
---@field catchable boolean
---@field xAnimator SimpleAnimator
---@field yAnimator SimpleAnimator
---@field sizeAnimator SimpleAnimator
---@field floatAnimator SimpleAnimator
---@field private animatorLib pd_animator_lib
Ball = {}

---@param animatorLib pd_animator_lib
---@return Ball
function Ball.new(animatorLib)
    return setmetatable({
        animatorLib = animatorLib,
        x = C.Center.x --[[@as number]],
        y = C.Center.y --[[@as number]],
        z = 0,
        catchable = true,
        size = C.SmallestBallRadius,
        heldBy = nil --[[@type Runner | nil]],

        xAnimator = utils.staticAnimator(C.BallOffscreen),
        yAnimator = utils.staticAnimator(C.BallOffscreen),

        -- TODO? Replace these with a ballAnimatorZ?
        -- ...that might lose some of the magic of both. Compromise available? idk
        sizeAnimator = utils.staticAnimator(C.SmallestBallRadius),
        floatAnimator = animatorLib.new(2000, -60, 0, utils.easingHill),
    }, { __index = Ball })
end

function Ball:updatePosition()
    if self.heldBy then
        self.x = self.heldBy.x
        self.y = self.heldBy.y
        self.z = C.GloveZ
        self.size = C.SmallestBallRadius
    else
        self.x = self.xAnimator:currentValue()
        local z = self.floatAnimator:currentValue()
        -- TODO: This `+ z` is more graphics logic than physics logic
        self.y = self.yAnimator:currentValue() + z
        self.z = z
        self.size = self.sizeAnimator:currentValue()
    end
end

function Ball:markUncatchable()
    self.catchable = false
    playdate.timer.new(200, function()
        self.catchable = true
    end)
end

--- Launches the ball from its current position to the given destination.
---@param destX number
---@param destY number
---@param easingFunc EasingFunc
---@param flyTimeMs number | nil
---@param floaty boolean | nil
---@param customBallScaler pd_animator | nil
function Ball:launch(destX, destY, easingFunc, flyTimeMs, floaty, customBallScaler)
    throwMeter:reset()
    self.heldBy = nil

    -- Prevent silly insta-catches
    self:markUncatchable()

    if not flyTimeMs then
        flyTimeMs = utils.distanceBetween(self.x, self.y, destX, destY) * C.DefaultLaunchPower
    end

    if customBallScaler then
        self.sizeAnimator = customBallScaler
    else
        self.sizeAnimator = self.animatorLib.new(flyTimeMs, 9, C.SmallestBallRadius, utils.easingHill)
    end
    self.yAnimator = self.animatorLib.new(flyTimeMs, self.y, destY, easingFunc)
    self.xAnimator = self.animatorLib.new(flyTimeMs, self.x, destX, easingFunc)
    if floaty then
        self.floatAnimator:reset(flyTimeMs)
    end
end
