local Query        = require "lib.spell.target_query"
local targetTest   = require "lib.spell.target_test"
local task         = require "lib.utils.task"
local easing       = require "lib.utils.easing"
local pf           = require "lib.pathfinder"

--- @alias SpellPreview "default" Подсветка возможных целей
--- | "path" Подсветка пути до цели

--- @class Spell
--- @field tag string
--- @field baseCost integer Базовые затраты маны на каст
--- @field baseCooldown integer Базовый кулдаун в ходах
--- @field targetQuery SpellTargetQuery Селектор возможных целей
--- @field targetType SpellPreview Вид превью во время каста
--- @field distance? integer Сторона квадрата с центром в позиции кастера, в пределах которого должна находиться цель, либо отсутствие ограничения
--- @field update fun(self: Spell, caster: Character, dt: number): nil Изменяет состояние спелла
--- @field draw fun(self: Spell): nil Рисует превью каста, ничего не должна изменять в идеальном мире
--- @field cast fun(self: Spell, caster: Character, target: Vec3): Task<nil>? Вызывается в момент каста, изменяет мир.
local spell        = {}
spell.__index      = spell
spell.tag          = "spell_base"
spell.baseCost     = 1
spell.baseCooldown = 1
spell.targetQuery  = Query(targetTest.any)
spell.targetType   = "default"


--- Вызывается, когда игрок выбирает спелл на панели заклинаний
--- @param caster Character
function spell:onSelected(caster)
    self.targets = self.targetQuery:asSet(caster)
    self.tSize = 0.67 -- анимация появления таргетов
    task.tween(self, { tSize = 1 }, 200, easing.easeOutQuad)
end

function spell:update(caster, dt)
    if self.targetType == "path" then
        local charPos = caster:has(Tree.behaviors.positioned).position:floor()
        --- @type Vec3
        local mpos = Tree.level.camera:toWorldPosition(Vec3 { love.mouse.getX(), love.mouse.getY() }):floor()
        if self.targetQuery.test(caster, mpos) then
            self.path = require "lib.pathfinder" (charPos, mpos)
        else
            self.path = nil
        end
    end
end

local icons = require("lib.utils.sprite_atlas").load(Tree.assets.files.overlay_icons)

function spell:draw()
    if self.targetType == "path" then
        local path = self.path --[[@as Deque?]]
        if not path then return end
        --- Это отрисовка пути персонажа к мышке
        Tree.level.render:enqueue(Tree.level.render.LAYERS.OVERLAY, 0, function()
            local i = 0
            path:pop_front()
            for p in path:values() do
                i = i + 1
                local s = 1 / Tree.level.camera.pixelsPerMeter
                local quad = i > self.distance and icons:pickQuad('dev_path_closed') or icons:pickQuad('dev_path')
                love.graphics.draw(icons.atlas, quad, p.x, p.y, 0, s, s)
            end
            love.graphics.setColor(1, 1, 1)
        end)
    else
        Tree.level.render:enqueue(Tree.level.render.LAYERS.OVERLAY, 0, function()
            love.graphics.setColor(1, 1, 1, 0.5)
            for _, p in pairs(self.targets) do
                local s = self.tSize / Tree.level.camera.pixelsPerMeter
                local quad = icons:pickQuad('dev_target')
                love.graphics.draw(icons.atlas, quad, p.x + 0.5 - self.tSize / 2, p.y + 0.5 - self.tSize / 2, 0, s, s)
            end
            love.graphics.setShader()
            love.graphics.setColor(1, 1, 1)
        end)
    end
end

function spell:cast(caster, target) return task.fromValue() end

--- Конструктор [Spell]
--- @param data {tag: string, baseCost: integer, baseCooldown: integer, targetQuery: SpellTargetQuery?, targetType: SpellPreview?, distance: integer?, onCast: fun(caster: Character, target: Vec3): Task<nil>?}
--- @return Spell
function spell.new(data)
    local newSpell = setmetatable({
        tag = data.tag,
        baseCost = data.baseCost,
        baseCooldown = data.baseCooldown,
        targetQuery = data.targetQuery,
        targetType = data.targetType,
        distance = data.distance
    }, spell)

    newSpell.targetQuery = (newSpell.distance and newSpell.targetType ~= "path")
        and newSpell.targetQuery:intersect(Query(targetTest.distance(newSpell.distance)))
        or newSpell.targetQuery

    function newSpell:cast(caster, target)
        if caster:try(Tree.behaviors.spellcaster, function(spellcaster) -- проверка на кулдаун
                return (spellcaster.cooldowns[self.tag] or 0) > 0
            end) then
            return
        end

        if not self.targetQuery.test(caster, target) then return end -- проверка корректности цели
        if self.targetType == "path" then
            -- дополнительное условие для спеллов с путями (количество шагов)
            if not caster:has(Tree.behaviors.tiled) or not caster:has(Tree.behaviors.positioned) then return end
            local i = 1
            for _ in pf(caster:has(Tree.behaviors.positioned).position, target):values() do
                if i > self.distance + 1 then return end -- учитывается начальная точка, где находится кастер
                i = i + 1
            end
        end

        -- проверка на достаточное количество маны
        if caster:try(Tree.behaviors.stats, function(stats)
                return stats.mana < self.baseCost
            end) then
            return
        end

        caster:try(Tree.behaviors.stats, function(stats)
            stats.mana = stats.mana - self.baseCost
        end)

        caster:try(Tree.behaviors.spellcaster, function(spellcaster)
            spellcaster.cooldowns[self.tag] = self.baseCooldown
        end)

        caster:try(Tree.behaviors.effects, function(effects)
            effects:beforeCast()
        end)
        return data.onCast(caster, target)
    end

    return newSpell
end

return spell
