--- @class Render
--- @field textures table<string, love.Canvas>
--- @field queue table[]
local render = {
    textures = {},
    queue = {},
    LAYERS = {
        FLOOR = 1,
        SHADOW = 2,
        LIGHT = 3,
        SPRITE = 4,
        SPRITE_LIGHT = 5,
        OVERLAY = 6
    }
}

function render:clear()
    local weather = Tree.level.weather
    local txs = self.textures
    self.queue = {}

    love.graphics.setCanvas(txs.shadowLayer)
    love.graphics.clear()
    love.graphics.setCanvas(txs.spriteLayer)
    love.graphics.clear()
    love.graphics.setCanvas(txs.spriteLightLayer)
    love.graphics.clear(weather.skyLight.x, weather.skyLight.y, weather.skyLight.z)
    love.graphics.setCanvas(txs.floorLayer)
    love.graphics.clear()
    love.graphics.setCanvas(txs.lightLayer)
    love.graphics.clear(weather.skyLight.x, weather.skyLight.y, weather.skyLight.z)
    love.graphics.setCanvas(txs.overlayLayer)
    love.graphics.clear()
end

function render:enqueue(layer, z, func)
    table.insert(self.queue, { layer = layer, z = z, func = func })
end

function render:free()
    for _, tx in pairs(self.textures) do
        tx:release()
    end
    self.textures = nil
end

--- TODO: это используется для блюра, должно кэшироваться и поддерживать ресайз

function render:applyBlur(input, radius)
    local blurShader = Tree.assets.files.shaders.blur

    -- Горизонтальный проход
    blurShader:send("direction", { 1.0, 0.0 })
    blurShader:send("radius", radius)

    self.textures.tmp1:renderTo(function()
        love.graphics.clear()
        love.graphics.setShader(blurShader)
        love.graphics.draw(input)
        love.graphics.setShader()
    end)

    -- Вертикальный проход
    self.textures.tmp2:renderTo(
        function()
            love.graphics.clear()
            love.graphics.setShader(blurShader)
            blurShader:send("direction", { 0.0, 1.0 })
            love.graphics.draw(self.textures.tmp1)
            love.graphics.setShader()
        end
    )
    return self.textures.tmp2
end

---@param params {w: number?, h: number?}
---@return table|Render
local function new(params)
    local w = params.w or love.graphics.getWidth()
    local h = params.h or love.graphics.getHeight()
    local lowResScale = 0.5

    return setmetatable({
        lowResScale = lowResScale,
        queue = {},
        textures = {
            shadowLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale),
            spriteLayer = love.graphics.newCanvas(w, h),
            spriteLightLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale),
            floorLayer = love.graphics.newCanvas(w, h),
            overlayLayer = love.graphics.newCanvas(w, h),
            lightLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale),
            tmp1 = love.graphics.newCanvas(w * lowResScale, h * lowResScale),
            tmp2 = love.graphics.newCanvas(w * lowResScale, h * lowResScale),
        }
    }, { __index = render })
end

function render:draw()
    local weather = Tree.level.weather
    local txs = self.textures

    -- 1. Сортировка очереди
    table.sort(self.queue, function(a, b)
        if a.layer ~= b.layer then
            return a.layer < b.layer
        end
        return a.z < b.z
    end)

    -- 2. Рендеринг очереди в соответствующие Canvas
    local currentLayer = nil
    for _, entry in ipairs(self.queue) do
        if entry.layer ~= currentLayer then
            if currentLayer then
                Tree.level.camera:detach()
                local wasLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.SPRITE_LIGHT or
                currentLayer == self.LAYERS.LIGHT
                if wasLowRes then
                    love.graphics.pop()
                end
            end
            currentLayer = entry.layer
            local isLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.SPRITE_LIGHT or
            currentLayer == self.LAYERS.LIGHT

            if currentLayer == self.LAYERS.FLOOR then
                love.graphics.setCanvas(txs.floorLayer)
            elseif currentLayer == self.LAYERS.SHADOW then
                love.graphics.setCanvas(txs.shadowLayer)
            elseif currentLayer == self.LAYERS.LIGHT then
                love.graphics.setCanvas(txs.lightLayer)
            elseif currentLayer == self.LAYERS.SPRITE then
                love.graphics.setCanvas(txs.spriteLayer)
            elseif currentLayer == self.LAYERS.SPRITE_LIGHT then
                love.graphics.setCanvas(txs.spriteLightLayer)
            elseif currentLayer == self.LAYERS.OVERLAY then
                love.graphics.setCanvas(txs.overlayLayer)
            end

            if isLowRes then
                love.graphics.push()
                love.graphics.scale(self.lowResScale)
            end
            Tree.level.camera:attach()
        end
        entry.func()
    end
    if currentLayer then
        Tree.level.camera:detach()
        local wasLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.SPRITE_LIGHT or
        currentLayer == self.LAYERS.LIGHT
        if wasLowRes then
            love.graphics.pop()
        end
    end
    love.graphics.setCanvas()

    -- 3. Пост-процессинг и композиция
    love.graphics.setCanvas(txs.lightLayer)
    -- Радиус блюра тоже масштабируем, так как текстура меньше
    love.graphics.draw(self:applyBlur(txs.shadowLayer, 4 * Tree.level.camera.scale * self.lowResScale))
    love.graphics.setCanvas()

    local lightShader = Tree.assets.files.shaders.light_postprocess
    lightShader:send("scene", txs.floorLayer)
    lightShader:send("light", self:applyBlur(txs.lightLayer, 2 * self.lowResScale))
    lightShader:send("ambient", { weather.ambientLight.x, weather.ambientLight.y, weather.ambientLight.z })
    love.graphics.setShader(lightShader)
    love.graphics.draw(txs.floorLayer)

    love.graphics.setShader()
    love.graphics.draw(txs.overlayLayer)
    love.graphics.setShader(lightShader)

    lightShader:send("scene", txs.spriteLayer)
    lightShader:send("light", txs.spriteLightLayer)
    love.graphics.draw(txs.spriteLayer)
    love.graphics.setShader()
end

return { new = new }
