-- Code based on tullaCooldownCount by Tuller
-- http://www.wowinterface.com/downloads/info17602-tullaCooldownCount.html
local _, private = ...

-- luacheck: globals _G

-- Lua Globals --
local next = _G.next

-- Libs --
local Aurora = _G.Aurora
local Color = Aurora.Color

-- RealUI --
local RealUI = private.RealUI
local round = RealUI.Round
local db

local MODNAME = "CooldownCount"
local CooldownCount = RealUI:NewModule(MODNAME, "AceTimer-3.0", "AceEvent-3.0")

local Timer = {}
CooldownCount.Timer = Timer

----------
--sexy constants!
local CD_FONT
local DAY, HOUR, MINUTE = 86400, 3600, 60 --used for formatting text
local DAYISH, HOURISH, MINUTEISH = 3600 * 23.5, 60 * 59.5, 59.5 --used for formatting text at transition points
local HALFDAYISH, HALFHOURISH, HALFMINUTEISH = DAY/2 + 0.5, HOUR/2 + 0.5, MINUTE/2 + 0.5 --used for calculating next update times

local EXPIRING_FORMAT = Color.red:WrapTextInColorCode("%d")
local SECONDS_FORMAT = Color.yellow:WrapTextInColorCode("%d")
local MINUTES_FORMAT = Color.white:WrapTextInColorCode("%dm")
local HOURS_FORMAT = Color.cyan:WrapTextInColorCode("%dh")
local DAYS_FORMAT = Color.blue:WrapTextInColorCode("%dd")

--returns both what text to display, and how long until the next update
local function getTimeText(s)
    --format text as seconds when at 90 seconds or below
    if s < MINUTEISH then
        local seconds = round(s)
        local formatString = seconds > db.expiringDuration and SECONDS_FORMAT or EXPIRING_FORMAT
        return formatString, seconds, s - (seconds - 0.51)
    --format text as minutes when below an hour
    elseif s < HOURISH then
        local minutes = round(s/MINUTE)
        return MINUTES_FORMAT, minutes, minutes > 1 and (s - (minutes * MINUTE - HALFMINUTEISH)) or (s - MINUTEISH)
    --format text as hours when below a day
    elseif s < DAYISH then
        local hours = round(s/HOUR)
        return HOURS_FORMAT, hours, hours > 1 and (s - (hours * HOUR - HALFHOURISH)) or (s - HOURISH)
    --format text as days
    else
        local days = round(s/DAY)
        return DAYS_FORMAT, days, days > 1 and (s - (days * DAY - HALFDAYISH)) or (s - DAYISH)
    end
end


---------------
---- Timer ----
---------------
function Timer:SetNextUpdate(nextUpdate)
    if self.updater and CooldownCount:TimeLeft(self.updater) > 0 then
        CooldownCount:CancelTimer(self.updater)
    end
    self.updater = CooldownCount:ScheduleTimer(self.UpdateText, nextUpdate, self)
end

--stops the timer
function Timer:Stop()
    self.enabled = nil
    self.start = nil
    self.duration = nil
    self.charges = nil
    self.maxCharges = nil
    CooldownCount:CancelTimer(self.updater)
    return self:Hide()
end


local sizeAdjust = {
    {time = 10, adj = 4},
    {time = MINUTE, adj = 2},
}

local function GetBaseFontSize(timer)
    local parent = timer:GetParent()
    if not parent then
        return CD_FONT.size, CD_FONT.size + sizeAdjust[1].adj
    end

    -- GetWidth/GetHeight can return secret numbers on nameplate cooldown
    -- frames (Blizzard secure context in BGs). B58: pre-check with
    -- canaccessvalue instead of arithmetic-in-pcall — the caught throw still
    -- wrote a taint.log entry. Same fallback: default sizes.
    local pw, ph = parent:GetWidth() or 0, parent:GetHeight() or 0
    if _G.canaccessvalue and not (_G.canaccessvalue(pw) and _G.canaccessvalue(ph)) then
        return CD_FONT.size, CD_FONT.size + sizeAdjust[1].adj
    end
    local shortestSide = _G.math.min(pw, ph)
    if shortestSide <= 0 then
        return CD_FONT.size, CD_FONT.size + sizeAdjust[1].adj
    end

    local baseSize = _G.math.min(CD_FONT.size, _G.math.max(6, _G.math.floor(shortestSide * 0.36 + 0.5)))
    local maxSize = _G.math.max(baseSize, _G.math.floor(shortestSide * 0.44 + 0.5))
    return baseSize, maxSize
end

function Timer:UpdateText()
    local ok, remain = _G.pcall(function()
        return self.enabled and (self.duration - (_G.GetTime() - self.start)) or 0
    end)
    if not ok then
        return self:Stop()
    end
    if round(remain) > 0 then
        local text = self.text
        local formatStr, time, nextUpdate = getTimeText(remain)

        local size, maxSize = GetBaseFontSize(self)
        for i = 1, #sizeAdjust do
            local info = sizeAdjust[i]
            if remain < info.time then
                size = _G.math.min(size + info.adj, maxSize)
                break
            end
        end
        text:SetFont(CD_FONT.font, size, CD_FONT.flags)

        text:SetFormattedText(formatStr, time)
        text:Show()
        return self:SetNextUpdate(nextUpdate)
    else
        return self:Stop()
    end
end

function Timer:Start(start, duration, modRate)
    -- In battlegrounds, Blizzard can pass secret number values that cannot be
    -- used in arithmetic or comparisons by addon code. B58: pre-check with
    -- canaccessvalue instead of comparing inside a pcall — the caught throw
    -- still wrote a taint.log entry. Secret values gate here, so UpdateText's
    -- arithmetic only ever sees plain numbers.
    local shouldStart = start and duration
    if shouldStart and _G.canaccessvalue
        and not (_G.canaccessvalue(start) and _G.canaccessvalue(duration)) then
        shouldStart = false
    end
    if shouldStart then
        shouldStart = start > 0 and duration > db.minDuration
    end
    if shouldStart then
        self.start = start
        self.duration = duration
        self.enabled = true
        local size = GetBaseFontSize(self)
        self.text:SetFont(CD_FONT.font, size, CD_FONT.flags)
        self:UpdateText()
        self:Show()
    else
        self:Stop()
    end
end

local anchor = {
    TOPLEFT = {x = 1, y = -1},
    BOTTOMLEFT = {x = 1, y = 1},
    TOPRIGHT = {x = -1, y = -1},
    BOTTOMRIGHT = {x = -1, y = 1}
}
--returns a new timer object
local function CreateTimer(cd)
    local timer = _G.CreateFrame('Frame', nil, cd)
    timer:SetAllPoints()
    timer:Hide()

    local point = anchor[db.point]
    local text = timer:CreateFontString(nil, 'OVERLAY')
    text:SetFont(CD_FONT.font, CD_FONT.size, CD_FONT.flags)
    text:SetPoint(db.point, point.x, point.y)
    text:Hide()
    timer.text = text

    for key, func in next, Timer do
        timer[key] = func
    end
    return timer
end

local ignore = {}
local function ShouldHaveTimer(cd)
    if ignore[cd] then return end

    if cd:IsForbidden() or cd.noCooldownCount then
        ignore[cd] = true
        return
    end

    local parent = cd:GetParent()

    -- Blizzard's native Cooldown Manager (Essential/Utility/BuffIcon/BuffBar
    -- CooldownViewer) sets .auraInstanceID on every item frame natively via
    -- CooldownViewerItemDataMixin, with no opt-in intent — indistinguishable
    -- from RealUI_Auras' own icons (Icons.lua) which set the same field on
    -- purpose. Every CooldownViewerItemMixin instance also sets .viewerFrame
    -- (SetViewerFrame), which RealUI's own icons never do, so use that to
    -- exclude native Cooldown Manager frames and avoid fighting the display
    -- Blizzard/RealUI_Auras' CooldownViewer.lua already manages for them.
    if parent and parent.viewerFrame then
        ignore[cd] = true
        return
    end

    local primaryCooldown = parent and (parent.cooldown or parent.Cooldown)
    if parent and primaryCooldown == cd and (parent.AutoCastShine or parent.auraInstanceID) then
        return true
    else
        ignore[cd] = true
    end
end

----------
function CooldownCount:RefreshMod()
    db = self.db.profile
end

function CooldownCount:OnInitialize()
    -- Register with ModuleFramework
    if RealUI.ModuleFramework then
        RealUI:RegisterRealUIModule(MODNAME, "enhancement", {}, {
            description = "Cooldown display enhancements",
            version = "1.0.0"
        })
    end

    self.db = RealUI.db:RegisterNamespace(MODNAME)
    self.db:RegisterDefaults({
        profile = {
            minDuration = 2,
            expiringDuration = 5,
            point = "BOTTOMLEFT",
        },
    })
    db = self.db.profile

    self:SetEnabledState(RealUI:GetModuleEnabled(MODNAME))
end

function CooldownCount:OnEnable()
    -- Use a fallback font since RealUI_Skins is a separate addon
    local fontPath = "Fonts\\FRIZQT__.TTF" -- Default WoW font

    -- Try to get font from RealUI_Skins if it's loaded
    if _G.C_AddOns.IsAddOnLoaded("RealUI_Skins") then
        local skinsDB = _G.RealUI_SkinsDB
        if skinsDB and skinsDB.profile and skinsDB.profile.fonts and skinsDB.profile.fonts.normal then
            fontPath = skinsDB.profile.fonts.normal.path or fontPath
        end
    end

    CD_FONT = {
        font = fontPath,
        size = 10,
        flags = "OUTLINE"
    }

    _G.hooksecurefunc(_G.getmetatable(_G["ActionButton1Cooldown"]).__index, "SetCooldown", function(cd, start, duration, modRate)
        if ShouldHaveTimer(cd) then
            if cd.SetHideCountdownNumbers then
                cd:SetHideCountdownNumbers(true)
            end
            if not cd._timer then
                cd._timer = CreateTimer(cd)
            end
            cd._timer:Start(start, duration, modRate)
        end
    end)
    _G.SetCVar("countdownForCooldowns", 1)
end

function CooldownCount:OnDisable()
    self:UnregisterEvent("ACTIONBAR_UPDATE_COOLDOWN")
    _G.SetCVar("countdownForCooldowns", 0)
end
