-- ============================================================================
-- File: ArenaCore/modules/CastBars.lua
-- Purpose: Cast bar system for ArenaCore
-- Works in both test mode and arena
-- ============================================================================

local AC = _G.ArenaCore
if not AC then return end

-- ============================================================================
-- DETECTION & CONSTANTS
-- ============================================================================

local isMidnight = select(4, GetBuildInfo()) >= 120000
local MAX_ARENA_ENEMIES = 3

-- Test mode cast data
local TEST_CAST_DATA = {
    [1] = { name = "Death Coil", icon = 136145, duration = 2.0, channel = false, uninterruptible = false },
    [2] = { name = "Polymorph", icon = 136071, duration = 1.7, channel = false, uninterruptible = false },
    [3] = { name = "Mass Dispel", icon = 135739, duration = 2.5, channel = false, uninterruptible = true },
}

-- ============================================================================
-- CAST BAR MODULE
-- ============================================================================

local CastBars = {}
AC.CastBars = CastBars

-- Store references to cast bars
CastBars.bars = {}

local function PixelSetPoint(region, point, relativeTo, relativePoint, offsetX, offsetY)
    if not region then return end
    if PixelUtil and PixelUtil.SetPoint then
        PixelUtil.SetPoint(region, point, relativeTo, relativePoint, offsetX or 0, offsetY or 0)
    else
        region:SetPoint(point, relativeTo, relativePoint, offsetX or 0, offsetY or 0)
    end
end

local function PixelSetSize(region, width, height)
    if not region then return end
    if PixelUtil and PixelUtil.SetSize then
        PixelUtil.SetSize(region, width or 0, height or 0, 1, 1)
    else
        region:SetSize(width or 0, height or 0)
    end
end

local function PixelSetWidth(region, width)
    if not region then return end
    if PixelUtil and PixelUtil.SetWidth then
        PixelUtil.SetWidth(region, width or 0, 1)
    else
        region:SetWidth(width or 0)
    end
end

local function PixelSetHeight(region, height)
    if not region then return end
    if PixelUtil and PixelUtil.SetHeight then
        PixelUtil.SetHeight(region, height or 0, 1)
    else
        region:SetHeight(height or 0)
    end
end

-- ============================================================================
-- GET SETTINGS FROM DATABASE
-- ============================================================================

local function GetSettings()
    local db = AC.DB and AC.DB.profile and AC.DB.profile.castBars
    -- Defaults should match Init.lua so things don't get weird after a reload.
    return db or {
        positioning = { horizontal = -4, vertical = -86 },
        sizing = { width = 220, height = 24 },
    }
end

-- ============================================================================
-- INITIALIZE CAST BARS
-- Called after ArenaCore frames are created
-- ============================================================================

function CastBars:Initialize()
    if not isMidnight then
        -- Non-Midnight would need custom cast bars. Not today.
        return
    end
    
    -- Wait for Blizzard frames to be available
    C_Timer.After(0.5, function()
        self:SetupCastBars()
    end)
end

-- ============================================================================
-- SETUP CAST BARS (Midnight Mode)
-- Uses Blizzard's CastingBarFrame directly
-- ============================================================================

function CastBars:SetupCastBars()
    if not isMidnight then return end
    
    local MFM = AC.MasterFrameManager
    if not MFM or not MFM.frames then 
        -- DEBUG DISABLED: print("|cffFF0000ArenaCore:|r SetupCastBars - No MFM frames")
        return 
    end
    
    -- DEBUG DISABLED: print("|cff00FF00ArenaCore:|r Setting up cast bars...")
    
    for i = 1, MAX_ARENA_ENEMIES do
        local acFrame = MFM.frames[i]
        local blizzFrame = _G["CompactArenaFrameMember" .. i]
        
        --[[ DEBUG DISABLED: print(string.format("|cff00FF00ArenaCore:|r Frame %d - acFrame: %s, blizzFrame: %s", 
            i, tostring(acFrame), tostring(blizzFrame))) --]]
        
        if acFrame and blizzFrame and blizzFrame.CastingBarFrame then
            -- DEBUG DISABLED: print("|cff00FF00ArenaCore:|r Found cast bar 1: " .. tostring(blizzFrame.CastingBarFrame))
            local castBar = blizzFrame.CastingBarFrame
            -- DEBUG DISABLED: print(string.format("|cff00FF00ArenaCore:|r Found cast bar %d: %s", i, tostring(castBar)))
            
            -- Skip if already set up
            if self.bars[i] == castBar then
                -- DEBUG DISABLED: print("|cffFFFF00ArenaCore:|r Cast bar %d already set up, skipping", i)
                return
            end
            
            -- Store reference
            self.bars[i] = castBar
            acFrame.castBar = castBar
            castBar._acUnitToken = "arena" .. i
            
            -- Reparent to ArenaCore (not Blizzard) so it stays visible when Blizzard decides to hide stuff.
            castBar:SetParent(acFrame)
            castBar:SetFrameStrata("HIGH")
            castBar:SetFrameLevel(10)
            
            -- This is kind of important I guess: don't inherit alpha from the Blizzard parent.
            castBar:SetIgnoreParentAlpha(true)
            
            -- Keep the original SetPoint so we can still move it later without fighting overrides.
            if not castBar._originalSetPoint then
                castBar._originalSetPoint = castBar.SetPoint
            end
            
            -- Make it look like ArenaCore, not like Blizzard's default arts-and-crafts project.
            self:ApplyBlockyStyle(castBar)
            
            -- Handle interruptible vs "shield" casts.
            self:HookCastBarColors(castBar)
        else
            -- DEBUG DISABLED: print(string.format("|cffFF0000ArenaCore:|r Missing components for frame %d", i))
        end
    end
end

-- ============================================================================
-- CLEAR ALL CAST BARS
-- Prevents stale cast data from persisting across arena state changes
-- ============================================================================

function CastBars:ClearAllCastBars()
    if not isMidnight then return end

    for i = 1, MAX_ARENA_ENEMIES do
        local castBar = self.bars[i]
        if castBar then
            -- Hide the cast bar
            castBar:Hide()
            
            -- Clear cast bar state
            castBar.casting = nil
            castBar.channeling = nil
            castBar.empowering = nil
            
            -- Reset progress
            castBar:SetValue(0)
            
            -- Clear text
            if castBar.Text then
                castBar.Text:SetText("")
            end
            
            -- Clear icon
            if castBar.Icon then
                castBar.Icon:SetTexture(nil)
            end
            
            -- Hide shield (uninterruptible indicator)
            if castBar.BorderShield then
                castBar.BorderShield:Hide()
            end
        end
    end
end

-- ============================================================================
-- APPLY BLOCKY STYLE (MaskTexture)
-- Creates sharp rectangle edges
-- ============================================================================

function CastBars:ApplyBlockyStyle(castBar)
    if not castBar then return end
    
    -- Use your selected texture (or fallback) and keep it clean/flat.
    local customTexture = self:GetCastBarTexture()
    local textureToUse = customTexture or "Interface\\RaidFrame\\Raid-Bar-Hp-Fill"
    castBar:SetStatusBarTexture(textureToUse)
    
    -- Strip ALL mask textures from the fill so the flat texture fills as a clean rectangle.
    -- Blizzard's atlas textures and our old CastBarMask.tga both had pill-shaped edges baked in.
    -- No mask = no rounded corners. The pixel border handles the visual outline.
    local statusBarTexture = castBar:GetStatusBarTexture()
    if statusBarTexture and statusBarTexture.GetMaskTextures then
        local masks = { statusBarTexture:GetMaskTextures() }
        for _, mask in ipairs(masks) do
            statusBarTexture:RemoveMaskTexture(mask)
        end
    end
    
    -- Also nuke the old mask if it exists from a previous style pass
    if castBar.arenaMask then
        if statusBarTexture then
            statusBarTexture:RemoveMaskTexture(castBar.arenaMask)
        end
        castBar.arenaMask:Hide()
        castBar.arenaMask = nil
    end
    
    -- Hide Blizzard's extra decorations.
    if castBar.Border then castBar.Border:Hide() end
    if castBar.Flash then castBar.Flash:Hide() end
    if castBar.Background then castBar.Background:Hide() end
    if castBar.Spark then castBar.Spark:Hide() end
    if castBar.BorderFrame then castBar.BorderFrame:Hide() end
    
    -- Hide stray textures except the fill/icon/text.
    local statusBarTexture = castBar:GetStatusBarTexture()
    for i = 1, castBar:GetNumRegions() do
        local region = select(i, castBar:GetRegions())
        if region and region:GetObjectType() == "Texture" and region ~= statusBarTexture then
            -- Don't hide icon/text, just the junk.
            if region ~= castBar.Icon and region ~= castBar.Text then
                region:Hide()
            end
        end
    end
    
    -- Background + border so it actually matches the rest of the theme.
    self:AddBackground(castBar)
    
    self:AddPixelBorder(castBar)
    
    self:EnsureBorderShield(castBar)
    self:SyncBorderShieldToIcon(castBar)
    
    self:AddSpellIconBorder(castBar)
    
    self:CenterText(castBar)
end

-- ============================================================================
-- SYNC BORDER SHIELD TO SPELL ICON
-- Makes the shield derive from the live icon geometry every time
-- ============================================================================

function CastBars:SyncBorderShieldToIcon(castBar)
    if not castBar then return end
    if not castBar.Icon then return end
    if not castBar.BorderShield then return end

    local icon = castBar.Icon
    local shield = castBar.BorderShield
    
    -- Read user's shield scale setting (default 100%)
    local db = AC.DB and AC.DB.profile and AC.DB.profile.castBars and AC.DB.profile.castBars.spellIcons
    local userShieldScale = (db and db.sizing and db.sizing.shieldScale) or 100
    local shieldScale = 0.88 * (userShieldScale / 100)

    -- MIDNIGHT 12.0 FIX: GetWidth/GetHeight return secret number values that cause taint when compared
    -- Use fixed standard cast bar icon dimensions instead (Blizzard's default is 16x16)
    local iconWidth = 16
    local iconHeight = 16
    local iconScale = 1

    shield:ClearAllPoints()
    shield:SetPoint("CENTER", icon, "CENTER", 0, 0)
    shield:SetScale(1)

    -- Always use the standard calculation (no comparison needed)
    shield:SetSize((iconWidth * iconScale) * (30 / 16) * shieldScale, (iconHeight * iconScale) * (34 / 16) * shieldScale)
end

-- ============================================================================
-- ENSURE BORDER SHIELD EXISTS
-- Creates shield icon overlay for uninterruptible casts if not present
-- ============================================================================

function CastBars:EnsureBorderShield(castBar)
    if not castBar then return end
    if not castBar.BorderShield then
        -- Create shield texture if Blizzard didn't already give us one
        castBar.BorderShield = castBar:CreateTexture(nil, "ARTWORK", nil, 1)
        castBar.BorderShield:Hide()
    end

    castBar.BorderShield:SetAtlas("UI-CastingBar-Shield")
    castBar.BorderShield:SetDrawLayer("BACKGROUND", 1)

    if castBar.Icon then
        self:SyncBorderShieldToIcon(castBar)
    else
        castBar.BorderShield:ClearAllPoints()
        castBar.BorderShield:SetPoint("LEFT", castBar, "LEFT", -15, 0)
        castBar.BorderShield:SetScale(1)
        castBar.BorderShield:SetSize(30, 34)
    end
    
    -- Overlay texture shown on uninterruptible casts instead of the atlas diagonal lines
    if not castBar.DiagonalOverlay then
        castBar.DiagonalOverlay = castBar:CreateTexture(nil, "ARTWORK", nil, 2)
        castBar.DiagonalOverlay:SetBlendMode("BLEND")
        castBar.DiagonalOverlay:SetAlpha(1.0)
        castBar.DiagonalOverlay:SetVertexColor(0.7, 0.7, 0.7, 1) -- Grey, same as the bar color
        castBar.DiagonalOverlay:Hide()
    end

    -- Always sync texture in case user changed it in settings
    local unintTex = self:GetUninterruptibleCastBarTexture()
    castBar.DiagonalOverlay:SetTexture(unintTex)
end

-- ============================================================================
-- HOOK CAST BAR STYLE
-- Intercepts ALL SetStatusBarTexture calls and replaces with custom texture
-- Also applies custom coloring based on Blizzard's barType
-- ============================================================================

-- Get user's selected uninterruptible cast bar texture from database
function CastBars:GetUninterruptibleCastBarTexture()
    local db = AC.DB and AC.DB.profile and AC.DB.profile.textures
    if db and db.uninterruptibleCastBarTexture then
        return db.uninterruptibleCastBarTexture
    end
    return "Interface\\AddOns\\ArenaCore\\Media\\Frametextures\\texture24.tga"
end

-- Get user's selected cast bar texture from database
function CastBars:GetCastBarTexture()
    local db = AC.DB and AC.DB.profile and AC.DB.profile.textures
    if not db then return nil end
    
    -- Check if user has a specific cast bar texture set
    if db.useDifferentCastBarTexture and db.castBarTexture then
        return db.castBarTexture
    end
    
    -- Fall back to power bar texture if set
    if db.useDifferentPowerBarTexture and db.powerBarTexture then
        return db.powerBarTexture
    end
    
    -- Fall back to health bar texture
    return db.healthBarTexture
end

function CastBars:HookCastBarColors(castBar)
    if not castBar then return end
    if castBar._arenaColorHooked then return end -- Already hooked
    
    -- Apply our texture AFTER Blizzard sets theirs.
    -- Do not recolor here; Blizzard is still mid-update and will just fight us.
    hooksecurefunc(castBar, "SetStatusBarTexture", function(self)
        local customTexture = CastBars:GetCastBarTexture()
        if customTexture and not self._settingCustomTexture then
            self._settingCustomTexture = true
            getmetatable(self).__index.SetStatusBarTexture(self, customTexture)
            self._settingCustomTexture = nil
            
            -- Blizzard atlas textures carry pill-shaped masks, strip them every time
            local tex = self:GetStatusBarTexture()
            if tex and tex.GetMaskTextures then
                local masks = { tex:GetMaskTextures() }
                for _, mask in ipairs(masks) do
                    tex:RemoveMaskTexture(mask)
                end
            end
        end
    end)
    
    -- OnEvent fires after Blizzard finishes its internal updates (including shield visibility).
    castBar:HookScript("OnEvent", function(self, event, eventUnit)
        CastBars:ApplyCastBarColor(self)
    end)
    
    -- Handle mid-cast interruptibility changes.
    -- Midnight can hand us "secret" values, so keep logic boring and safe.
    if castBar.UpdateInterruptibleState then
        hooksecurefunc(castBar, "UpdateInterruptibleState", function(self)
            CastBars:ApplyCastBarColor(self)
        end)
    end

    -- Shield visibility gets flipped here too.
    if castBar.UpdateIconShown then
        hooksecurefunc(castBar, "UpdateIconShown", function(self)
            CastBars:ApplyCastBarColor(self)
        end)
    end
    
    castBar._arenaColorHooked = true
end

-- Pre-created ColorMixin objects for SetVertexColorFromBoolean (C-side, taint-safe).
-- notInterruptible == true  → grey
-- notInterruptible == false → normal cast / channel color
local COLOR_UNINTERRUPTIBLE = CreateColor(0.7, 0.7, 0.7, 1)
local COLOR_CAST_NORMAL     = CreateColor(1.0, 0.7, 0.0, 1)
local COLOR_CHANNEL_NORMAL  = CreateColor(0.0, 1.0, 0.0, 1)
local COLOR_OVERLAY_SHOW    = CreateColor(0.7, 0.7, 0.7, 1)
local COLOR_OVERLAY_HIDE    = CreateColor(0.7, 0.7, 0.7, 0)

function CastBars:ApplyCastBarColor(castBar)
    if not castBar then return end

    self:EnsureBorderShield(castBar)
    self:SyncBorderShieldToIcon(castBar)

    local unitToken = castBar._acUnitToken
    if not unitToken then return end

    if not (castBar.casting or castBar.channeling or castBar.empowering or castBar.reverseChanneling) then
        castBar._acForcedGrey = nil
        -- Hide diagonal overlay when not casting
        if castBar.DiagonalOverlay then
            castBar.DiagonalOverlay:Hide()
        end
        return
    end

    -- Midnight may give a secret boolean here.
    -- Don't do fancy Lua logic with it; just hand it to the C-side helper.
    local notInterruptible
    if castBar.channeling or castBar.reverseChanneling then
        local _name, _text, _texture, _startTime, _endTime, _isTradeSkill, ni = UnitChannelInfo(unitToken)
        notInterruptible = ni
    elseif castBar.casting or castBar.empowering then
        local _name, _text, _texture, _startTime, _endTime, _isTradeSkill, _castID, ni = UnitCastingInfo(unitToken)
        notInterruptible = ni
    end

    if notInterruptible == nil then
        notInterruptible = false
    end

    local tex = castBar.GetStatusBarTexture and castBar:GetStatusBarTexture() or nil
    if not tex then return end

    -- Normal colors based on cast vs channel (these flags are safe).
    local normalColor = castBar.channeling and COLOR_CHANNEL_NORMAL or COLOR_CAST_NORMAL

    -- This is kind of important I guess: keep the bar color neutral so the vertex coloring is the only thing that matters.
    if castBar.SetStatusBarColor then
        castBar:SetStatusBarColor(1, 1, 1, 1)
    end

    -- C-side helper that accepts secret booleans.
    if tex.SetVertexColorFromBoolean then
        tex:SetVertexColorFromBoolean(notInterruptible, COLOR_UNINTERRUPTIBLE, normalColor)
    else
        -- Non-Midnight fallback (regular boolean).
        if notInterruptible then
            tex:SetVertexColor(0.7, 0.7, 0.7, 1)
        elseif castBar.channeling then
            tex:SetVertexColor(0, 1, 0, 1)
        else
            tex:SetVertexColor(1, 0.7, 0, 1)
        end
    end
    
    if castBar.DiagonalOverlay then
        castBar.DiagonalOverlay:SetAllPoints(tex)
        local unintTex = self:GetUninterruptibleCastBarTexture()
        castBar.DiagonalOverlay:SetTexture(unintTex)
        castBar.DiagonalOverlay:Show()
        if castBar.DiagonalOverlay.SetVertexColorFromBoolean then
            castBar.DiagonalOverlay:SetVertexColorFromBoolean(notInterruptible, COLOR_OVERLAY_SHOW, COLOR_OVERLAY_HIDE)
        else
            if notInterruptible then
                castBar.DiagonalOverlay:SetVertexColor(0.7, 0.7, 0.7, 1)
            else
                castBar.DiagonalOverlay:SetVertexColor(0.7, 0.7, 0.7, 0)
            end
        end
    end
end

-- ============================================================================
-- ENSURE BORDER SHIELD EXISTS
-- Creates shield icon overlay for uninterruptible casts if not present
-- Creates clean background behind cast bar
-- ============================================================================

function CastBars:AddBackground(castBar)
    if not castBar then return end
    if castBar.arenaBackground then return end -- Already has background
    
    -- Create background frame
    castBar.arenaBackground = CreateFrame("Frame", nil, castBar)
    castBar.arenaBackground:SetAllPoints()
    castBar.arenaBackground:SetFrameLevel(castBar:GetFrameLevel() - 1)
    
    -- Create semi-transparent black texture
    castBar.arenaBackground.texture = castBar.arenaBackground:CreateTexture(nil, "BACKGROUND")
    castBar.arenaBackground.texture:SetAllPoints()
    castBar.arenaBackground.texture:SetColorTexture(0, 0, 0, 0.5) -- Semi-transparent black
    castBar.arenaBackground:Show()
end

-- ============================================================================
-- CENTER TEXT VERTICALLY
-- Moves spell name text to center of cast bar instead of bottom
-- ============================================================================

function CastBars:CenterText(castBar)
    if not castBar then return end
    if not castBar.Text then return end
    
    -- Use ArenaCore's custom font for consistency
    local AC = _G.ArenaCore
    local fontPath = AC and AC.FONT_PATH or "Interface\\AddOns\\ArenaCore\\Media\\Fonts\\arenacore.ttf"
    local fontSize = 12 -- Standard cast bar text size
    
    -- Set custom font with outline
    castBar.Text:SetFont(fontPath, fontSize, "OUTLINE")
    
    -- Clear current text anchor and reposition to center
    castBar.Text:ClearAllPoints()
    castBar.Text:SetPoint("CENTER", castBar, "CENTER", 0, 0)
end

-- ============================================================================
-- ADD PIXEL BORDER (Black outline around cast bar)
-- ============================================================================

function CastBars:AddPixelBorder(castBar)
    if not castBar then return end
    if castBar.pixelBorder then return end -- Already has border
    
    local borderSize = 2 -- 2 pixel border (thicker outline)
    
    -- Create holder frame for borders
    castBar.pixelBorder = CreateFrame("Frame", nil, castBar)
    castBar.pixelBorder:SetAllPoints()
    castBar.pixelBorder:SetFrameLevel(castBar:GetFrameLevel() + 1)
    
    local holder = castBar.pixelBorder
    
    -- Create 4 edge textures (top, bottom, left, right)
    holder.edges = {}
    for i = 1, 4 do
        local tex = holder:CreateTexture(nil, "OVERLAY", nil, 7)
        tex:SetColorTexture(0, 0, 0, 1) -- Solid black
        holder.edges[i] = tex
    end
    
    local top, bottom, left, right = holder.edges[1], holder.edges[2], holder.edges[3], holder.edges[4]
    
    -- Top edge
    top:ClearAllPoints()
    PixelSetPoint(top, "TOPLEFT", castBar, "TOPLEFT", -borderSize, borderSize)
    PixelSetPoint(top, "TOPRIGHT", castBar, "TOPRIGHT", borderSize, borderSize)
    PixelSetHeight(top, borderSize)
    
    -- Bottom edge
    bottom:ClearAllPoints()
    PixelSetPoint(bottom, "BOTTOMLEFT", castBar, "BOTTOMLEFT", -borderSize, -borderSize)
    PixelSetPoint(bottom, "BOTTOMRIGHT", castBar, "BOTTOMRIGHT", borderSize, -borderSize)
    PixelSetHeight(bottom, borderSize)
    
    -- Left edge
    left:ClearAllPoints()
    PixelSetPoint(left, "TOPLEFT", castBar, "TOPLEFT", -borderSize, borderSize)
    PixelSetPoint(left, "BOTTOMLEFT", castBar, "BOTTOMLEFT", -borderSize, -borderSize)
    PixelSetWidth(left, borderSize)
    
    -- Right edge
    right:ClearAllPoints()
    PixelSetPoint(right, "TOPRIGHT", castBar, "TOPRIGHT", borderSize, borderSize)
    PixelSetPoint(right, "BOTTOMRIGHT", castBar, "BOTTOMRIGHT", borderSize, -borderSize)
    PixelSetWidth(right, borderSize)
    
    holder:Show()
end

-- ============================================================================
-- ADD SPELL ICON BORDER (Black outline around spell icon)
-- Creates black pixel border around the spell icon to match cast bar styling
-- ============================================================================

function CastBars:AddSpellIconBorder(castBar)
    if not castBar then return end
    if not castBar.Icon then return end -- No icon to border
    if castBar.Icon._borderFrame then return end -- Already has border
    
    -- Get icon's parent frame (legacy approach)
    local iconParent = castBar.Icon:GetParent()
    
    -- Create border frame for spell icon (parented to icon's parent, not castBar)
    local iconBorderFrame = CreateFrame("Frame", nil, iconParent)
    iconBorderFrame:SetAllPoints(castBar.Icon)
    iconBorderFrame:SetFrameLevel(castBar:GetFrameLevel() + 11)  -- Above cast bar borders

    local uiScale = (UIParent and UIParent.GetEffectiveScale and UIParent:GetEffectiveScale()) or 1
    local iconScale = (castBar.Icon.GetEffectiveScale and castBar.Icon:GetEffectiveScale()) or 1
    local borderSize = 2
    if iconScale and iconScale > 0 then
        borderSize = 2 * (uiScale / iconScale)
    end
    
    -- Top border (2px, no offset)
    local top = iconBorderFrame:CreateTexture(nil, "OVERLAY")
    top:SetColorTexture(0, 0, 0, 1)
    top:SetPoint("TOPLEFT", 0, 0)
    top:SetPoint("TOPRIGHT", 0, 0)
    top:SetHeight(borderSize)
    
    -- Bottom border
    local bottom = iconBorderFrame:CreateTexture(nil, "OVERLAY")
    bottom:SetColorTexture(0, 0, 0, 1)
    bottom:SetPoint("BOTTOMLEFT", 0, 0)
    bottom:SetPoint("BOTTOMRIGHT", 0, 0)
    bottom:SetHeight(borderSize)
    
    -- Left border
    local left = iconBorderFrame:CreateTexture(nil, "OVERLAY")
    left:SetColorTexture(0, 0, 0, 1)
    left:SetPoint("TOPLEFT", 0, 0)
    left:SetPoint("BOTTOMLEFT", 0, 0)
    left:SetWidth(borderSize)
    
    -- Right border
    local right = iconBorderFrame:CreateTexture(nil, "OVERLAY")
    right:SetColorTexture(0, 0, 0, 1)
    right:SetPoint("TOPRIGHT", 0, 0)
    right:SetPoint("BOTTOMRIGHT", 0, 0)
    right:SetWidth(borderSize)
    
    castBar.Icon._borderFrame = iconBorderFrame
end

-- ============================================================================
-- POSITION CAST BAR
-- ============================================================================

function CastBars:PositionCastBar(index)
    local castBar = self.bars[index]
    if not castBar then return end
    
    local MFM = AC.MasterFrameManager
    local acFrame = MFM and MFM.frames and MFM.frames[index]
    if not acFrame then return end
    
    local db = AC.DB and AC.DB.profile and AC.DB.profile.castBars
    local settings = db or {
        positioning = { horizontal = -4, vertical = -86 },
        sizing = { width = 220, height = 24 },
    }
    
    local pos = settings.positioning or {}
    local sizing = settings.sizing or {}
    
    -- Fallback defaults.
    local horizontal = pos.horizontal or -4
    local vertical = pos.vertical or -86
    local width = sizing.width or 220
    local height = sizing.height or 24
    
    -- Anchor to TOP->TOP so test mode and real arena don't drift apart for fun.
    castBar:ClearAllPoints()
    
    -- Use _originalSetPoint so we can still move it even if something overrides SetPoint later.
    if castBar._originalSetPoint then
        PixelSetPoint(castBar, "TOP", acFrame, "TOP", horizontal, vertical)
    else
        PixelSetPoint(castBar, "TOP", acFrame, "TOP", horizontal, vertical)
    end
    
    PixelSetSize(castBar, width, height)
end

-- ============================================================================
-- REFRESH ALL CAST BAR POSITIONS
-- Called when settings change
-- ============================================================================

function CastBars:RefreshLayout()
    for i = 1, MAX_ARENA_ENEMIES do
        self:PositionCastBar(i)
        -- Reapply blocky style after positioning to prevent texture reset
        local castBar = self.bars[i]
        if castBar then
            self:ApplyBlockyStyle(castBar)
            -- Apply spell icon settings from UI
            self:ApplySpellIconSettings(castBar)
        end
    end
    
    -- If in test mode, ensure cast bars are visible
    if AC.testModeEnabled then
        self:ShowTestCastBars()
    end
end

-- ============================================================================
-- APPLY SPELL ICON SETTINGS
-- Applies positioning and scale from UI settings to cast bar Icon
-- ============================================================================

function CastBars:ApplySpellIconSettings(castBar)
    if not castBar then return end
    if not castBar.Icon then return end
    
    -- Read spell icon settings from database
    local db = AC.DB and AC.DB.profile and AC.DB.profile.castBars
    local iconSettings = db and db.spellIcons
    
    if not iconSettings then return end
    
    -- Check if enabled
    local enabled = iconSettings.enabled
    if enabled == nil then enabled = true end
    
    if enabled then
        castBar.Icon:SetAlpha(1)
        castBar.Icon:Show()
        if castBar.BorderShield then
            castBar.BorderShield:SetAlpha(1)
        end
        -- Show the border frame when icon is enabled
        if castBar.Icon._borderFrame then
            castBar.Icon._borderFrame:Show()
        end
    else
        castBar.Icon:SetAlpha(0)
        castBar.Icon:Hide()
        if castBar.BorderShield then
            castBar.BorderShield:SetAlpha(0)
        end
        -- If the icon is hidden, the border should stop existing too.
        if castBar.Icon._borderFrame then
            castBar.Icon._borderFrame:Hide()
        end
        return -- Don't apply positioning if disabled
    end
    
    -- Apply positioning
    local pos = iconSettings.positioning or {}
    local horizontal = pos.horizontal or -4
    local vertical = pos.vertical or 0
    
    -- Apply scale
    local sizing = iconSettings.sizing or {}
    local scale = (sizing.scale or 100) / 100
    
    -- Reposition icon relative to cast bar
    castBar.Icon:ClearAllPoints()
    castBar.Icon:SetPoint("RIGHT", castBar, "LEFT", horizontal, vertical)
    castBar.Icon:SetScale(scale)
    
    self:EnsureBorderShield(castBar)
    self:SyncBorderShieldToIcon(castBar)
    
    -- Also scale and reposition the icon border to match the icon
    if castBar.Icon._borderFrame then
        castBar.Icon._borderFrame:ClearAllPoints()
        castBar.Icon._borderFrame:SetAllPoints(castBar.Icon)
    end
end

-- ============================================================================
-- TEST MODE: Show Cast Bars with Fake Data
-- ============================================================================

function CastBars:ShowTestCastBars()
    if not isMidnight then return end
    
    for i = 1, MAX_ARENA_ENEMIES do
        local castBar = self.bars[i]
        local testData = TEST_CAST_DATA[i]
        
        if castBar and testData then
            -- Show cast bar
            castBar:Show()
            castBar:SetAlpha(1)
            
            -- Set icon
            if castBar.Icon then
                castBar.Icon:SetTexture(testData.icon)
                castBar.Icon:Show()
                -- DEBUG DISABLED: print("|cff00FF00[CastBars]|r Frame " .. i .. " Icon exists, shown: " .. tostring(castBar.Icon:IsShown()))
            else
                -- DEBUG DISABLED: print("|cffFF0000[CastBars]|r Frame " .. i .. " Icon is nil!")
            end
            
            -- Set text
            if castBar.Text then
                castBar.Text:SetText(testData.name)
            end
            
            -- Reapply blocky style FIRST to ensure BorderShield is created
            self:ApplyBlockyStyle(castBar)
            
            -- Set colors based on cast type
            if testData.uninterruptible then
                castBar:SetStatusBarColor(0.7, 0.7, 0.7, 1) -- Gray for uninterruptible
                -- Show shield icon (Blizzard's BorderShield)
                if castBar.BorderShield then
                    castBar.BorderShield:Show()
                    -- DEBUG DISABLED: print("|cff00FF00[CastBars]|r Frame " .. i .. " BorderShield:Show() called")
                else
                    -- DEBUG DISABLED: print("|cffFF0000[CastBars]|r Frame " .. i .. " BorderShield is nil!")
                end
                -- Show diagonal line overlay for uninterruptible (absorb shield pattern)
                if castBar.DiagonalOverlay then
                    castBar.DiagonalOverlay:SetAllPoints(castBar:GetStatusBarTexture())
                    castBar.DiagonalOverlay:Show()
                end
            elseif testData.channel then
                castBar:SetStatusBarColor(0, 1, 0, 1) -- Green for channel
                if castBar.BorderShield then
                    castBar.BorderShield:Hide()
                end
                -- Hide diagonal overlay for normal casts
                if castBar.DiagonalOverlay then
                    castBar.DiagonalOverlay:Hide()
                end
            else
                castBar:SetStatusBarColor(1, 0.7, 0, 1) -- Orange for regular cast
                if castBar.BorderShield then
                    castBar.BorderShield:Hide()
                end
                -- Hide diagonal overlay for normal casts
                if castBar.DiagonalOverlay then
                    castBar.DiagonalOverlay:Hide()
                end
            end
            
            -- Animate the cast bar
            self:AnimateTestCastBar(castBar, testData.duration, i)
        end
    end
end

-- ============================================================================
-- ANIMATE TEST CAST BAR
-- Creates realistic cast bar animation in test mode
-- ============================================================================

function CastBars:AnimateTestCastBar(castBar, duration, index)
    if not castBar then return end
    
    -- Cancel any existing animation
    if castBar._testTicker then
        castBar._testTicker:Cancel()
        castBar._testTicker = nil
    end
    
    local startTime = GetTime()
    local endTime = startTime + duration
    
    -- Set initial value
    castBar:SetMinMaxValues(0, 1)
    castBar:SetValue(0)
    
    -- Create animation ticker
    castBar._testTicker = C_Timer.NewTicker(0.02, function()
        if not AC.testModeEnabled then
            castBar._testTicker:Cancel()
            castBar._testTicker = nil
            castBar:Hide()
            return
        end
        
        local now = GetTime()
        local elapsed = now - startTime
        local progress = elapsed / duration
        
        if progress >= 1 then
            -- Reset and loop
            startTime = GetTime()
            endTime = startTime + duration
            castBar:SetValue(0)
        else
            castBar:SetValue(progress)
        end
    end)
    
    -- Register ticker for cleanup
    if AC.RegisterTicker then
        AC:RegisterTicker(castBar._testTicker, "castbar_test_" .. index)
    end
end

-- ============================================================================
-- HIDE TEST CAST BARS
-- Called when exiting test mode
-- ============================================================================

function CastBars:HideTestCastBars()
    for i = 1, MAX_ARENA_ENEMIES do
        local castBar = self.bars[i]
        if castBar then
            -- Cancel animation
            if castBar._testTicker then
                castBar._testTicker:Cancel()
                castBar._testTicker = nil
            end
            
            -- Hide cast bar
            castBar:Hide()
            castBar:SetAlpha(0)
        end
    end
end

-- ============================================================================
-- HOOK INTO ARENACORE SYSTEMS
-- ============================================================================

-- Hook into test mode enable
local function OnTestModeEnabled()
    C_Timer.After(0.5, function()
        CastBars:SetupCastBars()
        CastBars:RefreshLayout()
    end)
end

-- Hook into test mode disable
local function OnTestModeDisabled()
    CastBars:HideTestCastBars()
end

-- Hook into arena entry
local function OnArenaEnter()
    C_Timer.After(0.3, function()
        CastBars:SetupCastBars()
        CastBars:RefreshLayout()
    end)
end

-- ============================================================================
-- EXPOSED API FOR SETTINGS PAGE
-- ============================================================================

function AC:RefreshCastBarsLayout()
    if CastBars.RefreshLayout then
        CastBars:RefreshLayout()
    end
end

-- ============================================================================
-- EVENT HANDLING
-- ============================================================================

local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
eventFrame:RegisterEvent("ARENA_PREP_OPPONENT_SPECIALIZATIONS")
eventFrame:RegisterEvent("ARENA_OPPONENT_UPDATE")

eventFrame:SetScript("OnEvent", function(self, event, ...)
    if event == "PLAYER_ENTERING_WORLD" then
        local _, instanceType = IsInInstance()
        if instanceType == "arena" then
            OnArenaEnter()
        end
    elseif event == "ARENA_PREP_OPPONENT_SPECIALIZATIONS" or event == "ARENA_OPPONENT_UPDATE" then
        CastBars:ClearAllCastBars()
        
        C_Timer.After(0.2, function()
            CastBars:SetupCastBars()
            CastBars:RefreshLayout()
        end)
    end
end)

-- ============================================================================
-- INITIALIZATION
-- ============================================================================

-- Initialize after a short delay to ensure frames exist
C_Timer.After(1, function()
    CastBars:Initialize()
end)

-- Hook into MFM test mode (with delay to ensure MFM exists)
C_Timer.After(2, function()
    if not AC.MasterFrameManager then return end
    
    local oldEnableTestMode = AC.MasterFrameManager.EnableTestMode
    if oldEnableTestMode then
        AC.MasterFrameManager.EnableTestMode = function(self, ...)
            local result = oldEnableTestMode(self, ...)
            OnTestModeEnabled()
            return result
        end
    end
    
    local oldDisableTestMode = AC.MasterFrameManager.DisableTestMode
    if oldDisableTestMode then
        AC.MasterFrameManager.DisableTestMode = function(self, ...)
            OnTestModeDisabled()
            return oldDisableTestMode(self, ...)
        end
    end
end)

-- DEBUG DISABLED: print("|cff00FF00ArenaCore:|r Cast Bars module loaded")
