--- Алгоритм обработки заклинания (for dummies):
--- 1) ПОКА выделен персонаж И он находится в режиме каста, вызывать spell:update() и spell:draw() каждый кадр (это отвечает за обработку и отрисовку превьюшки каста, например, превью пути или зоны поражения; реализуется через установку spellcaster.cast, см. код в кнопке)
---     ЕСЛИ выбран тайл, ТО вызвать spell:cast() (это запрос на обработку последствий применения заклинания, например, старт анимации ходьбы, выпуск снаряда и т.д.; реализовано в selector)
---         ЕСЛИ spell:cast() ИСТИНА, ТО вызвать selector:lock() (отключить обработку выделения всего на уровне; реализовано в selector)
---
--- 2) ПОКА анимация каста НЕ завершена, ничего не делать, ИНАЧЕ вызвать behaviors.spellcaster:endCast() (вот это сейчас нужно вызывать самостоятельно, т.к. нет возможности обобщенно отследить завершение анимаций)
--- --TODO: каждый каст должен возвращать объект, который позволит отследить момент завершения анимации спелла
--- Да, это Future/Promise/await/async

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

local walk           = spell.new {
    tag = "dev_move",
    targetType = "path",
    baseCooldown = 1,
    baseCost = 2,
    targetQuery = Query(targetTest.any):exclude(Query(targetTest.character)),
    distance = 3,
    onCast = function(caster, target)
        local initialPos = caster:has(Tree.behaviors.positioned).position:floor()
        local path = require "lib.pathfinder" (initialPos, target)
        path:pop_front()
        if path:is_empty() then
            print("[Walk]: the path is empty", initialPos, target)
            return
        end

        local sprite = caster:has(Tree.behaviors.sprite)
        assert(sprite, "[Walk]", "WTF DUDE WHERE'S YOUR SPRITE")
        if not sprite then
            return
        end

        return caster:has(Tree.behaviors.tiled):followPath(path)
    end
}

local regenerateMana = spell.new {
    tag = "dev_mana",
    baseCooldown = 2,
    baseCost = 0,
    targetQuery = Query(targetTest.caster),
    distance = 0,
    onCast = function(caster, target)
        caster:try(Tree.behaviors.stats, function(stats)
            stats.mana = 10
            stats.initiative = stats.initiative + 10
        end)

        local sprite = caster:has(Tree.behaviors.sprite)
        if not sprite then return end
        print(caster.id, "has regenerated mana and gained initiative")

        local light = require "lib/character/character".spawn("Light Effect")
        light:addBehavior {
            Tree.behaviors.light.new { color = Vec3 { 0.3, 0.3, 0.6 }, intensity = 4 },
            Tree.behaviors.positioned.new(caster:has(Tree.behaviors.positioned).position + Vec3 { 0.5, 0.5 }),
        }

        return task.wait {
            task.chain(task.tween(light:has(Tree.behaviors.light) --[[@as LightBehavior]],
                { intensity = 1, color = Vec3 {} }, 800, easing.easeInCubic), function()
                light:die()
                return task.fromValue()
            end),
            sprite:animate("hurt")
        }
    end
}

local attack         = spell.new {
    tag = "dev_attack",
    baseCooldown = 1,
    baseCost = 2,
    targetQuery = Query(targetTest.character):exclude(Query(targetTest.caster)),
    distance = 1,
    onCast = function(caster, target)
        --- @type Character
        local targetCharacterId = Tree.level.characterGrid:get(target)
        local targetCharacter = Tree.level.characters[targetCharacterId]
        targetCharacter:try(Tree.behaviors.stats, function(stats)
            stats.hp = stats.hp - 4
        end)

        local targetEffects = targetCharacter:has(Tree.behaviors.effects)
        local sprite = caster:has(Tree.behaviors.sprite)
        local targetSprite = targetCharacter:has(Tree.behaviors.sprite)
        if not sprite or not targetSprite or not targetEffects then return end -- проверять на эффект может и не стоит

        caster:try(Tree.behaviors.positioned, function(b) b:lookAt(target) end)

        return
            task.wait {
                sprite:animate("attack"),
                task.wait {
                    task.chain(targetCharacter:has(Tree.behaviors.residentsleeper):sleep(500),
                        function()
                            local light = require "lib/character/character".spawn("Light Effect")
                            light:addBehavior {
                                Tree.behaviors.light.new { color = Vec3 { 0.6, 0.3, 0.3 }, intensity = 4 },
                                Tree.behaviors.positioned.new(targetCharacter:has(Tree.behaviors.positioned).position + Vec3 { 0.5, 0.5 }),
                            }
                            return
                                task.wait {
                                    task.chain(task.tween(light:has(Tree.behaviors.light) --[[@as LightBehavior]],
                                        { intensity = 1, color = Vec3 {} }, 1000, easing.easeInCubic), function()
                                        light:die()
                                        return task.fromValue()
                                    end),
                                    targetSprite:animate("hurt"),
                                    targetEffects:addEffect(targetEffects.effectbook[1], 3, 3)
                                }
                        end
                    ),

                    Tree.audio:play(Tree.assets.files.audio.sounds.hurt)
                }
            }
    end
}

----------------------------------------
local spellbook      = {
    walk = walk,
    regenerateMana = regenerateMana,
    attack = attack
}

--- Создает новый спеллбук с уникальными спеллами (а не ссылками на шаблоны)
--- @param list Spell[]
function spellbook.of(list)
    local spb = {}
    for i, sp in ipairs(list) do
        print(i)
        spb[i] = setmetatable({}, { __index = sp })
    end
    return spb
end

return spellbook
