local npcBatDeg = 0
local BaseNpcBatSpeed <const> = 1500
local npcBatSpeed = 1500

-- selene: allow(unscoped_variables)
npc = {}

---@param ball XyPair
---@param catcherThrownBall boolean
---@param deltaSec number
---@return number
function npc.updateBatAngle(ball, catcherThrownBall, deltaSec)
    if not catcherThrownBall and ball.y > 200 and ball.y < 230 and (ball.x < C.Center.x + 15) then
        npcBatDeg = npcBatDeg + (deltaSec * npcBatSpeed)
    else
        npcBatSpeed = (1 + math.random()) * BaseNpcBatSpeed
        npcBatDeg = 200
    end
    return npcBatDeg
end

function npc.batSpeed()
    return npcBatSpeed
end

---@param runners Runner[]
---@return number
function npc.runningSpeed(runners)
    if #runners == 0 then
        return 0
    end
    local touchedBase = utils.isTouchingBase(runners[1].x, runners[1].y)
    if not touchedBase or touchedBase == C.Bases[C.Home] then
        return 10
    end
    return 0
end

---@param runners Runner[]
---@return Base[]
local function getForcedOutTargets(runners)
    local targets = {}
    for _, base in ipairs(C.Bases) do
        local runnerTargetingBase = utils.getRunnerWithNextBase(runners, base)
        if runnerTargetingBase then
            targets[#targets + 1] = base
        else
            return targets
        end
    end
    return targets
end

--- Returns the position,distance of the base closest to the runner who is *furthest* from a base
---@param runners Runner[]
---@return Base | nil, number | nil
local function getBaseOfStrandedRunner(runners)
    local farRunnersBase, farDistance
    for _, runner in pairs(runners) do
        --if runner ~= batter then
        local nearestBase, distance = utils.getNearestOf(C.Bases, runner.x, runner.y)
        if farRunnersBase == nil or farDistance < distance then
            farRunnersBase = nearestBase
            farDistance = distance
        end
        --end
    end

    return farRunnersBase, farDistance
end

--- Returns x,y of the out target
---@param runners Runner[]
---@return number|nil, number|nil
function npc.getNextOutTarget(runners)
    -- TODO: Handle missed throws, check for fielders at target, etc.
    local targets = getForcedOutTargets(runners)
    if #targets ~= 0 then
        return targets[#targets].x, targets[#targets].y
    end

    local baseCloseToStrandedRunner = getBaseOfStrandedRunner(runners)
    if baseCloseToStrandedRunner then
        return baseCloseToStrandedRunner.x, baseCloseToStrandedRunner.y
    end
end

---@param fielding Fielding
---@param fielder Fielder
---@param runners Runner[]
---@param launchBall LaunchBall
function npc.tryToMakeAPlay(fielding, fielder, runners, ball, launchBall)
    local targetX, targetY = npc.getNextOutTarget(runners)
    if targetX ~= nil and targetY ~= nil then
        local nearestFielder = utils.getNearestOf(fielding.fielders, targetX, targetY)
        nearestFielder.target = utils.xy(targetX, targetY)
        if nearestFielder == fielder then
            ball.heldBy = fielder
        else
            launchBall(targetX, targetY, playdate.easingFunctions.linear, nil, true)
        end
    end
end

---@param fielding Fielding
---@param baserunning Baserunning
---@param offenseState OffenseState
---@param fielder Fielder
---@param outedSomeRunner boolean
---@param ball { x: number, y: number, heldBy: Fielder | nil }
---@param launchBall LaunchBall
function npc.fielderAction(fielding, baserunning, offenseState, fielder, outedSomeRunner, ball, launchBall)
    if offenseState ~= C.Offense.running then
        return
    end
    if outedSomeRunner then
        -- Delay a little before the next play
        playdate.timer.new(750, function()
            npc.tryToMakeAPlay(fielding, fielder, baserunning.runners, ball, launchBall)
        end)
    else
        npc.tryToMakeAPlay(fielding, fielder, baserunning.runners, ball, launchBall)
    end
end

if not playdate then
    return npc
end
