local _, _ns = ...
local AC = _G.ArenaCore
if not AC then return end

-- =============================================================================
-- Core/BuffReminder.lua - Buff Reminder system
-- ported from Shade addon because forgetting buffs is embarrassing
-- shows glowing icons for class buffs you or your group are missing
-- =============================================================================

local BR = {}
AC.BuffReminder = BR

------------------------------------------------------------------------
-- class buff definitions -- spellID, icon, and whether its a targeted buff
-- raid-wide buffs: missing on player = show icon
-- targeted buffs: on player but NOT on a group member = show icon
------------------------------------------------------------------------
local CLASS_BUFFS = {
    DRUID       = {{ spellID = 1126,   name = "Mark of the Wild",       targeted = false }},
    MAGE        = {{ spellID = 1459,   name = "Arcane Intellect",       targeted = false }},
    PRIEST      = {{ spellID = 21562,  name = "Power Word: Fortitude",  targeted = false }},
    WARRIOR     = {{ spellID = 6673,   name = "Battle Shout",           targeted = false }},
    EVOKER      = {
        { spellID = 381748, name = "Blessing of the Bronze", targeted = false },
        { spellID = 369459, name = "Source of Magic",        targeted = true  },
    },
    SHAMAN      = {
        { spellID = 974,    name = "Earth Shield", targeted = true  },
        { spellID = 462854, name = "Skyfury",      targeted = false },
    },
}

local CLASS_SELF_BUFFS = {
    SHAMAN = {
        {
            spellIDs = {52127, 79949, 79950, 192106},
            name = "Water Shield or Lightning Shield",
            iconSpellID = 192106,
            skipInBattleground = true,
        },
    },
}

local SHAMAN_EARTH_SHIELD_SPELLIDS = {974, 383648}

local CLASS_WEAPON_ENCHANTS = {
    SHAMAN = { name = "Weapon Imbue", iconSpellID = 318038 },
    ROGUE  = { name = "Poisons",      iconSpellID = 315584 },
}

local ROGUE_POISON_BUFFS = {
    lethal = {315584, 2823, 8679, 381664},
    nonLethal = {381637, 5761, 3408},
}

local WEAPON_EQUIP_LOCS = {
    INVTYPE_WEAPON = true,
    INVTYPE_2HWEAPON = true,
    INVTYPE_WEAPONMAINHAND = true,
    INVTYPE_WEAPONOFFHAND = true,
}

------------------------------------------------------------------------
-- state -- all external, nothing written onto blizzard frames
------------------------------------------------------------------------
local iconFrames    = {}
local anchorFrame   = nil
local eventFrame    = nil
local missingList   = {}
local throttle      = 0
local SCAN_INTERVAL = 0.5
local isDragging    = false

------------------------------------------------------------------------
-- helpers
------------------------------------------------------------------------
local function GetDB()
    if not AC.DB or not AC.DB.profile then
        return { enabled = false, iconSize = 40, spacing = 6, iconZoom = 0.08, posX = nil, posY = nil }
    end
    AC.DB.profile.buffReminder = AC.DB.profile.buffReminder or {}
    local db = AC.DB.profile.buffReminder
    if db.enabled    == nil then db.enabled    = false end
    if db.iconSize   == nil then db.iconSize   = 40    end
    if db.spacing    == nil then db.spacing     = 6     end
    if db.iconZoom   == nil then db.iconZoom   = 0.08  end
    if db.posX       == nil then db.posX       = nil   end
    if db.posY       == nil then db.posY       = nil   end

    if db.posX == nil and db.posY == nil then
        AC.DB.global = AC.DB.global or {}
        AC.DB.global.positions = AC.DB.global.positions or {}
        local globalPos = AC.DB.global.positions.buffReminder
        if globalPos and globalPos.posX ~= nil then
            db.posX = globalPos.posX
            db.posY = globalPos.posY
        end
    end

    return db
end

local function HasBuff(unit, spellID)
    if not UnitExists(unit) then return false end
    local aura = C_UnitAuras.GetUnitAuraBySpellID(unit, spellID)
    return aura ~= nil
end

local function PlayerHasBuff(spellID)
    local aura = nil
    pcall(function()
        aura = C_UnitAuras.GetUnitAuraBySpellID("player", spellID)
        if aura ~= nil then return end
        aura = C_UnitAuras.GetPlayerAuraBySpellID(spellID)
    end)
    return aura ~= nil
end

local function PlayerKnowsSpell(spellID)
    if type(IsPlayerSpell) == "function" then
        return IsPlayerSpell(spellID) == true
    end
    return true
end

local function PlayerHasAnyBuff(spellIDs)
    for _, spellID in ipairs(spellIDs) do
        if PlayerHasBuff(spellID) then
            return true
        end
    end
    return false
end

local function GetGroupUnits()
    local units = {"player"}
    if IsInRaid() then
        for i = 1, GetNumGroupMembers() do
            units[#units + 1] = "raid" .. i
        end
    elseif IsInGroup() then
        for i = 1, GetNumSubgroupMembers() do
            units[#units + 1] = "party" .. i
        end
    end
    return units
end

local function GetSpellIcon(spellID)
    local info = C_Spell.GetSpellInfo(spellID)
    return info and info.iconID or nil
end

local function SlotCanHaveTempEnchant(slotID)
    local itemID = GetInventoryItemID("player", slotID)
    if not itemID then return false end
    local _, _, _, equipLoc = C_Item.GetItemInfoInstant(itemID)
    return equipLoc and WEAPON_EQUIP_LOCS[equipLoc] == true
end

local function HasAnyBuffFromPlayer(unit, spellIDs)
    if not UnitExists(unit) then return false end
    for _, spellID in ipairs(spellIDs) do
        local aura = C_UnitAuras.GetUnitAuraBySpellID(unit, spellID)
        if aura ~= nil and aura.sourceUnit == "player" then
            return true
        end
    end
    return false
end

local function PlayerHasAnyAuraBySpellID(spellIDs)
    if not spellIDs then return false end
    for _, spellID in ipairs(spellIDs) do
        local aura
        pcall(function()
            if C_UnitAuras and C_UnitAuras.GetPlayerAuraBySpellID then
                aura = C_UnitAuras.GetPlayerAuraBySpellID(spellID)
                if aura ~= nil then return end
            end
            if C_UnitAuras and C_UnitAuras.GetUnitAuraBySpellID then
                aura = C_UnitAuras.GetUnitAuraBySpellID("player", spellID)
            end
        end)
        if aura ~= nil then
            return true
        end
    end
    if C_UnitAuras and C_UnitAuras.GetAuraSlots then
        local slots = {C_UnitAuras.GetAuraSlots("player", "HELPFUL")}
        if #slots > 1 then
            for i = 2, #slots do
                local data = C_UnitAuras.GetAuraDataBySlot("player", slots[i])
                if data then
                    local id = data.spellId
                    if id and type(id) == "number" then
                        for _, spellID in ipairs(spellIDs) do
                            if id == spellID then
                                return true
                            end
                        end
                    end
                end
            end
        end
    end
    return false
end

local function AddSelfBuffReminders(playerClass)
    local defs = CLASS_SELF_BUFFS[playerClass]
    if not defs then return end

    local _, instanceType = IsInInstance()
    local inInstancedPvP = instanceType == "arena" or instanceType == "pvp"

    for _, def in ipairs(defs) do
        local shouldCheck = true
        if def.skipInBattleground and inInstancedPvP then
            shouldCheck = false
        end
        if shouldCheck then
            local hasIt = PlayerHasAnyAuraBySpellID(def.spellIDs)
            if (not def.knownSpellID or PlayerKnowsSpell(def.knownSpellID)) and not hasIt then
                local icon = GetSpellIcon(def.iconSpellID or def.spellIDs[1])
                if icon then
                    missingList[#missingList + 1] = {
                        spellID = def.iconSpellID or def.spellIDs[1],
                        icon    = icon,
                        tip     = "You are missing " .. def.name .. "!",
                    }
                end
            end
        end
    end
end

local function AddWeaponEnchantReminder(playerClass)
    local rule = CLASS_WEAPON_ENCHANTS[playerClass]
    if not rule then return end

    if playerClass == "ROGUE" then
        local missingKinds = {}

        if not PlayerHasAnyBuff(ROGUE_POISON_BUFFS.lethal) then
            missingKinds[#missingKinds + 1] = "Lethal"
        end

        if not PlayerHasAnyBuff(ROGUE_POISON_BUFFS.nonLethal) then
            missingKinds[#missingKinds + 1] = "Non-Lethal"
        end

        if #missingKinds == 0 then
            return
        end

        local icon = GetSpellIcon(rule.iconSpellID)
        if icon then
            missingList[#missingList + 1] = {
                spellID = rule.iconSpellID,
                icon    = icon,
                tip     = rule.name .. " missing: " .. table.concat(missingKinds, " / ") .. "!",
            }
        end

        return
    end

    if playerClass == "SHAMAN" then
        local spec = GetSpecialization()
        local mainSlot = INVSLOT_MAINHAND or 16
        local offSlot = INVSLOT_OFFHAND or 17
        local hasMainHandEnchant, _, _, _, hasOffHandEnchant = GetWeaponEnchantInfo()
        local missingSlots = {}
        local imbueSpellID = rule.iconSpellID

        if spec == 3 then
            imbueSpellID = 382021
            if SlotCanHaveTempEnchant(mainSlot) and not hasMainHandEnchant then
                missingSlots[#missingSlots + 1] = "Main Hand"
            end
        elseif spec == 1 then
            imbueSpellID = 318038
            if SlotCanHaveTempEnchant(mainSlot) and not hasMainHandEnchant then
                missingSlots[#missingSlots + 1] = "Main Hand"
            end
        elseif spec == 2 then
            if SlotCanHaveTempEnchant(mainSlot) and not hasMainHandEnchant then
                missingSlots[#missingSlots + 1] = "Main Hand"
            end
            if SlotCanHaveTempEnchant(offSlot) and not hasOffHandEnchant then
                missingSlots[#missingSlots + 1] = "Off Hand"
            end
        end

        if #missingSlots == 0 then
            return
        end

        local icon = GetSpellIcon(imbueSpellID)
        if not icon then
            icon = GetInventoryItemTexture("player", mainSlot) or GetInventoryItemTexture("player", offSlot)
        end

        if icon then
            missingList[#missingList + 1] = {
                spellID = imbueSpellID,
                icon    = icon,
                tip     = rule.name .. " missing on " .. table.concat(missingSlots, " / ") .. "!",
            }
        end

        return
    end

    local mainSlot = INVSLOT_MAINHAND or 16
    local offSlot = INVSLOT_OFFHAND or 17
    local hasMainHandEnchant, _, _, _, hasOffHandEnchant = GetWeaponEnchantInfo()
    local missingSlots = {}

    if SlotCanHaveTempEnchant(mainSlot) and not hasMainHandEnchant then
        missingSlots[#missingSlots + 1] = "Main Hand"
    end

    if SlotCanHaveTempEnchant(offSlot) and not hasOffHandEnchant then
        missingSlots[#missingSlots + 1] = "Off Hand"
    end

    if #missingSlots == 0 then
        return
    end

    local icon = GetSpellIcon(rule.iconSpellID)
    if not icon then
        icon = GetInventoryItemTexture("player", mainSlot) or GetInventoryItemTexture("player", offSlot)
    end

    if icon then
        missingList[#missingList + 1] = {
            spellID = rule.iconSpellID,
            icon    = icon,
            tip     = rule.name .. " missing on " .. table.concat(missingSlots, " / ") .. "!",
        }
    end
end

------------------------------------------------------------------------
-- scanning -- figures out which buffs are missing and why
------------------------------------------------------------------------
local function ScanMissing()
    missingList = {}
    local db = GetDB()
    if not db.enabled then return end

    local _, playerClass = UnitClass("player")
    if not playerClass then return end

    local defs = CLASS_BUFFS[playerClass]

    local inGroup = IsInGroup() or IsInRaid()
    local inBattleground = C_PvP.IsBattleground()

    if defs then
        for _, def in ipairs(defs) do
            local icon = GetSpellIcon(def.spellID)
            if icon then
                if not def.targeted then
                    if not PlayerHasBuff(def.spellID) then
                        missingList[#missingList + 1] = {
                            spellID = def.spellID,
                            icon    = icon,
                            tip     = "You are missing " .. def.name .. "!",
                        }
                    elseif inGroup and not inBattleground then
                        local units = GetGroupUnits()
                        for _, unit in ipairs(units) do
                            if UnitExists(unit) and not UnitIsDeadOrGhost(unit) and UnitIsConnected(unit) then
                                if not HasBuff(unit, def.spellID) then
                                    missingList[#missingList + 1] = {
                                        spellID = def.spellID,
                                        icon    = icon,
                                        tip     = def.name .. " missing on group member(s)!",
                                    }
                                    break
                                end
                            end
                        end
                    end
                else
                    if inGroup and not inBattleground then
                        local anyoneHasIt = false
                        local units = GetGroupUnits()
                        for _, unit in ipairs(units) do
                            if UnitExists(unit) and not UnitIsDeadOrGhost(unit) and UnitIsConnected(unit) then
                                if playerClass == "SHAMAN" and def.spellID == 974 then
                                    if HasAnyBuffFromPlayer(unit, SHAMAN_EARTH_SHIELD_SPELLIDS) then
                                        anyoneHasIt = true
                                        break
                                    end
                                elseif HasBuff(unit, def.spellID) then
                                    anyoneHasIt = true
                                    break
                                end
                            end
                        end
                        if not anyoneHasIt then
                            missingList[#missingList + 1] = {
                                spellID = def.spellID,
                                icon    = icon,
                                tip     = def.name .. " is not on any group member -- pick a target!",
                            }
                        end
                    elseif playerClass == "SHAMAN" and def.spellID == 974 and (PlayerKnowsSpell(974) or PlayerKnowsSpell(383648)) then
                        if not PlayerHasAnyBuff(SHAMAN_EARTH_SHIELD_SPELLIDS) then
                            missingList[#missingList + 1] = {
                                spellID = 974,
                                icon    = icon,
                                tip     = "You are missing " .. def.name .. "!",
                            }
                        end
                    end
                end
            end
        end
    end

    AddSelfBuffReminders(playerClass)
    AddWeaponEnchantReminder(playerClass)
end

------------------------------------------------------------------------
-- display -- icon frames with glow border
------------------------------------------------------------------------
local function EnsureAnchor()
    if anchorFrame then return end

    anchorFrame = CreateFrame("Frame", "ArenaCoreBuffReminderAnchor", UIParent)
    anchorFrame:SetSize(1, 1)
    anchorFrame:SetFrameStrata("MEDIUM")
    anchorFrame:SetFrameLevel(50)
    anchorFrame:SetClampedToScreen(true)

    local db = GetDB()
    if db.posX and db.posY then
        anchorFrame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", db.posX, db.posY)
    else
        anchorFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 150)
    end

    -- draggable -- left-click + drag, saves position in real time
    anchorFrame:SetMovable(true)
    anchorFrame:EnableMouse(false)
end

local function CreateIconFrame(index)
    local f = CreateFrame("Frame", "ArenaCoreBuffReminderIcon" .. index, anchorFrame, "BackdropTemplate")
    f:SetFrameStrata("MEDIUM")
    f:SetFrameLevel(51)

    -- icon texture
    f.icon = f:CreateTexture(nil, "ARTWORK")
    f.icon:SetAllPoints()

    -- glow border -- IconAlert scales cleanly with the frame and looks way nicer
    f.glowBorder = f:CreateTexture(nil, "OVERLAY")
    f.glowBorder:SetTexture("Interface\\SpellActivationOverlay\\IconAlert")
    f.glowBorder:SetTexCoord(0.00781250, 0.50781250, 0.27734375, 0.52734375)
    f.glowBorder:SetPoint("TOPLEFT", f.icon, "TOPLEFT", -14, 14)
    f.glowBorder:SetPoint("BOTTOMRIGHT", f.icon, "BOTTOMRIGHT", 14, -14)
    f.glowBorder:SetBlendMode("ADD")
    f.glowBorder:SetVertexColor(0.8, 0.4, 0.9, 0.85)

    -- pulse animation for that "hey dummy you forgot" vibe
    f.glowAnim = f.glowBorder:CreateAnimationGroup()
    f.glowAnim:SetLooping("BOUNCE")
    local fadeOut = f.glowAnim:CreateAnimation("Alpha")
    fadeOut:SetFromAlpha(0.85)
    fadeOut:SetToAlpha(0.35)
    fadeOut:SetDuration(0.8)
    fadeOut:SetSmoothing("IN_OUT")
    f.glowAnim:Play()

    -- tooltip on hover
    f:EnableMouse(true)
    f:SetScript("OnEnter", function(self)
        if self.tipText then
            GameTooltip:SetOwner(self, "ANCHOR_BOTTOM")
            GameTooltip:AddLine(self.tipText, 1, 1, 1, true)
            GameTooltip:Show()
        end
    end)
    f:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    -- drag support -- each icon drags the whole anchor
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", function()
        isDragging = true
        anchorFrame:StartMoving()
    end)
    f:SetScript("OnDragStop", function()
        anchorFrame:StopMovingOrSizing()
        isDragging = false
        local db = GetDB()
        local cx, cy = anchorFrame:GetCenter()
        if cx and cy then
            db.posX = cx
            db.posY = cy

            -- Save to global for account-wide sync
            AC.DB.global = AC.DB.global or {}
            AC.DB.global.positions = AC.DB.global.positions or {}
            AC.DB.global.positions.buffReminder = AC.DB.global.positions.buffReminder or {}
            AC.DB.global.positions.buffReminder.posX = cx
            AC.DB.global.positions.buffReminder.posY = cy
        end
        -- catch any buff changes that happened while we were dragging
        BR:Refresh()
    end)

    f:Hide()
    return f
end

local function LayoutIcons()
    local db = GetDB()
    local size = db.iconSize or 40
    local spacing = db.spacing or 6
    local zoom = db.iconZoom or 0.08

    EnsureAnchor()

    -- hide all existing icons first
    for _, f in ipairs(iconFrames) do
        f:Hide()
    end

    if not db.enabled or #missingList == 0 then
        if anchorFrame then anchorFrame:Hide() end
        return
    end

    -- show anchor and resize to fit the row
    anchorFrame:Show()
    local totalWidth = (#missingList * size) + ((#missingList - 1) * spacing)
    anchorFrame:SetSize(totalWidth, size)

    for i, info in ipairs(missingList) do
        local f = iconFrames[i]
        if not f then
            f = CreateIconFrame(i)
            iconFrames[i] = f
        end

        f:SetSize(size, size)
        f:ClearAllPoints()
        if i == 1 then
            f:SetPoint("LEFT", anchorFrame, "LEFT", 0, 0)
        else
            f:SetPoint("LEFT", iconFrames[i - 1], "RIGHT", spacing, 0)
        end

        f.icon:SetTexture(info.icon)
        f.icon:SetTexCoord(zoom, 1 - zoom, zoom, 1 - zoom)
        f.tipText = info.tip

        -- restart glow animation
        if f.glowAnim and not f.glowAnim:IsPlaying() then
            f.glowAnim:Play()
        end

        f:Show()
    end

    -- hide any excess icon frames
    for j = #missingList + 1, #iconFrames do
        if iconFrames[j] then
            iconFrames[j]:Hide()
        end
    end
end

------------------------------------------------------------------------
-- public API
------------------------------------------------------------------------
function BR:Refresh()
    local db = GetDB()
    if not db.enabled then return end

    ScanMissing()
    LayoutIcons()
end

function BR:Enable()
    local db = GetDB()
    db.enabled = true
    self:Refresh()
end

function BR:Disable()
    local db = GetDB()
    db.enabled = false
    for _, f in ipairs(iconFrames) do
        f:Hide()
    end
end

function BR:UpdateIconSize(val)
    local db = GetDB()
    db.iconSize = val
    self:Refresh()
end

function BR:UpdateSpacing(val)
    local db = GetDB()
    db.spacing = val
    self:Refresh()
end

function BR:UpdateIconZoom(val)
    local db = GetDB()
    db.iconZoom = val
    self:Refresh()
end

------------------------------------------------------------------------
-- initialization -- hooks events, starts the scan loop
------------------------------------------------------------------------
function BR:Initialize()
    if self._initialized then return end
    self._initialized = true

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

    EnsureAnchor()

    eventFrame = CreateFrame("Frame")
    eventFrame:RegisterEvent("UNIT_AURA")
    eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
    eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
    eventFrame:RegisterEvent("UNIT_INVENTORY_CHANGED")
    eventFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")

    eventFrame:SetScript("OnEvent", function(_, event, unit)
        local db = GetDB()
        if not db.enabled then return end

        if isDragging then return end
        -- only care about player / group aura changes
        if event == "UNIT_AURA" then
            if unit ~= "player" and not unit:match("^party") and not unit:match("^raid") then
                return
            end
            -- in combat, aura events fire a LOT -- coalesce into one refresh instead of stacking timers
            if InCombatLockdown() then
                if not BR._refreshPending then
                    BR._refreshPending = true
                    C_Timer.After(0.2, function()
                        BR._refreshPending = false
                        BR:Refresh()
                    end)
                end
                return
            end
        elseif event == "UNIT_INVENTORY_CHANGED" then
            if unit ~= "player" then return end
        end
        BR:Refresh()
    end)

    self:Refresh()
end

-- called when the user toggles on/off from settings without reload
function BR:Toggle(enabled)
    if enabled then
        self._initialized = false
        self:Initialize()
        self:Enable()
    else
        local db = GetDB()
        db.enabled = false
        for _, f in ipairs(iconFrames) do
            f:Hide()
        end
        if eventFrame then
            eventFrame:UnregisterEvent("UNIT_AURA")
            eventFrame:UnregisterEvent("GROUP_ROSTER_UPDATE")
            eventFrame:UnregisterEvent("PLAYER_ENTERING_WORLD")
            eventFrame:UnregisterEvent("PLAYER_REGEN_ENABLED")
            eventFrame:UnregisterEvent("UNIT_INVENTORY_CHANGED")
            eventFrame:UnregisterEvent("ZONE_CHANGED_NEW_AREA")
        end
    end
end
