SFrames.Raid = {}
local _A = SFrames.ActiveTheme

local RAID_FRAME_WIDTH = 60
local RAID_FRAME_HEIGHT = 40
local GROUP_PADDING = 8
local UNIT_PADDING = 2

local RAID_UNIT_LOOKUP = {}
for i = 1, 40 do RAID_UNIT_LOOKUP["raid" .. i] = true end

local function GetIncomingHeals(unit)
    return SFrames:GetIncomingHeals(unit)
end

function SFrames.Raid:GetMetrics()
    local db = SFramesDB or {}

    local width = tonumber(db.raidFrameWidth) or RAID_FRAME_WIDTH
    width = math.max(30, math.min(150, width))

    local height = tonumber(db.raidFrameHeight) or RAID_FRAME_HEIGHT
    height = math.max(20, math.min(80, height))

    local healthHeight = tonumber(db.raidHealthHeight) or math.floor((height - 3) * 0.8)
    healthHeight = math.max(10, math.min(height - 6, healthHeight))

    local powerHeight = height - healthHeight - 3
    if not db.raidShowPower then
        powerHeight = 0
        healthHeight = height - 2
    end

    local hgap = tonumber(db.raidHorizontalGap) or UNIT_PADDING
    local vgap = tonumber(db.raidVerticalGap) or UNIT_PADDING
    local groupGap = tonumber(db.raidGroupGap) or GROUP_PADDING

    local nameFont = tonumber(db.raidNameFontSize) or 10
    local valueFont = tonumber(db.raidValueFontSize) or 9

    return {
        width = width,
        height = height,
        healthHeight = healthHeight,
        powerHeight = powerHeight,
        horizontalGap = hgap,
        verticalGap = vgap,
        groupGap = groupGap,
        nameFont = nameFont,
        valueFont = valueFont,
        showPower = db.raidShowPower ~= false,
    }
end

function SFrames.Raid:ApplyFrameStyle(frame, metrics)
    if not frame then return end

    frame:SetWidth(metrics.width)
    frame:SetHeight(metrics.height)

    if frame.health then
        frame.health:ClearAllPoints()
        frame.health:SetPoint("TOPLEFT", frame, "TOPLEFT", 1, -1)
        frame.health:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -1, -1)
        frame.health:SetHeight(metrics.healthHeight)
    end

    if frame.healthBGFrame then
        frame.healthBGFrame:ClearAllPoints()
        frame.healthBGFrame:SetPoint("TOPLEFT", frame.health, "TOPLEFT", -1, 1)
        frame.healthBGFrame:SetPoint("BOTTOMRIGHT", frame.health, "BOTTOMRIGHT", 1, -1)
    end

    if frame.power then
        if metrics.showPower then
            frame.power:Show()
            if frame.powerBGFrame then frame.powerBGFrame:Show() end
            frame.power:ClearAllPoints()
            frame.power:SetPoint("TOPLEFT", frame.health, "BOTTOMLEFT", 0, -1)
            frame.power:SetPoint("TOPRIGHT", frame.health, "BOTTOMRIGHT", 0, 0)
            frame.power:SetHeight(metrics.powerHeight)
        else
            frame.power:Hide()
            if frame.powerBGFrame then frame.powerBGFrame:Hide() end
        end
    end

    local outline = (SFrames and SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE"
    local fontPath = SFrames:GetFont()

    if frame.nameText then
        frame.nameText:SetFont(fontPath, metrics.nameFont, outline)
    end
    if frame.healthText then
        frame.healthText:SetFont(fontPath, metrics.valueFont, outline)
    end
end

function SFrames.Raid:ApplyLayout()
    if not (self.parent and self._framesBuilt) then return end

    local metrics = self:GetMetrics()
    self.metrics = metrics
    local mode = (SFramesDB and SFramesDB.raidLayout) or "horizontal"
    local showLabel = SFramesDB and SFramesDB.raidShowGroupLabel ~= false

    local LABEL_H = 16  -- px above each column (horizontal mode)
    local LABEL_W = 16  -- px left of each row (vertical mode)

    for g = 1, 8 do
        local groupParent = self.groups[g]
        groupParent:ClearAllPoints()

        if mode == "horizontal" then
            local extraTop = showLabel and LABEL_H or 0
            if g == 1 then
                groupParent:SetPoint("TOPLEFT", self.parent, "TOPLEFT", 0, 0)
            else
                groupParent:SetPoint("TOPLEFT", self.groups[g-1], "TOPRIGHT", metrics.groupGap, 0)
            end
            groupParent:SetWidth(metrics.width)
            groupParent:SetHeight(extraTop + (metrics.height * 5) + (metrics.verticalGap * 4))
        else
            local extraLeft = showLabel and LABEL_W or 0
            if g == 1 then
                groupParent:SetPoint("TOPLEFT", self.parent, "TOPLEFT", 0, 0)
            else
                groupParent:SetPoint("TOPLEFT", self.groups[g-1], "BOTTOMLEFT", 0, -metrics.groupGap)
            end
            groupParent:SetWidth(extraLeft + (metrics.width * 5) + (metrics.horizontalGap * 4))
            groupParent:SetHeight(metrics.height)
        end

        -- Position label; visibility is controlled per-group in UpdateAll
        local lbl = self.groupLabels and self.groupLabels[g]
        if lbl then
            lbl:ClearAllPoints()
            if showLabel then
                if mode == "horizontal" then
                    -- Place label at the very top of the column, centered horizontally
                    -- Frames start at -LABEL_H, so the strip [0, -LABEL_H] is exclusively for the label
                    lbl:SetPoint("TOP", groupParent, "TOP", 0, -2)
                else
                    -- Vertically centered on the left strip, centered horizontally within it
                    lbl:SetPoint("CENTER", groupParent, "LEFT", LABEL_W / 2, 0)
                end
            else
                lbl:Hide()
            end
        end

        for i = 1, 5 do
            local index = (g - 1) * 5 + i
            local f = self.frames[index].frame
            f:ClearAllPoints()

            if mode == "horizontal" then
                local extraTop = showLabel and LABEL_H or 0
                if i == 1 then
                    -- Frames start below the label strip
                    f:SetPoint("TOPLEFT", groupParent, "TOPLEFT", 0, -extraTop)
                else
                    f:SetPoint("TOPLEFT", self.frames[index-1].frame, "BOTTOMLEFT", 0, -metrics.verticalGap)
                end
            else
                local extraLeft = showLabel and LABEL_W or 0
                if i == 1 then
                    f:SetPoint("TOPLEFT", groupParent, "TOPLEFT", extraLeft, 0)
                else
                    f:SetPoint("TOPLEFT", self.frames[index-1].frame, "TOPRIGHT", metrics.horizontalGap, 0)
                end
            end
        end
    end

    local extraTop = showLabel and LABEL_H or 0
    local extraLeft = showLabel and LABEL_W or 0
    if mode == "horizontal" then
        self.parent:SetWidth((metrics.width * 8) + (metrics.groupGap * 7))
        self.parent:SetHeight(extraTop + (metrics.height * 5) + (metrics.verticalGap * 4))
    else
        self.parent:SetWidth(extraLeft + (metrics.width * 5) + (metrics.horizontalGap * 4))
        self.parent:SetHeight((metrics.height * 8) + (metrics.groupGap * 7))
    end
end

function SFrames.Raid:SavePosition()
    if not (self.parent and SFramesDB) then return end
    if not SFramesDB.Positions then SFramesDB.Positions = {} end
    local point, _, relativePoint, xOfs, yOfs = self.parent:GetPoint()
    if point and relativePoint then
        SFramesDB.Positions["RaidFrame"] = {
            point = point,
            relativePoint = relativePoint,
            xOfs = xOfs or 0,
            yOfs = yOfs or 0,
        }
    end
end

function SFrames.Raid:ApplyPosition()
    if not self.parent then return end
    self.parent:ClearAllPoints()
    local pos = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["RaidFrame"]
    if pos and pos.point and pos.relativePoint and type(pos.xOfs) == "number" and type(pos.yOfs) == "number" then
        self.parent:SetPoint(pos.point, UIParent, pos.relativePoint, pos.xOfs, pos.yOfs)
    else
        self.parent:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 15, -200)
    end
end

local function TryDropCursorOnUnit(unit)
    if not unit or not UnitExists(unit) then return false end
    if not CursorHasItem or not CursorHasItem() then return false end
    if not DropItemOnUnit then return false end

    local ok = pcall(DropItemOnUnit, unit)
    return not CursorHasItem()
end

function SFrames.Raid:Initialize()
    self.frames = {}
    self.groups = {}
    self._framesBuilt = false
    
    if not SFramesDB then SFramesDB = {} end

    local parent = CreateFrame("Frame", "SFramesRaidParent", UIParent)
    local frameScale = (SFramesDB and type(SFramesDB.raidFrameScale) == "number") and SFramesDB.raidFrameScale or 1
    parent:SetScale(frameScale)
    self.parent = parent
    self:ApplyPosition()

    parent:SetMovable(true)
    
    self.groupLabels = {}
    for g = 1, 8 do
        local groupParent = CreateFrame("Frame", "SFramesRaidGroup"..g, parent)
        self.groups[g] = groupParent

        local lbl = groupParent:CreateFontString(nil, "OVERLAY")
        lbl:SetFont(SFrames:GetFont(), 9, "OUTLINE")
        lbl:SetTextColor(0.85, 0.78, 0.92)
        lbl:SetShadowColor(0, 0, 0, 1)
        lbl:SetShadowOffset(1, -1)
        lbl:SetText("小队 " .. g)
        lbl:Hide()
        self.groupLabels[g] = lbl
    end

    SFrames:RegisterEvent("RAID_ROSTER_UPDATE", function() self:UpdateAll() end)
    SFrames:RegisterEvent("PARTY_LEADER_CHANGED", function() self:UpdateAll() end)
    SFrames:RegisterEvent("PLAYER_TARGET_CHANGED", function() self:UpdateTargetHighlight() end)
    SFrames:RegisterEvent("UNIT_HEALTH", function() if RAID_UNIT_LOOKUP[arg1] then self:UpdateHealth(arg1) end end)
    SFrames:RegisterEvent("UNIT_MAXHEALTH", function() if RAID_UNIT_LOOKUP[arg1] then self:UpdateHealth(arg1) end end)
    SFrames:RegisterEvent("UNIT_MANA", function() if RAID_UNIT_LOOKUP[arg1] then self:UpdatePower(arg1) end end)
    SFrames:RegisterEvent("UNIT_MAXMANA", function() if RAID_UNIT_LOOKUP[arg1] then self:UpdatePower(arg1) end end)
    SFrames:RegisterEvent("UNIT_AURA", function() if RAID_UNIT_LOOKUP[arg1] then self:UpdateAuras(arg1) end end)
    SFrames:RegisterEvent("RAID_TARGET_UPDATE", function() self:UpdateRaidIcons() end)

    self:UpdateAll()

    if SFrames.Movers and SFrames.Movers.RegisterMover and self.parent then
        SFrames.Movers:RegisterMover("RaidFrame", self.parent, "团队",
            "TOPLEFT", "UIParent", "TOPLEFT", 15, -200)
    end
end

function SFrames.Raid:EnsureFrames()
    if self._framesBuilt then return end
    self._framesBuilt = true

    for i = 1, 40 do
        local unit = "raid" .. i
        local groupIndex = math.ceil(i / 5)
        local groupParent = self.groups[groupIndex]
        local f = CreateFrame("Button", "SFramesRaidFrame"..i, groupParent)
        
        f.id = i
        f:RegisterForClicks("LeftButtonUp", "RightButtonUp")
        f:RegisterForDrag("LeftButton")
        f:SetScript("OnDragStart", function()
            if IsAltKeyDown() or SFrames.isUnlocked then
                SFrames.Raid.parent:StartMoving()
            end
        end)
        f:SetScript("OnDragStop", function()
            SFrames.Raid.parent:StopMovingOrSizing()
            SFrames.Raid:SavePosition()
        end)
        f:SetScript("OnClick", function()
            if arg1 == "LeftButton" then
                if TryDropCursorOnUnit(this.unit) then return end
                if SpellIsTargeting and SpellIsTargeting() then
                    SpellTargetUnit(this.unit)
                    return
                end
                TargetUnit(this.unit)
            elseif arg1 == "RightButton" then
                if UnitExists(this.unit) then
                    GameTooltip:Hide()
                    if not SFrames.Raid.dropDown then
                        SFrames.Raid.dropDown = CreateFrame("Frame", "SFramesRaidDropDown", UIParent, "UIDropDownMenuTemplate")
                        SFrames.Raid.dropDown.displayMode = "MENU"
                        SFrames.Raid.dropDown.initialize = function()
                            local dd = SFrames.Raid.dropDown
                            local unit = dd.unit
                            local name = dd.name
                            if not unit or not name then return end

                            local info = {}
                            info.text = name
                            info.isTitle = 1
                            info.notCheckable = 1
                            UIDropDownMenu_AddButton(info)

                            if not UnitIsUnit(unit, "player") then
                                info = {}
                                info.text = WHISPER or "密语"
                                info.notCheckable = 1
                                info.func = function() ChatFrame_SendTell(name) end
                                UIDropDownMenu_AddButton(info)

                                info = {}
                                info.text = INSPECT or "检查"
                                info.notCheckable = 1
                                info.func = function() InspectUnit(unit) end
                                UIDropDownMenu_AddButton(info)

                                info = {}
                                info.text = TRADE or "交易"
                                info.notCheckable = 1
                                info.func = function() InitiateTrade(unit) end
                                UIDropDownMenu_AddButton(info)

                                info = {}
                                info.text = FOLLOW or "跟随"
                                info.notCheckable = 1
                                info.func = function() FollowUnit(unit) end
                                UIDropDownMenu_AddButton(info)
                            end

                            if (IsRaidLeader and IsRaidLeader()) or (IsRaidOfficer and IsRaidOfficer()) then
                                if not UnitIsUnit(unit, "player") then
                                    info = {}
                                    info.text = "提升为助手"
                                    info.notCheckable = 1
                                    info.func = function() PromoteToAssistant(name) end
                                    UIDropDownMenu_AddButton(info)

                                    info = {}
                                    info.text = "移出团队"
                                    info.notCheckable = 1
                                    info.func = function() UninviteByName(name) end
                                    UIDropDownMenu_AddButton(info)
                                end
                            end

                            info = {}
                            info.text = CANCEL or "取消"
                            info.notCheckable = 1
                            UIDropDownMenu_AddButton(info)
                        end
                    end
                    SFrames.Raid.dropDown.unit = this.unit
                    SFrames.Raid.dropDown.name = UnitName(this.unit)
                    ToggleDropDownMenu(1, nil, SFrames.Raid.dropDown, "cursor")
                end
            end
        end)
        f:SetScript("OnReceiveDrag", function()
            if TryDropCursorOnUnit(this.unit) then return end
            if SpellIsTargeting and SpellIsTargeting() then
                SpellTargetUnit(this.unit)
            end
        end)
        f:SetScript("OnEnter", function()
            GameTooltip_SetDefaultAnchor(GameTooltip, this)
            GameTooltip:SetUnit(this.unit)
        end)
        f:SetScript("OnLeave", function() GameTooltip:Hide() end)
        f.unit = unit
        
        -- Health Bar
        f.health = SFrames:CreateStatusBar(f, "SFramesRaidFrame"..i.."Health")
        
        local hbg = CreateFrame("Frame", nil, f)
        hbg:SetFrameLevel(f:GetFrameLevel() - 1)
        SFrames:CreateUnitBackdrop(hbg)
        f.healthBGFrame = hbg
        
        f.health.bg = f.health:CreateTexture(nil, "BACKGROUND")
        f.health.bg:SetAllPoints()
        f.health.bg:SetTexture(SFrames:GetTexture())
        f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)

        -- Heal prediction overlay (incoming heals)
        f.health.healPredMine = f.health:CreateTexture(nil, "OVERLAY")
        f.health.healPredMine:SetTexture(SFrames:GetTexture())
        f.health.healPredMine:SetVertexColor(0.4, 1.0, 0.55, 0.78)
        f.health.healPredMine:Hide()

        f.health.healPredOther = f.health:CreateTexture(nil, "OVERLAY")
        f.health.healPredOther:SetTexture(SFrames:GetTexture())
        f.health.healPredOther:SetVertexColor(0.2, 0.9, 0.35, 0.5)
        f.health.healPredOther:Hide()
        
        -- Power Bar
        f.power = SFrames:CreateStatusBar(f, "SFramesRaidFrame"..i.."Power")
        f.power:SetMinMaxValues(0, 100)
        
        local powerbg = CreateFrame("Frame", nil, f)
        powerbg:SetFrameLevel(f:GetFrameLevel() - 1)
        SFrames:CreateUnitBackdrop(powerbg)
        f.powerBGFrame = powerbg
        
        f.power.bg = f.power:CreateTexture(nil, "BACKGROUND")
        f.power.bg:SetAllPoints()
        f.power.bg:SetTexture(SFrames:GetTexture())
        f.power.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)

        -- Target Highlight
        f.targetHighlight = f:CreateTexture(nil, "OVERLAY")
        f.targetHighlight:SetPoint("TOPLEFT", f, "TOPLEFT", -2, 2)
        f.targetHighlight:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 2, -2)
        f.targetHighlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
        f.targetHighlight:SetBlendMode("ADD")
        f.targetHighlight:SetVertexColor(1, 1, 1, 0.6)
        f.targetHighlight:Hide()
        
        -- Texts
        f.nameText = SFrames:CreateFontString(f.health, 10, "CENTER")
        f.nameText:SetPoint("TOP", f.health, "TOP", 0, -2)
        f.nameText:SetShadowColor(0, 0, 0, 1)
        f.nameText:SetShadowOffset(1, -1)
        
        f.healthText = SFrames:CreateFontString(f.health, 9, "CENTER")
        f.healthText:SetPoint("BOTTOM", f.health, "BOTTOM", 0, 2)
        f.healthText:SetShadowColor(0, 0, 0, 1)
        f.healthText:SetShadowOffset(1, -1)
        f.healthText:SetTextColor(0.8, 0.8, 0.8)

        local offOvr = CreateFrame("Frame", nil, f)
        offOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 4)
        offOvr:SetAllPoints(f)
        local offIco = SFrames:CreateIcon(offOvr, "offline", 16)
        offIco:SetPoint("CENTER", offOvr, "CENTER", 0, 0)
        offIco:SetVertexColor(0.7, 0.7, 0.7, 0.9)
        offIco:Hide()
        f.offlineIcon = offIco

        local roleOvr = CreateFrame("Frame", nil, f)
        roleOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 6)
        roleOvr:SetAllPoints(f)

        f.leaderIcon = roleOvr:CreateTexture(nil, "OVERLAY")
        f.leaderIcon:SetWidth(12)
        f.leaderIcon:SetHeight(12)
        f.leaderIcon:SetPoint("TOPLEFT", f, "TOPLEFT", -3, 3)
        f.leaderIcon:SetTexture("Interface\\GroupFrame\\UI-Group-LeaderIcon")
        f.leaderIcon:Hide()

        f.assistantIcon = roleOvr:CreateFontString(nil, "OVERLAY")
        f.assistantIcon:SetFont(SFrames:GetFont(), 12, "OUTLINE")
        f.assistantIcon:SetPoint("TOPLEFT", f, "TOPLEFT", -1, 1)
        f.assistantIcon:SetTextColor(0.5, 0.8, 1.0)
        f.assistantIcon:SetShadowColor(0, 0, 0, 1)
        f.assistantIcon:SetShadowOffset(1, -1)
        f.assistantIcon:SetText("+")
        f.assistantIcon:Hide()

        -- Raid Target Icon
        local raidIconSize = 16
        local raidIconOvr = CreateFrame("Frame", nil, f)
        raidIconOvr:SetFrameLevel((f:GetFrameLevel() or 0) + 7)
        raidIconOvr:SetWidth(raidIconSize)
        raidIconOvr:SetHeight(raidIconSize)
        raidIconOvr:SetPoint("CENTER", f.health, "TOP", 0, 0)
        f.raidIcon = raidIconOvr:CreateTexture(nil, "OVERLAY")
        f.raidIcon:SetTexture("Interface\\TargetingFrame\\UI-RaidTargetingIcons")
        f.raidIcon:SetAllPoints(raidIconOvr)
        f.raidIcon:Hide()
        f.raidIconOverlay = raidIconOvr

        f.indicators = {}
        for pos = 1, 4 do
            local ind = CreateFrame("Button", nil, f)
            ind:SetWidth(10)
            ind:SetHeight(10)
            ind:SetFrameLevel(f:GetFrameLevel() + 5)
            SFrames:CreateUnitBackdrop(ind)
            
            ind.icon = ind:CreateTexture(nil, "ARTWORK")
            ind.icon:SetAllPoints()
            ind.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
            
            if pos == 1 then
                ind:SetPoint("TOPLEFT", f.health, "TOPLEFT", 1, -1)
            elseif pos == 2 then
                ind:SetPoint("TOPRIGHT", f.health, "TOPRIGHT", -1, -1)
            elseif pos == 3 then
                ind:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", 1, 1)
            elseif pos == 4 then
                ind:SetPoint("BOTTOMRIGHT", f.health, "BOTTOMRIGHT", -1, 1)
            end
            
            ind:SetScript("OnEnter", function()
                if this.index then
                    GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
                    if this.isDebuff then
                        GameTooltip:SetUnitDebuff(f.unit, this.index)
                    else
                        GameTooltip:SetUnitBuff(f.unit, this.index)
                    end
                end
            end)
            ind:SetScript("OnLeave", function() GameTooltip:Hide() end)
            
            ind:Hide()
            f.indicators[pos] = ind
        end

        self.frames[i] = { frame = f, unit = unit, index = i }
        
        self:ApplyFrameStyle(f, self:GetMetrics())

        f.rangeTimer = 0
        f.auraScanTimer = 0
        f.healPredTimer = 0
        
        f:SetScript("OnShow", function()
            if not SFrames.Raid.testing then
                SFrames.Raid:UpdateFrame(this.unit)
            end
        end)
        
        f:Hide()
    end

    if not self._globalUpdateFrame then
        self._globalUpdateFrame = CreateFrame("Frame", nil, UIParent)
        self._globalUpdateFrame:SetScript("OnUpdate", function()
            if SFrames.Raid.testing then return end
            local dt = arg1
            local frames = SFrames.Raid.frames
            if not frames then return end
            for i = 1, 40 do
                local entry = frames[i]
                if entry then
                    local f = entry.frame
                    if f:IsVisible() and f.unit and f.unit ~= "" then
                        f.rangeTimer = f.rangeTimer + dt
                        if f.rangeTimer >= 0.5 then
                            if UnitExists(f.unit) and not CheckInteractDistance(f.unit, 4) then
                                f:SetAlpha(0.6)
                            else
                                f:SetAlpha(1.0)
                            end
                            f.rangeTimer = 0
                        end

                        f.auraScanTimer = f.auraScanTimer + dt
                        if f.auraScanTimer >= 0.7 then
                            SFrames.Raid:UpdateAuras(f.unit)
                            f.auraScanTimer = 0
                        end

                        f.healPredTimer = f.healPredTimer + dt
                        if f.healPredTimer >= 0.2 then
                            SFrames.Raid:UpdateHealPrediction(f.unit)
                            f.healPredTimer = 0
                        end
                    end
                end
            end
        end)
    end

    self:ApplyLayout()
end

function SFrames.Raid:UpdateAll()
    if self.testing then return end
    
    if SFramesDB and SFramesDB.enableRaidFrames == false then
        self.parent:Hide()
        return
    else
        self.parent:Show()
    end

    if GetNumRaidMembers() > 0 then
        self:EnsureFrames()
        for i = 1, 40 do
            self.frames[i].frame:Hide()
            self.frames[i].frame.unit = ""
            self.frames[i].unit = ""
        end

        local groupSlots = {}
        for g = 1, 8 do
            groupSlots[g] = 0
        end

        for i = 1, 40 do
            local name, rank, subgroup = GetRaidRosterInfo(i)
            if name and subgroup and subgroup >= 1 and subgroup <= 8 then
                groupSlots[subgroup] = groupSlots[subgroup] + 1
                local slot = groupSlots[subgroup]
                if slot <= 5 then
                    local frameIndex = (subgroup - 1) * 5 + slot
                    local f = self.frames[frameIndex].frame
                    f.unit = "raid" .. i
                    f.raidRank = rank
                    self.frames[frameIndex].unit = "raid" .. i
                    f:Show()
                    self:UpdateFrame("raid" .. i)
                end
            end
        end

        -- Show/hide group labels based on whether each group has members
        local showLabel = SFramesDB and SFramesDB.raidShowGroupLabel ~= false
        for g = 1, 8 do
            local lbl = self.groupLabels and self.groupLabels[g]
            if lbl then
                if showLabel and groupSlots[g] > 0 then
                    lbl:Show()
                else
                    lbl:Hide()
                end
            end
        end
    else
        if not self.testing and self._framesBuilt then
            for i = 1, 40 do
                self.frames[i].frame:Hide()
                self.frames[i].frame.unit = ""
                self.frames[i].unit = ""
            end
        end
        for g = 1, 8 do
            local lbl = self.groupLabels and self.groupLabels[g]
            if lbl then lbl:Hide() end
        end
    end
end

function SFrames.Raid:UpdateFrame(unit)
    if self.testing or not self._framesBuilt then return end
    for i=1, 40 do
        if self.frames[i].unit == unit then
            local f = self.frames[i].frame
            local name = UnitName(unit) or ""
            local _, class = UnitClass(unit)
            
            local display = string.sub(name, 1, 15)
            f.nameText:SetText(display)

            local rank = f.raidRank or 0
            if f.leaderIcon then
                if rank == 2 then f.leaderIcon:Show() else f.leaderIcon:Hide() end
            end
            if f.assistantIcon then
                if rank == 1 then f.assistantIcon:Show() else f.assistantIcon:Hide() end
            end

            if not UnitIsConnected(unit) then
                f.health:SetStatusBarColor(0.5, 0.5, 0.5)
                f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
                f.nameText:SetTextColor(0.5, 0.5, 0.5)
                if f.offlineIcon then f.offlineIcon:Show() end
            else
                if f.offlineIcon then f.offlineIcon:Hide() end
                if class and SFrames.Config.colors.class[class] then
                    local color = SFrames.Config.colors.class[class]
                    f.health:SetStatusBarColor(color.r, color.g, color.b)
                    f.nameText:SetTextColor(1, 1, 1)
                else
                    f.health:SetStatusBarColor(0, 1, 0)
                    f.nameText:SetTextColor(1, 1, 1)
                end
            end
            
            self:UpdateHealth(unit)
            self:UpdatePower(unit)
            self:UpdateAuras(unit)
            self:UpdateRaidIcon(unit)
            self:UpdateTargetHighlight()
            break
        end
    end
end

function SFrames.Raid:UpdateTargetHighlight()
    if self.testing or not self._framesBuilt then return end
    for i=1, 40 do
        local f = self.frames[i].frame
        if UnitExists("target") and UnitIsUnit("target", self.frames[i].unit) then
            f.targetHighlight:Show()
        else
            f.targetHighlight:Hide()
        end
    end
end

function SFrames.Raid:UpdateHealth(unit)
    if not self._framesBuilt then return end
    for i=1, 40 do
        if self.frames[i].unit == unit then
            local f = self.frames[i].frame
            if not UnitIsConnected(unit) then
                f.health:SetMinMaxValues(0, 100)
                f.health:SetValue(0)
                f.healthText:SetText("离线")
                if f.offlineIcon then f.offlineIcon:Show() end
                return
            end
            if f.offlineIcon then f.offlineIcon:Hide() end
            
            local hp = UnitHealth(unit)
            local maxHp = UnitHealthMax(unit)
            
            if UnitIsDead(unit) then
                f.health:SetMinMaxValues(0, 100)
                f.health:SetValue(0)
                f.healthText:SetText("死亡")
                f.nameText:SetTextColor(0.5, 0.5, 0.5)
                return
            end
            
            if UnitIsGhost(unit) then
                f.health:SetMinMaxValues(0, 100)
                f.health:SetValue(0)
                f.healthText:SetText("灵魂")
                f.nameText:SetTextColor(0.5, 0.5, 0.5)
                return
            end

            f.health:SetMinMaxValues(0, maxHp)
            f.health:SetValue(hp)
            
            local percent = 0
            if maxHp > 0 then
                percent = math.floor((hp / maxHp) * 100)
            end

            local db = SFramesDB or {}
            local txt = ""
            if db.raidHealthFormat == "percent" then
                txt = percent .. "%"
            elseif db.raidHealthFormat == "deficit" then
                if maxHp - hp > 0 then
                    txt = "-" .. (maxHp - hp)
                end
            else
                txt = (math.floor(hp/100)/10).."k" -- default compact e.g. 4.5k
                if hp < 1000 then txt = tostring(hp) end
            end
            
            f.healthText:SetText(txt)
            self:UpdateHealPrediction(unit)
            break
        end
    end
end

function SFrames.Raid:UpdateHealPrediction(unit)
    if not self._framesBuilt then return end
    local frameData = nil
    for i=1, 40 do
        if self.frames[i].unit == unit then
            frameData = self.frames[i]
            break
        end
    end
    if not frameData then return end

    local f = frameData.frame
    if not (f.health and f.health.healPredMine and f.health.healPredOther) then return end

    local predMine = f.health.healPredMine
    local predOther = f.health.healPredOther

    local function HidePredictions()
        predMine:Hide()
        predOther:Hide()
    end

    if not UnitExists(unit) or not UnitIsConnected(unit) then
        HidePredictions()
        return
    end

    local hp = UnitHealth(unit) or 0
    local maxHp = UnitHealthMax(unit) or 0
    if maxHp <= 0 or hp >= maxHp then
        HidePredictions()
        return
    end

    local _, mineIncoming, othersIncoming = GetIncomingHeals(unit)
    local missing = maxHp - hp
    if missing <= 0 then
        HidePredictions()
        return
    end

    local mineShown = math.min(math.max(0, mineIncoming), missing)
    local remaining = missing - mineShown
    local otherShown = math.min(math.max(0, othersIncoming), remaining)
    if mineShown <= 0 and otherShown <= 0 then
        HidePredictions()
        return
    end

    local barWidth = f.health:GetWidth() or 0
    if barWidth <= 0 then
        HidePredictions()
        return
    end

    local currentWidth = math.floor((hp / maxHp) * barWidth + 0.5)
    if currentWidth < 0 then currentWidth = 0 end
    if currentWidth > barWidth then currentWidth = barWidth end

    local availableWidth = barWidth - currentWidth
    if availableWidth <= 0 then
        HidePredictions()
        return
    end

    local mineWidth = math.floor((mineShown / maxHp) * barWidth + 0.5)
    local otherWidth = math.floor((otherShown / maxHp) * barWidth + 0.5)
    if mineWidth < 0 then mineWidth = 0 end
    if otherWidth < 0 then otherWidth = 0 end
    if mineWidth > availableWidth then mineWidth = availableWidth end
    if otherWidth > (availableWidth - mineWidth) then
        otherWidth = availableWidth - mineWidth
    end

    if mineWidth > 0 then
        predMine:ClearAllPoints()
        predMine:SetPoint("TOPLEFT", f.health, "TOPLEFT", currentWidth, 0)
        predMine:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentWidth, 0)
        predMine:SetWidth(mineWidth)
        predMine:Show()
    else
        predMine:Hide()
    end

    if otherWidth > 0 then
        predOther:ClearAllPoints()
        predOther:SetPoint("TOPLEFT", f.health, "TOPLEFT", currentWidth + mineWidth, 0)
        predOther:SetPoint("BOTTOMLEFT", f.health, "BOTTOMLEFT", currentWidth + mineWidth, 0)
        predOther:SetWidth(otherWidth)
        predOther:Show()
    else
        predOther:Hide()
    end
end

function SFrames.Raid:UpdatePower(unit)
    if not self._framesBuilt then return end
    local db = SFramesDB or {}
    if db.raidShowPower == false then return end

    for i=1, 40 do
        if self.frames[i].unit == unit then
            local f = self.frames[i].frame
            local power = UnitMana(unit)
            local maxPower = UnitManaMax(unit)
            local _, class = UnitClass(unit)
            
            f.power:SetMinMaxValues(0, maxPower)
            f.power:SetValue(power)

            local pType = UnitPowerType(unit)
            local color = SFrames.Config.colors.power[pType] or SFrames.Config.colors.power[0]
            f.power:SetStatusBarColor(color.r, color.g, color.b)
            break
        end
    end
end

function SFrames.Raid:UpdateRaidIcons()
    if not self._framesBuilt then return end
    for i = 1, 40 do
        local unit = self.frames[i].unit
        if unit and unit ~= "" and self.frames[i].frame:IsShown() then
            self:UpdateRaidIcon(unit)
        end
    end
end

function SFrames.Raid:UpdateRaidIcon(unit)
    if not self._framesBuilt then return end
    for i = 1, 40 do
        if self.frames[i].unit == unit then
            local f = self.frames[i].frame
            if not f.raidIcon then return end
            if not GetRaidTargetIndex then
                f.raidIcon:Hide()
                return
            end
            if not UnitExists(unit) then
                f.raidIcon:Hide()
                return
            end
            local index = GetRaidTargetIndex(unit)
            if index and index > 0 and index <= 8 then
                local col = math.mod(index - 1, 4)
                local row = math.floor((index - 1) / 4)
                f.raidIcon:SetTexCoord(col * 0.25, (col + 1) * 0.25, row * 0.25, (row + 1) * 0.25)
                f.raidIcon:Show()
            else
                f.raidIcon:Hide()
            end
            break
        end
    end
end

local PlayerClassBuffs = nil

function SFrames.Raid:GetClassBuffs()
    if PlayerClassBuffs then return PlayerClassBuffs end

    local _, playerClass = UnitClass("player")
    PlayerClassBuffs = {}

    if playerClass == "PRIEST" then
        PlayerClassBuffs = {
            [1] = { "真言术：韧", "坚韧祷言" },
            [2] = { "神圣之灵", "精神祷言" },
            [3] = { "恢复" },
            [4] = { "虚弱灵魂", isDebuff = true },
            [5] = { "防恐结界" }
        }
    elseif playerClass == "DRUID" then
        PlayerClassBuffs = {
            [1] = { "野性印记", "野性赐福" },
            [2] = { "荆棘术" },
            [3] = { "回春术" },
            [4] = { "愈合" },
        }
    elseif playerClass == "PALADIN" then
        PlayerClassBuffs = {
            [1] = { "王者祝福", "强效王者祝福" },
            [2] = { "拯救祝福", "强效拯救祝福" },
            [3] = { "智慧祝福", "强效智慧祝福" },
            [4] = { "力量祝福", "强效力量祝福" },
            [5] = { "庇护祝福", "强效庇护祝福" },
            [6] = { "光明祝福", "强效光明祝福" }
        }
    elseif playerClass == "MAGE" then
        PlayerClassBuffs = {
            [1] = { "奥术智慧", "奥术光辉" },
            [2] = { "魔法抑制" },
            [3] = { "魔法增效" }
        }
    end

    return PlayerClassBuffs
end

function SFrames.Raid:UpdateAuras(unit)
    if not self._framesBuilt then return end
    local frameData = nil
    for i=1, 40 do
        if self.frames[i].unit == unit then
            frameData = self.frames[i]
            break
        end
    end
    if not frameData then return end
    
    local f = frameData.frame
    local buffsNeeded = self:GetClassBuffs()
    
    local foundIndicators = { [1] = false, [2] = false, [3] = false, [4] = false }

    -- Hide all first
    for i = 1, 4 do
        f.indicators[i]:Hide()
        f.indicators[i].index = nil
    end

    if not buffsNeeded or not UnitIsConnected(unit) or UnitIsDeadOrGhost(unit) then
        f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
        return
    end

    local function MatchesList(auraName, list)
        for _, name in ipairs(list) do
            if string.find(auraName, name) then
                return true
            end
        end
        return false
    end

    -- Check Buffs
    for i = 1, 32 do
        local texture, applications = UnitBuff(unit, i)
        if not texture then break end
        
        SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
        SFrames.Tooltip:SetUnitBuff(unit, i)
        local buffName = SFramesScanTooltipTextLeft1:GetText()
        SFrames.Tooltip:Hide()
        
        if buffName then
            for pos, listData in pairs(buffsNeeded) do
                if pos <= 4 and not listData.isDebuff and not foundIndicators[pos] then
                    if MatchesList(buffName, listData) then
                        f.indicators[pos].icon:SetTexture(texture)
                        f.indicators[pos].index = i
                        f.indicators[pos].isDebuff = false
                        f.indicators[pos]:Show()
                        foundIndicators[pos] = true
                    end
                end
            end
        end
    end

    local hasDebuff = false
    local debuffColor = {r=_A.slotBg[1], g=_A.slotBg[2], b=_A.slotBg[3]}

    -- Check Debuffs
    for i = 1, 16 do
        local texture, applications, dispelType = UnitDebuff(unit, i)
        if not texture then break end
        
        if dispelType then
            hasDebuff = true
            if dispelType == "Magic" then debuffColor = {r=0.2, g=0.6, b=1}
            elseif dispelType == "Curse" then debuffColor = {r=0.6, g=0, b=1}
            elseif dispelType == "Disease" then debuffColor = {r=0.6, g=0.4, b=0}
            elseif dispelType == "Poison" then debuffColor = {r=0, g=0.6, b=0}
            end
        end

        SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
        SFrames.Tooltip:SetUnitDebuff(unit, i)
        local debuffName = SFramesScanTooltipTextLeft1:GetText()
        SFrames.Tooltip:Hide()
        
        if debuffName then
            for pos, listData in pairs(buffsNeeded) do
                if pos <= 4 and listData.isDebuff and not foundIndicators[pos] then
                    if MatchesList(debuffName, listData) then
                        f.indicators[pos].icon:SetTexture(texture)
                        f.indicators[pos].index = i
                        f.indicators[pos].isDebuff = true
                        f.indicators[pos]:Show()
                        foundIndicators[pos] = true
                    end
                end
            end
        end
    end

    if hasDebuff then
        f.health.bg:SetVertexColor(debuffColor.r, debuffColor.g, debuffColor.b, 1)
    else
        f.health.bg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
    end
end

