local addonName, ns = ...

-- HealerManaWatch Display Module
-- Shows mana bars for healers (or full group) with mana-regen buff detection.

local HMW = {}
ns.HealerManaWatch = HMW

local function GetBorderColor(db)
    if not db then return 0, 207/255, 1.0 end
    if db.borderUseClassColor then
        local cc = RAID_CLASS_COLORS and RAID_CLASS_COLORS[select(2, UnitClass("player"))]
        if cc then return cc.r, cc.g, cc.b end
    end
    local r = db.borderColorR or 0
    local g = db.borderColorG or (207/255)
    local b = db.borderColorB or 1.0
    return r, g, b
end


local W = ns.Widgets

local MAX_UNITS    = 40
local UPD_INTERVAL = 0.5
local BAR_TEX      = "Interface\\TargetingFrame\\UI-StatusBar"
local INSET        = 4

HMW.ALL_MANA_BUFFS = {
    { id = 29166,  name = "Innervate",          class = "Druid"   },
    { id = 64901,  name = "Symbol of Hope",     class = "Priest"  },
    { id = 16191,  name = "Mana Tide Totem",    class = "Shaman"  },
    { id = 388019, name = "Blessing of Winter", class = "Paladin" },
}

local activeBuffs = {}
local anchor      = nil
local rows        = {}
local elapsed_acc = 0

---------------------------------------------------------------------------
-- DB / font helpers
---------------------------------------------------------------------------

local function GetDB()
    return BrightQoL.healerMana
end

local function GetFont()
    local db = GetDB()
    return db.font or "Fonts\\FRIZQT__.TTF"
end

local function GetFontFlags()
    local db = GetDB()
    if not db.fontOutline then return "" end
    local style = db.fontOutlineStyle or "OUTLINE"
    return style
end

local function GetNameColor(unit)
    local _, cls = UnitClass(unit)
    if cls and RAID_CLASS_COLORS and RAID_CLASS_COLORS[cls] then
        local c = RAID_CLASS_COLORS[cls]
        return c.r, c.g, c.b
    end
    return 0.82, 0.82, 0.82
end

local function GetManaBuffOnUnit(unit)
    local foundName, foundId = nil, nil
    pcall(function()
        for i = 1, 40 do
            local aura = C_UnitAuras.GetBuffDataByIndex(unit, i)
            if not aura then break end
            local name = activeBuffs[aura.spellId]
            if name then foundName = name; foundId = aura.spellId; return end
        end
    end)
    return foundName, foundId
end

local function HasMana(unit)
    if UnitPowerType(unit) == 0 then return true end
    local _, cls = UnitClass(unit)
    if cls == "DRUID" or cls == "SHAMAN" then return true end
    return false
end

local function GetGroupUnits()
    local t = {}
    if IsInRaid() then
        for i = 1, MAX_UNITS do
            local u = "raid" .. i
            if UnitExists(u) then t[#t + 1] = u end
        end
    else
        t[#t + 1] = "player"
        for i = 1, 4 do
            local u = "party" .. i
            if UnitExists(u) then t[#t + 1] = u end
        end
    end
    return t
end

---------------------------------------------------------------------------
-- Backdrop
---------------------------------------------------------------------------

function HMW:ApplyBackdrop()
    if not anchor then return end
    local db = GetDB()
    local wantBg  = db.showBackground == true
    local wantBrd = db.showBorder     == true

    if not wantBg and not wantBrd then
        anchor:SetBackdrop(nil)
        return
    end

    anchor:SetBackdrop({
        bgFile   = wantBg  and [[Interface\Buttons\WHITE8x8]] or nil,
        edgeFile = wantBrd and [[Interface\Buttons\WHITE8x8]] or nil,
        edgeSize = wantBrd and 1 or 0,
        insets   = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    if wantBg  then anchor:SetBackdropColor(0.05, 0.05, 0.08, 0.85) end
    if wantBrd then local _r,_g,_b = GetBorderColor(db); anchor:SetBackdropBorderColor(_r,_g,_b, 0.5) end
end

---------------------------------------------------------------------------
-- Row pool
---------------------------------------------------------------------------

local function CreateRow(i)
    local db    = GetDB()
    local font  = GetFont()
    local flags = GetFontFlags()
    local r = {}

    r.frame = CreateFrame("Frame", nil, anchor)
    r.frame:SetHeight(db.barHeight)

    r.bg = r.frame:CreateTexture(nil, "BACKGROUND")
    r.bg:SetAllPoints()
    r.bg:SetColorTexture(0, 0, 0, 0.25)
    r.bg:SetShown(GetDB().showBackground == true)

    r.bar = CreateFrame("StatusBar", nil, r.frame)
    r.bar:SetAllPoints()
    r.bar:SetStatusBarTexture(BAR_TEX)
    r.bar:SetMinMaxValues(0, 100)
    r.bar:SetValue(100)
    r.bar:SetStatusBarColor(0, 0, 0, 0)  -- transparent; bar is only a text container

    r.name = r.bar:CreateFontString(nil, "OVERLAY")
    r.name:SetFont(font, db.fontSize, flags)
    r.name:SetPoint("LEFT", 4, 0)
    r.name:SetJustifyH("LEFT")
    r.name:SetShadowOffset(1, -1)

    r.pct = r.bar:CreateFontString(nil, "OVERLAY")
    r.pct:SetFont(font, db.fontSize, flags)
    r.pct:SetPoint("RIGHT", -3, 0)
    r.pct:SetJustifyH("RIGHT")
    r.pct:SetShadowOffset(1, -1)

    r.buff = r.bar:CreateFontString(nil, "OVERLAY")
    r.buff:SetFont(font, math.max(db.fontSize - 1, 7), flags)
    r.buff:SetShadowOffset(1, -1)
    r.buff:SetTextColor(0.2, 1.0, 0.3)
    HMW:PositionBuffLabel(r, db.buffRight)

    local iconSize = math.max(db.barHeight - 2, 8)
    r.buffIcon = r.bar:CreateTexture(nil, "OVERLAY")
    r.buffIcon:SetSize(iconSize, iconSize)
    HMW:PositionBuffIcon(r, db.buffRight)
    r.buffIcon:Hide()

    r.buffIconFrame = CreateFrame("Frame", nil, r.bar)
    r.buffIconFrame:SetAllPoints(r.buffIcon)
    r.buffIconFrame:EnableMouse(true)
    r.buffIconFrame:SetScript("OnEnter", function(self)
        if self.spellId then
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetSpellByID(self.spellId)
            GameTooltip:Show()
        end
    end)
    r.buffIconFrame:SetScript("OnLeave", function() GameTooltip:Hide() end)
    r.buffIconFrame.spellId = nil
    r.buffIconFrame:Hide()

    r.frame:Hide()
    rows[i] = r
    return r
end

local function GetRow(i)
    return rows[i] or CreateRow(i)
end

function HMW:PositionBuffLabel(r, buffRight)
    r.buff:ClearAllPoints()
    if buffRight then
        r.buff:SetPoint("LEFT", r.pct, "RIGHT", 2, 0)
        r.buff:SetJustifyH("LEFT")
    else
        r.buff:SetPoint("RIGHT", r.name, "LEFT", -2, 0)
        r.buff:SetJustifyH("RIGHT")
    end
end

function HMW:PositionBuffIcon(r, buffRight)
    r.buffIcon:ClearAllPoints()
    if buffRight then
        r.buffIcon:SetPoint("LEFT", r.pct, "RIGHT", 2, 0)
    else
        r.buffIcon:SetPoint("RIGHT", r.name, "LEFT", -2, 0)
    end
end

function HMW:RefreshRows()
    local db   = GetDB()
    local font = GetFont()
    local fs   = db.fontSize
    local ff   = GetFontFlags()
    local showBg = db.showBackground == true
    for _, r in ipairs(rows) do
        r.frame:SetHeight(db.barHeight)
        r.name:SetFont(font, fs, ff)
        r.pct:SetFont(font, fs, ff)
        r.buff:SetFont(font, math.max(fs - 1, 7), ff)
        self:PositionBuffLabel(r, db.buffRight)
        local iconSize = math.max(db.barHeight - 2, 8)
        r.buffIcon:SetSize(iconSize, iconSize)
        self:PositionBuffIcon(r, db.buffRight)
        r.buffIconFrame:SetAllPoints(r.buffIcon)
        r.bg:SetAlpha(showBg and 1 or 0)
        r.bg:SetShown(showBg)
    end
end

local function LayoutRows(count)
    local db = GetDB()
    for i = 1, count do
        local r = rows[i]
        r.frame:ClearAllPoints()
        r.frame:SetPoint("TOPLEFT", anchor, "TOPLEFT",
            INSET, -(INSET + (i - 1) * (db.barHeight + db.barGap)))
        r.frame:SetWidth(db.width - INSET * 2)
    end
    local h = count * (db.barHeight + db.barGap) - db.barGap + INSET * 2
    anchor:SetSize(db.width, math.max(h, db.barHeight + INSET * 2))
end

---------------------------------------------------------------------------
-- Position / scale
---------------------------------------------------------------------------

function HMW:ApplyPosition()
    if not anchor then return end
    local db = GetDB()
    anchor:ClearAllPoints()
    -- Use same value for both point and relativePoint (like CombatTimer)
    -- so position is always relative to the same UIParent anchor
    local pt = db.point or "TOPLEFT"
    anchor:SetPoint(pt, UIParent, pt, db.x or 0, db.y or -200)
    anchor:SetScale(db.scale or 1.0)
end

---------------------------------------------------------------------------
-- Active buffs
---------------------------------------------------------------------------

function HMW:RebuildActiveBuffs()
    local db = GetDB()
    wipe(activeBuffs)
    for _, info in ipairs(self.ALL_MANA_BUFFS) do
        if db.trackedBuffs and db.trackedBuffs[info.id] then
            activeBuffs[info.id] = info.name
        end
    end
end

---------------------------------------------------------------------------
-- Startup event handler
---------------------------------------------------------------------------

local hmwLoader = CreateFrame("Frame")
hmwLoader:RegisterEvent("PLAYER_LOGIN")
hmwLoader:RegisterEvent("GROUP_ROSTER_UPDATE")
hmwLoader:RegisterEvent("PLAYER_ENTERING_WORLD")
hmwLoader:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
hmwLoader:SetScript("OnEvent", function(self, event)
    if event == "PLAYER_LOGIN" then
        if BrightQoL.healerMana and BrightQoL.healerMana.enabled then
            HMW:Init()
        end
        return
    end

    local function doRefresh()
        local db = BrightQoL.healerMana
        if not db or not db.enabled then return end
        if not anchor then
            HMW:Init()
        else
            HMW:Refresh()
        end
    end

    if event == "PLAYER_SPECIALIZATION_CHANGED" then
        -- Delay slightly so GetSpecialization() reflects the new spec
        C_Timer.After(0.3, doRefresh)
    else
        doRefresh()
    end
end)

---------------------------------------------------------------------------
-- Helpers
---------------------------------------------------------------------------

local function GetManaPct(unit)
    -- Use the 12.0.1-correct form with CurveConstants to avoid taint
    return UnitPowerPercent(unit, Enum.PowerType.Mana, true, CurveConstants.ScaleTo100) or 0
end


local function OnUpdate(self, elapsed)
    elapsed_acc = elapsed_acc + elapsed
    if elapsed_acc < UPD_INTERVAL then return end
    elapsed_acc = 0

    local db = GetDB()
    if not db then return end

    -- Unlock/preview: show a visible box so the player can position the frame.
    -- Force backdrop on during unlock regardless of settings.
    if db.unlock then
        anchor:SetBackdrop({
            bgFile   = [[Interface\Buttons\WHITE8x8]],
            edgeSize = 0,
            insets   = { left = 1, right = 1, top = 1, bottom = 1 },
        })
        anchor:SetBackdropColor(0.05, 0.05, 0.08, 0.85)

        local r   = GetRow(1)
        local pct = GetManaPct("player")
        r.bar:SetValue(pct)
        r.bar:Show()
        r.bg:Hide()
        r.name:SetText(UnitName("player") or "Player")
        r.name:SetTextColor(GetNameColor("player"))
        r.pct:SetFormattedText("%.f%%", pct)
        r.pct:SetTextColor(1, 1, 1)
        r.buff:SetText(""); r.buff:Hide()
        r.buffIcon:Hide(); r.buffIconFrame:Hide()
        r.frame:Show()
        for i = 2, #rows do rows[i].frame:Hide() end
        LayoutRows(1)
        anchor:Show()
        return
    end

    -- Restore normal backdrop when locked
    HMW:ApplyBackdrop()

    if not db.enabled then anchor:Hide(); return end

    -- Determine if the player is currently specced as a healer
    local playerSpecRole = GetSpecializationRole(GetSpecialization())
    local playerIsHealer = (playerSpecRole == "HEALER")

    local inGroup = GetNumGroupMembers() > 1

    -- Check if there is at least one healer in the group
    local groupHasHealer = false
    if inGroup then
        local groupSize = GetNumGroupMembers()
        local isRaid = IsInRaid()
        for i = 1, groupSize do
            local u = isRaid and ("raid"..i) or (i == 1 and "player" or "party"..(i-1))
            if UnitExists(u) and UnitGroupRolesAssigned(u) == "HEALER" then
                groupHasHealer = true
                break
            end
        end
    end

    -- Visibility rule:
    -- healer → always show
    -- non-healer → only show if in group and group has a healer
    local shouldShow = playerIsHealer or (inGroup and groupHasHealer)
    if not shouldShow then
        anchor:Hide(); return
    end

    local units = GetGroupUnits()
    local count = 0

    for _, u in ipairs(units) do
        if UnitExists(u) and UnitIsConnected(u) then
            -- Show healers; if no assigned role, fall back to HasMana
            local role = UnitGroupRolesAssigned(u)
            local show = (role == "HEALER") or
                         ((role == "" or role == "NONE") and HasMana(u))

            if show then
                count = count + 1
                local r   = GetRow(count)
                local pct = GetManaPct(u)

                r.bar:SetValue(pct or 0)
                r.bar:Show()
                r.bg:SetShown(db.showBackground == true)
                r.name:SetText(UnitName(u) or "?")
                r.name:SetTextColor(GetNameColor(u))
                r.pct:SetFormattedText("%.f%%", pct or 0)
                r.pct:SetTextColor(1, 1, 1)

                local buffName, buffSpellId = GetManaBuffOnUnit(u)
                if buffName then
                    if db.buffIcon and buffSpellId then
                        local icon = C_Spell.GetSpellTexture(buffSpellId)
                        if icon then
                            r.buffIcon:SetTexture(icon)
                            r.buffIcon:Show()
                            r.buffIconFrame.spellId = buffSpellId
                            r.buffIconFrame:Show()
                        else
                            r.buffIcon:Hide(); r.buffIconFrame:Hide()
                        end
                        r.buff:SetText(""); r.buff:Hide()
                    else
                        r.buff:SetText(db.buffRight
                            and (" < " .. buffName)
                            or  (buffName .. " > "))
                        r.buff:Show()
                        r.buffIcon:Hide(); r.buffIconFrame:Hide()
                    end
                else
                    r.buff:SetText(""); r.buff:Hide()
                    r.buffIcon:Hide()
                    r.buffIconFrame.spellId = nil; r.buffIconFrame:Hide()
                end
                r.frame:Show()
            end
        end
    end

    for i = count + 1, #rows do rows[i].frame:Hide() end

    if count > 0 then
        anchor:Show()
        LayoutRows(count)
    else
        anchor:Hide()
    end
end

---------------------------------------------------------------------------
-- Init / Enable / Disable / Refresh
---------------------------------------------------------------------------

function HMW:Init()
    if anchor then
        self:ApplyPosition()
        self:RebuildActiveBuffs()
        self:Refresh()
        return
    end

    anchor = CreateFrame("Frame", "BrightQoL_HMW_Anchor", UIParent, "BackdropTemplate")
    anchor:SetSize(160, 20)
    anchor:SetClampedToScreen(true)
    anchor:SetFrameStrata("MEDIUM")

    self:ApplyPosition()
    self:RebuildActiveBuffs()
    self:ApplyBackdrop()

    -- Save both point and relPoint on drag stop
    W.MakeDraggable(anchor, {
        db          = GetDB(),
        enableMouse = false,
        userPlaced  = false,
        anchorToKey = "relPoint",
    })

    anchor:SetScript("OnUpdate", OnUpdate)

    ns.SettingsIO:RegisterRefresh("healerMana", function()
        HMW:Refresh()
    end)

    -- Apply initial visibility (Refresh handles show/hide)
    self:Refresh()
end

function HMW:Enable()
    if not anchor then self:Init(); return end
    anchor:SetScript("OnUpdate", OnUpdate)
    -- Force immediate show if unlock is active
    if GetDB().unlock then anchor:Show() end
end

function HMW:Disable()
    if anchor then
        anchor:SetScript("OnUpdate", nil)
        for i = 1, #rows do rows[i].frame:Hide() end
        anchor:Hide()
    end
end

function HMW:Refresh()
    if not anchor then return end
    local db = GetDB()
    self:ApplyBackdrop()
    self:RebuildActiveBuffs()
    self:RefreshRows()
    anchor:SetScale(db.scale or 1.0)
    anchor:EnableMouse(db.unlock or false)

    if db.enabled then
        -- Always keep OnUpdate running when enabled so spec changes are detected
        anchor:SetScript("OnUpdate", OnUpdate)
        if db.unlock then anchor:Show() end
    else
        self:Disable()
    end
end
