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

---@class Npc: InputHandler
---@field runners Runner[]
---@field fielders Fielder[]
-- selene: allow(unscoped_variables)
Npc = {}

---@param runners Runner[]
---@param fielders Fielder[]
---@return Npc
function Npc.new(runners, fielders)
    return setmetatable({
        runners = runners,
        fielders = fielders,
    }, { __index = Npc })
end

function Npc.update() end

-- TODO: FAR more nuanced NPC batting.
-- luacheck: no unused
---@param ball XyPair
---@param pitchIsOver boolean
---@param deltaSec number
---@return number batAngleDeg, number batSpeed
function Npc:updateBat(ball, pitchIsOver, deltaSec)
    if not pitchIsOver 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 = 230
    end
    return npcBatDeg, (self:batSpeed() * deltaSec)
end

---@return number
function Npc:batSpeed()
    return npcBatSpeed / 1.5
end

---@return number flyTimeMs, number pitchId, number accuracy
function Npc:pitch()
    return C.PitchFlyMs / self:pitchSpeed(), math.random(#Pitches), 0.9
end

local baseRunningSpeed = 25

---@param runner Runner
---@param ball Point3d
---@return number
function Npc:runningSpeed(runner, ball)
    if #self.runners == 0 then
        return 0
    end

    local distanceFromBall = utils.distanceBetweenZ(ball.x, ball.y, ball.z, runner.x, runner.y, 0)

    if distanceFromBall > 400 or runner.forcedTo then
        return baseRunningSpeed
    end

    local touchedBase = utils.isTouchingBase(runner.x, runner.y)
    if not touchedBase and runner.nextBase then
        local distToNext = utils.distanceBetween(runner.x, runner.y, runner.nextBase.x, runner.nextBase.y)
        local distToPrev = utils.distanceBetween(runner.x, runner.y, runner.prevBase.x, runner.prevBase.y)
        if distToNext < distToPrev or distanceFromBall > 350 then
            return baseRunningSpeed
        else
            return -1 * baseRunningSpeed
        end
    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
local function 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 fielders Fielder[]
---@param fielder Fielder
---@param runners Runner[]
---@param ball { x: number, y: number, heldBy: Fielder | nil, launch: LaunchBall }
local function tryToMakeAPlay(fielders, fielder, runners, ball)
    local targetX, targetY = getNextOutTarget(runners)
    if targetX ~= nil and targetY ~= nil then
        local nearestFielder = utils.getNearestOf(fielders, targetX, targetY)
        nearestFielder.targets = { utils.xy(targetX, targetY) }
        if nearestFielder == fielder then
            ball.heldBy = fielder
        else
            ball:launch(targetX, targetY, playdate.easingFunctions.linear, nil, true)
        end
    end
end

---@param fielder Fielder
---@param outedSomeRunner boolean
---@param ball { x: number, y: number, heldBy: Fielder | nil, launch: LaunchBall }
function Npc:fielderAction(fielder, outedSomeRunner, ball)
    if not fielder then
        return
    end
    if outedSomeRunner then
        -- Delay a little before the next play
        playdate.timer.new(750, function()
            tryToMakeAPlay(self.fielders, fielder, self.runners, ball)
        end)
    else
        tryToMakeAPlay(self.fielders, fielder, self.runners, ball)
    end
end

---@return number
function Npc:pitchSpeed()
    return 2
end

if not playdate then
    return Npc
end
