local Vec3 = require "lib.utils.vec3"
local utils = require "lib.utils.utils"
local task = require "lib.utils.task"
local easing = require "lib.utils.easing"

local EPSILON = 0.001

--- @class Camera
--- @field position Vec3
--- @field velocity Vec3
--- @field speed number
--- @field pixelsPerMeter integer
--- @field scale number
local camera = {
    position = Vec3 {},
    velocity = Vec3 {},
    acceleration = 0.2,
    speed = 5,
    pixelsPerMeter = 32,
}

function camera:getDefaultScale()
    return love.window.getDesktopDimensions() /
        (self.pixelsPerMeter * 30) -- 30 meters wide regardless of the actual screen size
end

camera.__index = camera
camera.scale = camera:getDefaultScale()

---------------------------------------------------

local controlMap = {
    cameraMoveUp = Vec3({ 0, -1 }),
    cameraMoveLeft = Vec3({ -1 }),
    cameraMoveDown = Vec3({ 0, 1 }),
    cameraMoveRight = Vec3({ 1 })
}

function camera:update(dt)
    -------------------- зум на колесо ---------------------
    local y = Tree.controls.mouseWheelY
    if camera.scale > camera:getDefaultScale() * 5 and y > 0 then return end;
    if camera.scale < camera:getDefaultScale() / 5 and y < 0 then return end;
    camera.scale = camera.scale + (camera.scale * 0.1 * y)
    --------------------------------------------------------

    local ps = Tree.panning
    if ps.delta:length() > 0 then
        local worldDelta = ps.delta:scale(1 / (self.pixelsPerMeter * self.scale)):scale(dt):scale(self.speed)
        self.velocity = self.velocity + worldDelta
    elseif utils.any(utils.keys(controlMap), function(el)
            return Tree.controls:isDown(el)
        end) then
        local input = Vec3 {}
        for k, v in pairs(controlMap) do
            if Tree.controls:isDown(k) then input = input + v end
        end
        input = input:normalize() or Vec3 {}

        self.velocity = self.velocity:add(input:scale(self.acceleration):scale(dt))
        if self.velocity:length() > self.speed then self.velocity = self.velocity:normalize() * self.speed end
    end

    self.position = self.position + self.velocity
    self.velocity = self.velocity -
        self.velocity * 5 *
        dt -- магическая формула, которая означает "экспоненциально замедлиться до нуля"
    if self.velocity:length() < EPSILON then self.velocity = Vec3 {} end
end

function camera:attach()
    local wc, hc = love.graphics.getWidth() / 2, love.graphics.getHeight() / 2
    love.graphics.push()
    love.graphics.translate(wc, hc)
    love.graphics.scale(self.pixelsPerMeter * self.scale)
    love.graphics.translate(-self.position.x, -self.position.y)
end

--- @param position Vec3
function camera:toWorldPosition(position)
    local wx, wy = love.window.getMode()
    position = Vec3 {
        (position.x - wx / 2) / self.pixelsPerMeter / self.scale + self.position.x,
        (position.y - wy / 2) / self.pixelsPerMeter / self.scale + self.position.y
    }
    return position
end

function camera:detach()
    love.graphics.pop()
end

--- Плавно перемещает камеру к указанной точке.
--- @param position Vec3
--- @param duration number?
--- @param easing function?
--- @return Task
function camera:animateTo(position, duration, easing)
    self.velocity = Vec3 {} -- Сбрасываем инерцию перед началом анимации
    return task.tween(self, { position = position }, duration or 1000, easing)
end

--- @return Camera
local function new()
    return setmetatable({
    }, camera)
end

return { new = new }
