local TXUI, F, E, I, V, P, G = unpack(select(2, ...))
local SD = TXUI:NewModule("SkinsDetailsGradients", "AceHook-3.0")

-- Globals
local _G = _G
local abs = math.abs
local ipairs = ipairs

-- Vars
local DT
local GR

function SD:RefreshDetails()
    for _, instance in DT:ListInstances() do
        if instance.iniciada then
            instance:InstanceReset()
            instance:InstanceRefreshRows()
            instance:ReajustaGump()
            instance:ForceRefresh()
        end
    end
end

function SD:RefreshTexture(row)
    if not self.isEnabled or not self.db or not self.db.enabled then return end
    self.hooks[row.textura]["SetTexture"](row.textura, self.db.texture)
    if row.background then row.background.SetTexture(row.background, self.db.texture) end
end

function SD:RefreshRow(row, instanceId, dR, dG, dB)
    if not self.isEnabled or not self.db or not self.db.enabled then return end
    if not row or not row.textura then return self:DebugPrint("Row is empty", row) end

    local cache = row.cachedColorMap or {}
    local statusMin, statusMax = row.statusbar:GetMinMaxValues()
    local percent = (row.statusbar:GetValue() - statusMin) / (statusMax - statusMin)
    local percentChange = cache.percent == nil or (abs(cache.percent - percent) > 0.05)
    local nR, nG, nB, sR, sG, sB

    local useCache = not percentChange and cache.dR and (not dR or (F.AlmostEqual(cache.dR, dR) and F.AlmostEqual(cache.dG, dG) and F.AlmostEqual(cache.dB, dB)))
    if percentChange then cache.percent = percent end

    if dR then
        cache.dR, cache.dG, cache.dB = dR, dG, dB
    end

    if not useCache then
        local unitClass
        local actor = row.minha_tabela
        local calculateBackupFG = true

        if actor and actor.name then
            unitClass = actor:class()
            if unitClass then calculateBackupFG = false end
        end

        if not calculateBackupFG then
            local shiftMap = self.db.classColorMap[I.Enum.GradientMode.Color.SHIFT][unitClass]
            local normalMap = self.db.classColorMap[I.Enum.GradientMode.Color.NORMAL][unitClass]

            if not shiftMap or not normalMap then
                calculateBackupFG = true
            else
                nR, nG, nB = normalMap.r, normalMap.g, normalMap.b
                sR, sG, sB = shiftMap.r, shiftMap.g, shiftMap.b
            end
        end

        if calculateBackupFG and not dR then return end

        if calculateBackupFG then
            nR, nG, nB = dR, dG, dB
            sR, sG, sB = GR:CalculateBackupColor(dR, dG, dB)
        end

        cache.nR, cache.nG, cache.nB = nR, nG, nB
        cache.sR, cache.sG, cache.sB = sR, sG, sB
    else
        nR, nG, nB = cache.nR, cache.nG, cache.nB
        sR, sG, sB = cache.sR, cache.sG, cache.sB
    end

    local fadeMode = I.Enum.GradientMode.Mode[I.Enum.GradientMode.Mode.HORIZONTAL]
    local rnfR, rnfG, rnfB, rsfR, rsfG, rsfB, rnbR, rnbG, rnbB, rsbR, rsbG, rsgB =
        GR:CalculateCachedFading(cache, useCache, (instanceId == 1 and I.Enum.GradientMode.Direction.RIGHT or I.Enum.GradientMode.Direction.LEFT), percent, nR, nG, nB, sR, sG, sB)

    if not self.updateCache[row] then
        row.lineBorder:Kill()
        row.statusbar:CreateBackdrop("Default", nil, false, false, true)
        self:RefreshTexture(row)
    end

    row.textura:SetGradient(fadeMode, rnfR, rnfG, rnfB, rsfR, rsfG, rsfB)
    if row.background then row.background:SetGradient(fadeMode, rnbR, rnbG, rnbB, rsbR, rsbG, rsgB) end

    row.cachedColorMap = cache
    self.updateCache[row] = true
end

function SD:RefreshRows(instance, instanceSpecific)
    if not self.isEnabled or not self.db or not self.db.enabled then return end

    if instanceSpecific then instance = instanceSpecific end
    if not instance or not instance.barras then return self:DebugPrint("Instance is empty", instance) end

    local instanceId = instance:GetInstanceId()

    for _, bar in ipairs(instance.barras) do
        if not bar or not bar.textura then return end

        -- Hook for Color Update
        if not self:IsHooked(bar.textura, "SetVertexColor") then
            self:RawHook(bar.textura, "SetVertexColor", function(_, r, g, b)
                self:RefreshRow(bar, instanceId, r, g, b)
            end, true)
        end

        -- Hook for texture update
        if not self:IsHooked(bar.textura, "SetTexture") then self:RawHook(bar.textura, "SetTexture", function()
            self:RefreshTexture(bar)
        end, true) end

        -- Hook for Value Update
        if not self:IsHooked(bar.statusbar, "SetValue") then self:SecureHook(bar.statusbar, "SetValue", function()
            self:RefreshRow(bar, instanceId)
        end) end
    end
end

function SD:EndRefresh(_, instance)
    self:RefreshRows(instance)
end

function SD:SettingsUpdate()
    if not DT or not self.isEnabled or not self.Initialized then return end

    self.updateCache = {}
    self:RefreshDetails()
end

function SD:Disable()
    if not DT or not self.Initialized then return end
    if not self:IsHooked(DT, "InstanceRefreshRows") then return end

    self:UnhookAll()

    -- Set old colors if using our profile
    if TXUI:HasRequirements(I.Enum.Requirements.DETAILS_LOADED_AND_TXPROFILE) then
        for _, instance in DT:ListInstances() do
            instance.row_info.textL_class_colors = false
            instance.row_info.textR_class_colors = false
            instance.row_info.texture_class_colors = true
        end

        -- Refresh after unhook
        self:RefreshDetails()
    end
end

function SD:Enable()
    if not self.isEnabled or not self.Initialized then return end
    if self:IsHooked(DT, "InstanceRefreshRows") then return end

    -- Get frameworks
    DT = DT or _G.Details
    GR = GR or TXUI:GetModule("ThemesGradients")

    -- Sanity check
    if not DT then return self:DebugPrint("Could not get framework", DT) end

    -- Set correct colors
    for _, instance in DT:ListInstances() do
        instance.row_info.textL_class_colors = false
        instance.row_info.textR_class_colors = false
        instance.row_info.texture_class_colors = true
    end

    -- Hook functions
    self:SecureHook(DT, "InstanceRefreshRows", F.Event.GenerateClosure(self.RefreshRows, self))
    self:SecureHook(DT, "EndRefresh", F.Event.GenerateClosure(self.EndRefresh, self))
    self:SecureHook(DT, "ApplyProfile", F.Event.GenerateClosure(self.DatabaseUpdate, self))

    -- Refresh after hook
    self:SettingsUpdate()
end

function SD:DatabaseUpdate()
    -- Set db
    self.db = F.GetDBFromPath("TXUI.themes.gradientMode")

    -- Set enable state
    local isEnabled = (self.db and self.db.enabled)
        and TXUI:HasRequirements(I.Requirements.DetailsGradientMode)
        and TXUI:HasRequirements(I.Enum.Requirements.DETAILS_LOADED_AND_TXPROFILE)

    if self.isEnabled == isEnabled then return end
    self.isEnabled = isEnabled

    F.Event.ContinueOutOfCombat(function()
        -- Disable only out of combat
        self:Disable()

        -- Enable only if details is loaded
        if self.isEnabled then self:Enable() end
    end)
end

function SD:Initialize()
    -- Don't init second time
    if self.Initialized then return end

    -- Vars
    self.isEnabled = false
    self.updateCache = {}

    -- Register for updates
    F.Event.RegisterCallback("TXUI.DatabaseUpdate", self.DatabaseUpdate, self)
    F.Event.RegisterCallback("SkinsDetailsGradients.DatabaseUpdate", self.DatabaseUpdate, self)
    F.Event.RegisterCallback("SkinsDetailsGradients.SettingsUpdate", self.SettingsUpdate, self)

    -- Trigger Update when Details is loaded
    F.Event.RegisterOnceCallback("TXUI.InitializedSafe", function()
        F.Event.ContinueOnAddOnLoaded("Details", function()
            self:DatabaseUpdate()
        end)
    end)
    -- We are done, hooray!
    self.Initialized = true
end

TXUI:RegisterModule(SD:GetName())
