local task = require "lib.utils.task"
local ease = require "lib.utils.easing"

local EFFECTS_SUPPORTED = love.audio.isEffectsSupported()

--- @alias SourceFilter { type: "bandpass"|"highpass"|"lowpass", volume: number, highgain: number, lowgain: number }

--- @class Audio
--- @field musicVolume number
--- @field soundVolume number
--- @field looped boolean
--- @field from love.Source?
--- @field to love.Source?
audio = {}
audio.__index = audio

--- здесь мы должны выгружать значения из файлика с сохранением настроек
local function new(musicVolume, soundVolume)
    return setmetatable({
        musicVolume = musicVolume,
        soundVolume = soundVolume,
        looped = true
    }, audio)
end

function audio:update(dt)
    if self.fader then
        local t = self.fader.value
        if self.from then self.from:setVolume(self.musicVolume * (1 - t)) end
        if self.to then self.to:setVolume(self.musicVolume * t) end
    end
end

--- if from is nil, than we have fade in to;
--- if to is nil, than we have fade out from
---
--- also we should guarantee, that from and to have the same volume
--- @param from love.Source
--- @param to love.Source
--- @param ms number? in milliseconds
function audio:crossfade(from, to, ms)
    print("[Audio]: Triggered crossfade")
    self:play(to)
    to:setVolume(0)
    self.from = from
    self.to = to

    -- Using a dummy object to tween a value from 0 to 1
    local fade = { value = 0 }
    
    task.tween(fade, { value = 1 }, ms or 1000, ease.easeOutCubic)(function()
        self.from:setVolume(0)
        self.to:setVolume(self.musicVolume)
        self.from:stop()
        print("[Audio]: Crossfade done")
    end)
    
    -- We need a custom update loop for the volume during the tween
    -- Since task.tween updates properties, we can use a "monitor" task or just rely on `update` if we expose the task?
    -- Actually, task.tween updates `fade.value` every frame.
    -- We need to apply this value to the audio sources every frame.
    -- BUT task.tween doesn't provide an "onUpdate" callback easily. 
    -- Workaround: create a task that runs every frame? Or modify task.tween?
    
    -- Let's check task.lua... it updates properties on the target object.
    -- So `fade.value` will change. We need to apply it.
    -- We can hook into `update` or use a metatable on `fade`?
    
    -- Let's use a "reactive" table with metatable setters? Overkill.
    -- Simplest way: Add `onUpdate` to task.tween or just use `audio:update` to poll `fade.value`?
    -- But `fade` is local.
    
    -- Better approach: Tween `self` properties? No, volume is on Source userdate.
    -- Let's add a `fader` object to `self` that we tween, and in `update` we apply it.
    
    self.fader = { value = 0 }
    task.tween(self.fader, { value = 1 }, ms or 1000, ease.easeOutCubic)(function()
        self.fader = nil
    end)
end

--- @param source love.Source
--- @param settings SourceFilter?
--- @param effectName string?
function audio:play(source, settings, effectName)
    if source:getType() == "stream" then
        source:setLooping(self.looped)
        source:setVolume(self.musicVolume)
        source:play()
    else
        source:setVolume(self.soundVolume)
        source:play()
    end
    if settings and EFFECTS_SUPPORTED then
        source.setFilter(source, settings)
    end
    if effectName and EFFECTS_SUPPORTED then
        source:setEffect(effectName, true)
    end
end

function audio:setMusicVolume(volume)
    self.musicVolume = volume
end

function audio:setSoundVolume(volume)
    self.soundVolume = volume
end

return { new = new }
