-- stylua: ignore start
import 'CoreLibs/animation.lua'
import 'CoreLibs/graphics.lua'
-- stylua: ignore end

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

function utils.easingHill(t, b, c, d)
    c = c + 0.0 -- convert to float to prevent integer overflow
    t = t / d
    t = ((t * 2) - 1)
    t = t * t
    return (c * t) + b
end

---@param x number
---@param y number
---@return XYPair
function utils.xy(x, y)
    return {
        x = x,
        y = y,
    }
end

--- Returns the normalized vector as two values, plus the distance between the given points.
---@param x1 number
---@param y1 number
---@param x2 number
---@param y2 number
---@return number x, number y, number distance
function utils.normalizeVector(x1, y1, x2, y2)
    local distance, x, y = utils.distanceBetween(x1, y1, x2, y2)
    return x / distance, y / distance, distance
end

--- Push the given obect at the given speed toward a target. Speed should be pre-multiplied by the frame's delta time.
--- Stops when within 1. Returns true only if the object did actually move.
---@param mover { x: number, y: number }
---@param speed number
---@param target { x: number, y: number }
---@return boolean
function utils.moveAtSpeed(mover, speed, target)
    local x, y, distance = utils.normalizeVector(mover.x, mover.y, target.x, target.y)

    if distance > 1 then
        mover.x = mover.x - (x * speed)
        mover.y = mover.y - (y * speed)
        return true
    end

    return false
end

---@generic T
---@param array T[]
---@param condition fun(T): boolean
---@return T[]
function utils.filter(array, condition)
    local newArray = {}
    for _, element in pairs(array) do
        if condition(element) then
            newArray[#newArray + 1] = element
        end
    end
    return newArray
end

---@generic T
---@param array T[]
---@param condition fun(T): boolean
---@return T | nil
function utils.first(array, condition)
    for _, element in ipairs(array) do
        if condition(element) then
            return element
        end
    end
    return nil
end

---@return number distance, number x, number y
function utils.distanceBetween(x1, y1, x2, y2)
    local x = x1 - x2
    local y = y1 - y2
    return math.sqrt((x * x) + (y * y)), x, y
end

---@return number distance, number x, number y, number z
function utils.distanceBetweenZ(x1, y1, z1, x2, y2, z2)
    local x = x1 - x2
    local y = y1 - y2
    local z = z1 - z2
    return math.sqrt((x * x) + (y * y) + (z * z)), x, y, z
end

--- Returns true only if the point is below the given line, within the x bounds of said line, and above the bottomBound
--- @return boolean
function utils.pointDirectlyUnderLine(pointX, pointY, lineX1, lineY1, lineX2, lineY2, bottomBound)
    -- This check currently assumes right-handedness.
    -- I.e. it assumes the ball is to the right of batBaseX
    if pointX < lineX1 or pointX > lineX2 or pointY > bottomBound then
        return false
    end

    local m = (lineY2 - lineY1) / (lineX2 - lineX1)

    -- y = mx + b
    -- b = y1 - (m * x1)
    local b = lineY1 - (m * lineX1)
    local yOnLine = (m * pointX) + b
    local yP = pointY
    local yDelta = yOnLine - yP

    return yDelta <= 0
end

--- Returns the nearest position object from the given point, as well as its distance from that point
---@generic T : {x: number, y: number | nil}
---@param array T[]
---@param x number
---@param y number
---@return T nearest,number |nil distance
function utils.getNearestOf(array, x, y, extraCondition)
    local nearest, nearestDistance = nil, nil
    for _, element in pairs(array) do
        if not extraCondition or extraCondition(element) then
            if nearest == nil then
                nearest = element
                nearestDistance = utils.distanceBetween(element.x, element.y, x, y)
            else
                local distance = utils.distanceBetween(element.x, element.y, x, y)
                if distance < nearestDistance then
                    nearest = element
                    nearestDistance = distance
                end
            end
        end
    end

    return nearest, nearestDistance
end

---@alias Cache { get: fun(key: `Key`): `Value` }

if not playdate then
    return utils
end
