local _, ns = ...

-- Castbar module
local Castbar = {}
ns.Castbar = Castbar

-- Store castbar frames
Castbar.frames = {}
Castbar.anchors = {}

-- Media paths
local STATUSBAR_TEXTURE = "Interface\\AddOns\\nCastbar\\Media\\statusbarTexture"
local BORDER_TEXTURE = "Interface\\Tooltips\\UI-Tooltip-Border"

-- Create beauty border using Beautycase
local function CreateBorder(frame, size)
    size = size or 11
    frame:CreateBeautyBorder(size)
    frame:SetBeautyBorderPadding(2.66)
    return frame
end

-- Update border color using Beautycase
local function UpdateBorderColor(frame, r, g, b)
    if frame.beautyBorder then
        frame:SetBeautyBorderColor(r, g, b, 1)
    end
    -- Icon border color update (if icon frame has beauty border)
    if frame.iconFrame and frame.iconFrame.beautyBorder then
        frame.iconFrame:SetBeautyBorderColor(r, g, b, 1)
    end
end

-- Create anchor frame for positioning
function Castbar:CreateAnchor(unit, text)
    local config = nCastbarDB.profile[unit]
    if not config then return end
    
    local width = (config.width + config.height) * config.scale
    local height = config.height * config.scale
    
    local anchor = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
    anchor:SetSize(width, height)
    anchor:SetScale(1.193)
    
    if config.position then
        anchor:SetPoint(unpack(config.position))
    else
        anchor:SetPoint("CENTER")
    end
    
    anchor:SetFrameStrata("HIGH")
    anchor:SetMovable(true)
    anchor:SetClampedToScreen(true)
    anchor:SetUserPlaced(true)
    
    -- Backdrop for background
    anchor:SetBackdrop({
        bgFile = "Interface\\MINIMAP\\TooltipBackdrop-Background"
    })
    
    -- Beautycase border
    anchor:CreateBeautyBorder(11)
    anchor:SetBeautyBorderPadding(2.66)
    
    anchor:EnableMouse(true)
    anchor:RegisterForDrag("LeftButton")
    anchor:Hide()
    
    anchor.text = anchor:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    anchor.text:SetAllPoints(anchor)
    anchor.text:SetText(text .. " Castbar")
    
    anchor:SetScript("OnDragStart", function(self) self:StartMoving() end)
    anchor:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        -- Save position
        local point, _, relativePoint, xOfs, yOfs = self:GetPoint()
        config.position = {point, "UIParent", relativePoint, xOfs, yOfs}
    end)
    
    self.anchors[unit] = anchor
    return anchor
end

-- Create castbar for unit
function Castbar:Create(unit)
    local config = nCastbarDB.profile[unit]
    if not config or not config.enabled then
        ns.Debug("Castbar disabled for", unit)
        return
    end
    
    ns.Debug("Creating castbar for", unit)
    
    -- Create anchor if it doesn't exist
    if not self.anchors[unit] then
        local unitName = unit:gsub("^%l", string.upper)
        self:CreateAnchor(unit, unitName)
    end
    
    -- Create the castbar frame
    local castbar = CreateFrame("StatusBar", nil, UIParent)
    castbar:SetStatusBarTexture(STATUSBAR_TEXTURE)
    castbar:SetSize(config.width, config.height)
    castbar:SetScale(config.scale)
    castbar:SetMinMaxValues(0, 1)
    castbar:SetValue(0)
    
    -- Position
    if self.anchors[unit] then
        castbar:SetPoint("CENTER", self.anchors[unit])
    elseif config.position then
        castbar:SetPoint(unpack(config.position))
    end
    
    -- Background
    castbar.bg = castbar:CreateTexture(nil, "BACKGROUND")
    castbar.bg:SetColorTexture(0, 0, 0, 0.5)
    castbar.bg:SetAllPoints(castbar)
    
    -- Border
    CreateBorder(castbar)
    
    -- Store references
    castbar.baseWidth = config.width
    
    -- Spell text (create without positioning yet)
    -- Use a named FontObject per unit so SetFontObject always works and overrides any template
    local fontObjName = "nCastbar_Font_" .. unit
    if not _G[fontObjName] then
        _G[fontObjName] = CreateFont(fontObjName)
    end
    local spellFont = _G[fontObjName]
    do
        local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
        local fontSize = config.spellNameFontSize or 12
        local outline  = config.spellNameOutline or "OUTLINE"
        if outline == "NONE" then outline = "" end
        local fontName = config.spellNameFont or "Friz Quadrata TT"
        local fontPath = (LSM and LSM:Fetch("font", fontName))
                      or "Interface\\AddOns\\nCastbar\\Media\\FRIZQT__.ttf"
        spellFont:SetFont(fontPath, fontSize, outline)
        spellFont:SetTextColor(1, 1, 1, 1)
    end
    castbar.text = castbar:CreateFontString(nil, "OVERLAY")
    castbar.text:SetFontObject(spellFont)
    castbar.text:SetJustifyH("LEFT")
    castbar.text:SetWordWrap(false)
    castbar.spellFont = spellFont  -- store ref for later updates
    
    -- Time text (create without positioning yet)
    castbar.time = castbar:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    castbar.time:SetJustifyH("RIGHT")
    
    -- Icon (must be created before UpdateTextPosition!)
    if config.icon and config.icon.show then
        -- Create icon frame (needed for Beautycase border)
        castbar.iconFrame = CreateFrame("Frame", nil, castbar)
        castbar.iconFrame:SetSize(config.height, config.height)
        
        -- Create icon texture inside the frame
        castbar.icon = castbar.iconFrame:CreateTexture(nil, "ARTWORK")
        castbar.icon:SetAllPoints(castbar.iconFrame)
        castbar.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
        
        -- Position icon
        if config.icon.position == "LEFT" then
            if config.icon.positionOutside then
                castbar.iconFrame:SetPoint("RIGHT", castbar, "LEFT", -8, 0)
            else
                castbar.iconFrame:SetPoint("LEFT", castbar, "LEFT", 2, 0)
            end
        else
            if config.icon.positionOutside then
                castbar.iconFrame:SetPoint("LEFT", castbar, "RIGHT", 8, 0)
            else
                castbar.iconFrame:SetPoint("RIGHT", castbar, "RIGHT", -2, 0)
            end
        end
        
        if config.icon.positionOutside then
            castbar.iconFrame:CreateBeautyBorder(10)
            castbar.iconFrame:SetBeautyBorderPadding(2)
        end
    end
    
    -- Position text based on config (AFTER icon is created!)
    self:UpdateTextPosition(castbar, config)
    
    -- Player-specific features
    if unit == "player" then
        -- Safezone
        if config.showSafezone then
            castbar.safezone = castbar:CreateTexture(nil, "OVERLAY")
            castbar.safezone:SetColorTexture(unpack(config.colors.safezone))
            castbar.safezone:SetPoint("TOPRIGHT")
            castbar.safezone:SetPoint("BOTTOMRIGHT")
        end
        
        -- Latency text
        if config.showLatency then
            castbar.latency = castbar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            castbar.latency:SetPoint("RIGHT", castbar, "BOTTOMRIGHT", -2, -2)
            castbar.latency:SetTextColor(0.6, 0.6, 0.6, 1)
        end
    end
    
    -- Store config reference
    castbar.unit = unit
    castbar.config = config
    
    -- Create fade out animation
    castbar.fadeOut = castbar:CreateAnimationGroup()
    local fadeOutAnim = castbar.fadeOut:CreateAnimation("Alpha")
    fadeOutAnim:SetFromAlpha(1)
    fadeOutAnim:SetToAlpha(0)
    fadeOutAnim:SetDuration(0.3)
    fadeOutAnim:SetSmoothing("IN")
    
    castbar.fadeOut:SetScript("OnFinished", function(self)
        local frame = self:GetParent()
        frame:Hide()
        frame:SetAlpha(1)  -- Reset alpha for next show
    end)
    
    -- Hide initially
    castbar:Hide()
    
    -- Store frame reference
    self.frames[unit] = castbar
    
    -- Register for events
    self:RegisterEvents(castbar)
    
    ns.Debug("Castbar created for", unit)
    return castbar
end

-- Update text position based on config
function Castbar:UpdateTextPosition(castbar, config)
    -- Clear all points
    castbar.text:ClearAllPoints()
    castbar.time:ClearAllPoints()
    
    local textPosition = config.textPosition or "inside"
    local showTime = config.showTime ~= false  -- Default true
    local hasIconInside = castbar.iconFrame and config.icon and config.icon.show and not config.icon.positionOutside
    
    if textPosition == "above" then
        -- Text above the castbar
        castbar.text:SetPoint("BOTTOM", castbar, "TOP", 0, 2)
        castbar.text:SetWidth(config.width)
        castbar.text:SetJustifyH("CENTER")
        
        -- Time on the right side of text (if shown)
        if showTime then
            castbar.time:SetPoint("LEFT", castbar.text, "RIGHT", 4, 0)
            castbar.time:Show()
        else
            castbar.time:Hide()
        end
        
    elseif textPosition == "below" then
        -- Text below the castbar
        castbar.text:SetPoint("TOP", castbar, "BOTTOM", 0, -2)
        castbar.text:SetWidth(config.width)
        castbar.text:SetJustifyH("CENTER")
        
        -- Time on the right side of text (if shown)
        if showTime then
            castbar.time:SetPoint("LEFT", castbar.text, "RIGHT", 4, 0)
            castbar.time:Show()
        else
            castbar.time:Hide()
        end
        
    else  -- "inside" (default)
        -- Text inside the castbar
        if hasIconInside and config.icon.position == "LEFT" then
            castbar.text:SetPoint("LEFT", castbar.iconFrame, "RIGHT", 4, 0)
        else
            castbar.text:SetPoint("LEFT", castbar, "LEFT", 4, 0)
        end
        
        if showTime then
            if hasIconInside and config.icon.position == "RIGHT" then
                castbar.text:SetPoint("RIGHT", castbar.iconFrame, "LEFT", -4, 0)
                castbar.time:SetPoint("RIGHT", castbar.iconFrame, "LEFT", -4, 0)
            else
                castbar.text:SetPoint("RIGHT", castbar, "CENTER", -10, 0)
                castbar.time:SetPoint("RIGHT", castbar, "RIGHT", -4, 0)
            end
            castbar.time:Show()
        else
            -- If time is hidden, text can use full width
            if hasIconInside and config.icon.position == "RIGHT" then
                castbar.text:SetPoint("RIGHT", castbar.iconFrame, "LEFT", -4, 0)
            else
                castbar.text:SetPoint("RIGHT", castbar, "RIGHT", -4, 0)
            end
            castbar.time:Hide()
        end
    end
end

-- Update castbar width dynamically based on spell name
function Castbar:UpdateDynamicWidth(castbar, spellName)
    local config = castbar.config
    
    if not config.dynamicWidth then
        -- Reset to base width if dynamic width is disabled
        if castbar:GetWidth() ~= config.width then
            castbar:SetWidth(config.width)
        end
        return
    end
    
    -- Calculate text width
    castbar.text:SetText(spellName)
    local textWidth = castbar.text:GetStringWidth()
    
    -- Add padding for time text if shown
    local padding = 40  -- Base padding for borders and spacing
    if config.showTime ~= false then
        padding = padding + 50  -- Additional space for time text
    end
    
    -- Add space for icon if inside
    if config.icon and config.icon.show and not config.icon.positionOutside then
        padding = padding + config.height + 8
    end
    
    -- Calculate new width
    local newWidth = math.max(config.width, textWidth + padding)
    newWidth = math.min(newWidth, 500)  -- Max width cap
    
    -- Apply new width
    castbar:SetWidth(newWidth)
end

-- Register events for castbar
function Castbar:RegisterEvents(castbar)
    local unit = castbar.unit
    
    -- player und pet: RegisterUnitEvent reicht, da diese Units immer bekannt sind
    -- target und focus: RegisterEvent (globales Event) nötig, damit auch NPCs und
    -- fremde Spieler erfasst werden, denn RegisterUnitEvent liefert für diese Units
    -- keine Events wenn sie nicht in der eigenen Gruppe sind.
    local function reg(eventName)
        if unit == "player" or unit == "pet" then
            castbar:RegisterUnitEvent(eventName, unit)
        else
            castbar:RegisterEvent(eventName)
        end
    end
    
    reg("UNIT_SPELLCAST_START")
    reg("UNIT_SPELLCAST_STOP")
    reg("UNIT_SPELLCAST_FAILED")
    reg("UNIT_SPELLCAST_INTERRUPTED")
    reg("UNIT_SPELLCAST_DELAYED")
    reg("UNIT_SPELLCAST_CHANNEL_START")
    reg("UNIT_SPELLCAST_CHANNEL_STOP")
    reg("UNIT_SPELLCAST_CHANNEL_UPDATE")
    reg("UNIT_SPELLCAST_INTERRUPTIBLE")
    reg("UNIT_SPELLCAST_NOT_INTERRUPTIBLE")
    
    castbar:SetScript("OnEvent", function(self, event, ...)
        Castbar:OnEvent(self, event, ...)
    end)
    
    castbar:SetScript("OnUpdate", function(self, elapsed)
        Castbar:OnUpdate(self, elapsed)
    end)
end

-- Event handler
function Castbar:OnEvent(castbar, event, unit, ...)
    -- Bei globalen Events kommt als erstes Argument ein beliebiger Unit-Token
    -- (z.B. "party1", "nameplate3" etc.) – NICHT zwingend "target" oder "focus".
    -- Daher prüfen wir mit UnitIsUnit(), ob die gemeldete Unit unserer
    -- zugewiesenen Unit entspricht.
    if unit ~= castbar.unit and not UnitIsUnit(unit, castbar.unit) then return end
    
    -- Ab hier immer castbar.unit verwenden (nicht den rohen Event-Token)
    local resolvedUnit = castbar.unit
    
    ns.DebugLog(string.format("=== EVENT: %s for unit=%s (token=%s), currentSpell=%s, isShown=%s", 
        event, resolvedUnit, tostring(unit), tostring(castbar.currentSpell), tostring(castbar:IsShown())))
    
    if event == "UNIT_SPELLCAST_START" then
        self:OnCastStart(castbar, resolvedUnit, false)
    elseif event == "UNIT_SPELLCAST_CHANNEL_START" then
        self:OnCastStart(castbar, resolvedUnit, true)
    elseif event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP" then
        self:OnCastStop(castbar, resolvedUnit)
    elseif event == "UNIT_SPELLCAST_FAILED" then
        self:OnCastFailed(castbar, resolvedUnit)
    elseif event == "UNIT_SPELLCAST_INTERRUPTED" then
        self:OnCastInterrupted(castbar, resolvedUnit)
    elseif event == "UNIT_SPELLCAST_DELAYED" or event == "UNIT_SPELLCAST_CHANNEL_UPDATE" then
        self:OnCastDelayed(castbar, resolvedUnit)
    elseif event == "UNIT_SPELLCAST_INTERRUPTIBLE" then
        ns.DebugLog(">>> Cast is now interruptible")
        self:UpdateInterruptible(castbar, false)
    elseif event == "UNIT_SPELLCAST_NOT_INTERRUPTIBLE" then
        ns.DebugLog(">>> Cast is now NOT interruptible")
        self:UpdateInterruptible(castbar, true)
    end
end

-- Cast start
function Castbar:OnCastStart(castbar, unit, channeled)
    local name, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible
    
    if channeled then
        name, text, texture, startTime, endTime, isTradeSkill, notInterruptible = UnitChannelInfo(unit)
    else
        name, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo(unit)
    end
    
    ns.DebugLog(string.format(">>> OnCastStart: unit=%s, channeled=%s, name=%s", 
        tostring(unit), tostring(channeled), tostring(name)))
    
    if not name then 
        ns.DebugLog("!!! OnCastStart: No spell name returned, aborting")
        return 
    end
    
    -- Check if we should ignore this spell (pet specific)
    if unit == "pet" and castbar.config.ignoreSpells then
        for _, spellID in ipairs(castbar.config.ignoreList) do
            local spellName = C_Spell.GetSpellName(spellID)
            if name == spellName then
                ns.DebugLog(">>> Ignoring pet spell:", name)
                castbar:Hide()
                return
            end
        end
    end
    
    -- Safely handle potentially tainted values with pcall
    local success, calcStartTime, calcEndTime, calcDuration = pcall(function()
        if not startTime or not endTime then
            error("Missing time values")
        end
        local st = startTime / 1000
        local et = endTime / 1000
        return st, et, et - st
    end)
    
    -- If we can't calculate times (tainted or missing values), bail out
    if not success then
        ns.DebugLog("!!! OnCastStart: Failed to calculate times, aborting")
        return
    end
    
    -- Check if castbar is already showing and this is a new cast
    if castbar:IsShown() then
        ns.DebugLog(string.format("!!! WARNING: Castbar already showing! Old cast: %s, New cast: %s", 
            tostring(castbar.currentSpell), tostring(text)))
    end
    
    -- Cancel any existing hide timer from previous failed cast
    if castbar.hideTimer then
        ns.DebugLog(">>> Cancelling previous hide timer")
        castbar.hideTimer:Cancel()
        castbar.hideTimer = nil
    end
    
    castbar.channeling = channeled
    castbar.notInterruptible = notInterruptible
    castbar.startTime = calcStartTime
    castbar.endTime = calcEndTime
    castbar.duration = calcDuration
    castbar.currentSpell = text  -- Track current spell name
    castbar.castStarted = GetTime()  -- Track when cast actually started
    
    ns.DebugLog(string.format(">>> Cast Details: duration=%.2f, startTime=%.2f, endTime=%.2f, currentTime=%.2f", 
        calcDuration, calcStartTime, calcEndTime, GetTime()))
    
    -- Set text
    castbar.text:SetText(text)
    
    -- Update dynamic width if enabled
    self:UpdateDynamicWidth(castbar, text)
    
    -- Set icon
    if castbar.icon then
        castbar.icon:SetTexture(texture)
    end
    
    -- Set color
    self:UpdateCastColor(castbar)
    
    -- Update safezone and latency for player
    if unit == "player" then
        if castbar.safezone then
            local width = castbar:GetWidth()
            local _, _, lagHome, lagWorld = GetNetStats()
            local safezoneWidth = (width / castbar.duration) * (lagHome / 1000)
            castbar.safezone:SetWidth(math.min(safezoneWidth, width))
        end
        
        if castbar.latency then
            local _, _, lagHome, lagWorld = GetNetStats()
            local avgLag = (lagHome + lagWorld) / 2
            castbar.latency:SetText(string.format("%.0fms", avgLag))
        end
    end
    
    -- Update interruptible state
    self:UpdateInterruptible(castbar, notInterruptible)
    
    ns.DebugLog(string.format(">>> Showing castbar for: %s", text))
    
    -- Stop any running fade out animation
    if castbar.fadeOut and castbar.fadeOut:IsPlaying() then
        castbar.fadeOut:Stop()
        castbar:SetAlpha(1)
    end
    
    castbar:Show()
end

-- Cast stop
function Castbar:OnCastStop(castbar, unit)
    ns.DebugLog(string.format(">>> OnCastStop: unit=%s, spell=%s, wasShown=%s", 
        tostring(unit), tostring(castbar.currentSpell), tostring(castbar:IsShown())))
    
    -- Cancel any hide timer
    if castbar.hideTimer then
        ns.DebugLog(">>> Cancelling hide timer in OnCastStop")
        castbar.hideTimer:Cancel()
        castbar.hideTimer = nil
    end
    
    castbar.currentSpell = nil
    
    -- Fade out instead of instant hide
    if castbar.fadeOut then
        castbar.fadeOut:Play()
    else
        castbar:Hide()
    end
end

-- Cast failed
function Castbar:OnCastFailed(castbar, unit)
    ns.DebugLog(string.format(">>> OnCastFailed: unit=%s, spell=%s", 
        tostring(unit), tostring(castbar.currentSpell)))
    
    -- Cancel any existing hide timer
    if castbar.hideTimer then
        castbar.hideTimer:Cancel()
        castbar.hideTimer = nil
    end
    
    if castbar.config.colors.failed then
        castbar:SetStatusBarColor(unpack(castbar.config.colors.failed))
        castbar.bg:SetColorTexture(castbar.config.colors.failed[1]*0.3, 
                                   castbar.config.colors.failed[2]*0.3, 
                                   castbar.config.colors.failed[3]*0.3, 0.5)
    end
    
    local failedSpell = castbar.currentSpell
    castbar.currentSpell = nil
    
    -- Create new timer and store reference
    castbar.hideTimer = C_Timer.NewTimer(1, function()
        -- Only hide if no new cast has started
        if not castbar.currentSpell or castbar.currentSpell == failedSpell then
            ns.DebugLog(">>> Hiding castbar after failed cast: " .. tostring(failedSpell))
            -- Fade out instead of instant hide
            if castbar.fadeOut then
                castbar.fadeOut:Play()
            else
                castbar:Hide()
            end
        else
            ns.DebugLog(">>> NOT hiding castbar - new cast active: " .. tostring(castbar.currentSpell))
        end
        castbar.hideTimer = nil
    end)
end

-- Cast interrupted
function Castbar:OnCastInterrupted(castbar, unit)
    self:OnCastFailed(castbar, unit)
end

-- Cast delayed
function Castbar:OnCastDelayed(castbar, unit)
    local name, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible
    
    if castbar.channeling then
        name, text, texture, startTime, endTime, isTradeSkill, notInterruptible = UnitChannelInfo(unit)
    else
        name, text, texture, startTime, endTime, isTradeSkill, castID, notInterruptible = UnitCastingInfo(unit)
    end
    
    if not name then 
        ns.DebugLog("!!! OnCastDelayed: No spell name returned")
        return 
    end
    
    local oldDuration = castbar.duration
    castbar.startTime = startTime / 1000
    castbar.endTime = endTime / 1000
    castbar.duration = castbar.endTime - castbar.startTime
    
    ns.DebugLog(string.format(">>> OnCastDelayed: spell=%s, oldDuration=%.2f, newDuration=%.2f, delay=%.2f", 
        tostring(text), oldDuration, castbar.duration, castbar.duration - oldDuration))
end

-- Update cast color
function Castbar:UpdateCastColor(castbar)
    local config = castbar.config
    local color
    
    -- Use class color for cast bar if classColorCast is enabled
    if config.classColorCast then
        local _, class = UnitClass(castbar.unit)
        if class then
            local classColor = RAID_CLASS_COLORS[class]
            if classColor then
                color = {classColor.r, classColor.g, classColor.b}
            end
        end
    end
    
    -- Otherwise use configured colors (channel or cast)
    if not color then
        color = castbar.channeling and config.colors.channel or config.colors.cast
    end
    
    castbar:SetStatusBarColor(unpack(color))
    castbar.bg:SetColorTexture(color[1]*0.3, color[2]*0.3, color[3]*0.3, 0.5)
end

-- Update interruptible state
function Castbar:UpdateInterruptible(castbar, notInterruptible)
    castbar.notInterruptible = notInterruptible
    
    if castbar.unit ~= "player" and notInterruptible then
        local color = castbar.config.colors.nonInterruptible
        UpdateBorderColor(castbar, unpack(color))
    else
        UpdateBorderColor(castbar, 1, 1, 1)
    end
end

-- OnUpdate handler
function Castbar:OnUpdate(castbar, elapsed)
    if not castbar:IsShown() then return end
    
    local currentTime = GetTime()
    local remaining
    local progress
    
    if castbar.channeling then
        remaining = castbar.endTime - currentTime
        progress = remaining
    else
        remaining = currentTime - castbar.startTime
        progress = remaining
    end
    
    -- Check if cast has completed
    local castCompleted = false
    if castbar.channeling then
        -- For channeling, cast is complete when remaining <= 0
        if remaining <= 0 then
            castCompleted = true
            ns.DebugLog(string.format("!!! OnUpdate: Channel completed! spell=%s, remaining=%.3f", 
                tostring(castbar.currentSpell), remaining))
        end
    else
        -- For regular casts, cast is complete when we've reached or exceeded endTime
        if currentTime >= castbar.endTime then
            castCompleted = true
            ns.DebugLog(string.format("!!! OnUpdate: Cast completed! spell=%s, currentTime=%.3f, endTime=%.3f, overshoot=%.3f", 
                tostring(castbar.currentSpell), currentTime, castbar.endTime, currentTime - castbar.endTime))
        end
    end
    
    -- Auto-hide when cast completes (if no STOP event was received)
    if castCompleted then
        -- Double-check with UnitCastingInfo/UnitChannelInfo
        local stillCasting = false
        if castbar.channeling then
            local name = UnitChannelInfo(castbar.unit)
            stillCasting = (name ~= nil)
        else
            local name = UnitCastingInfo(castbar.unit)
            stillCasting = (name ~= nil)
        end
        
        if stillCasting then
            ns.DebugLog(string.format("!!! OnUpdate: Cast appears complete but unit still casting: %s", tostring(castbar.currentSpell)))
        else
            ns.DebugLog(string.format(">>> OnUpdate: Auto-hiding castbar (cast completed without STOP event): %s", tostring(castbar.currentSpell)))
            castbar.currentSpell = nil
            -- Fade out instead of instant hide
            if castbar.fadeOut then
                castbar.fadeOut:Play()
            else
                castbar:Hide()
            end
            return
        end
    end
    
    if remaining < 0 then remaining = 0 end
    
    -- Update progress bar
    if castbar.channeling then
        castbar:SetValue(remaining)
        castbar:SetMinMaxValues(0, castbar.duration)
    else
        castbar:SetValue(remaining)
        castbar:SetMinMaxValues(0, castbar.duration)
    end
    
    -- Update time text
    if castbar.duration > 86400 then
        castbar.time:SetText(string.format("%.1f / %.1f", remaining/86400, castbar.duration/86400))
    else
        castbar.time:SetText(string.format("%.1f / %.1f", remaining, castbar.duration))
    end
    
    -- Periodic debug logging (every 0.5 seconds)
    if not castbar.lastDebugLog or (currentTime - castbar.lastDebugLog) >= 0.5 then
        ns.DebugLog(string.format(">>> OnUpdate: spell=%s, remaining=%.2f, duration=%.2f, progress=%.1f%%", 
            tostring(castbar.currentSpell), remaining, castbar.duration, (progress/castbar.duration)*100))
        castbar.lastDebugLog = currentTime
    end
end

-- Toggle anchors visibility
function Castbar:ToggleAnchors()
    if InCombatLockdown() then
        ns.Error(ns.L["COMBAT_LOCKDOWN"])
        return
    end
    
    for unit, anchor in pairs(self.anchors) do
        if anchor:IsShown() then
            anchor:Hide()
        else
            anchor:Show()
        end
    end
end

-- Initialize all castbars
function Castbar:Initialize()
    ns.Debug("Initializing castbars...")
    
    -- Hide Blizzard castbars
    self:HideBlizzardCastbars()
    
    -- Create castbars for all units
    for _, unit in ipairs({"player", "target", "focus", "pet"}) do
        self:Create(unit)
    end
    
    ns.Debug("Castbars initialized")
end

-- Hide Blizzard's default castbars
function Castbar:HideBlizzardCastbars()
    -- Hide player castbar
    if PlayerCastingBarFrame then
        PlayerCastingBarFrame:UnregisterAllEvents()
        PlayerCastingBarFrame:Hide()
        PlayerCastingBarFrame:SetAlpha(0)
    end
    
    -- Hide pet castbar
    if PetCastingBarFrame then
        PetCastingBarFrame:UnregisterAllEvents()
        PetCastingBarFrame:Hide()
        PetCastingBarFrame:SetAlpha(0)
    end
    
    ns.Debug("Blizzard castbars hidden")
end

-- Cleanup
function Castbar:Cleanup()
    for unit, frame in pairs(self.frames) do
        frame:UnregisterAllEvents()
        frame:Hide()
    end
    -- Also hide orphaned anchor frames so they don't float after Reload
    for unit, anchor in pairs(self.anchors) do
        anchor:Hide()
    end
end

-- Reload castbars (for settings changes)
function Castbar:Reload()
    self:Cleanup()
    self.frames = {}
    self.anchors = {}
    self:Initialize()
end

ns.Debug("Castbar.lua loaded")
