local _, ns = ...

local Castbar = {}
ns.Castbar = Castbar

Castbar.frames  = {}
Castbar.anchors = {}

local STATUSBAR_TEXTURE = "Interface\\AddOns\\nCastbar\\Media\\statusbarTexture"

local function CreateBorder(frame, size)
    frame:CreateBeautyBorder(size or 11)
    frame:SetBeautyBorderPadding(2.66)
end

-- ============================================================
-- Anchor
-- ============================================================
function Castbar:CreateAnchor(unit, label)
    local config = nCastbarDB.profile[unit]
    if not config then return end
    local anchor = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
    anchor:SetSize((config.width + config.height) * config.scale, config.height * config.scale)
    anchor:SetScale(1.193)
    anchor:SetPoint(unpack(config.position or {"CENTER"}))
    anchor:SetFrameStrata("HIGH")
    anchor:SetMovable(true); anchor:SetClampedToScreen(true); anchor:SetUserPlaced(true)
    anchor:SetBackdrop({ bgFile = "Interface\\MINIMAP\\TooltipBackdrop-Background" })
    CreateBorder(anchor, 11)
    anchor:EnableMouse(true); anchor:RegisterForDrag("LeftButton"); anchor:Hide()
    anchor.text = anchor:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    anchor.text:SetAllPoints(anchor)
    anchor.text:SetText(label .. " Castbar")
    anchor:SetScript("OnDragStart", function(self) self:StartMoving() end)
    anchor:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        local point, _, relPoint, x, y = self:GetPoint()
        config.position = {point, "UIParent", relPoint, x, y}
    end)
    self.anchors[unit] = anchor
    return anchor
end

-- ============================================================
-- Create
-- ============================================================
function Castbar:Create(unit)
    local config = nCastbarDB.profile[unit]
    if not config or not config.enabled then return end
    if not self.anchors[unit] then
        self:CreateAnchor(unit, unit:gsub("^%l", string.upper))
    end

    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)
    if self.anchors[unit] then
        castbar:SetPoint("CENTER", self.anchors[unit])
    elseif config.position then
        castbar:SetPoint(unpack(config.position))
    end

    castbar.bg = castbar:CreateTexture(nil, "BACKGROUND")
    castbar.bg:SetColorTexture(0, 0, 0, 0.5)
    castbar.bg:SetAllPoints(castbar)
    CreateBorder(castbar, 11)
    castbar.baseWidth = config.width

    -- notInterruptible overlay (immer vorhanden, Alpha 0/1)
    castbar.shieldOverlay = castbar:CreateTexture(nil, "ARTWORK")
    castbar.shieldOverlay:SetAllPoints(castbar)
    castbar.shieldOverlay:SetColorTexture(unpack(config.colors.nonInterruptible or {0.7, 0.7, 0.7}))
    castbar.shieldOverlay:SetAlpha(0)
    castbar.shieldOverlay:Show()

    -- Font
    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

    castbar.time = castbar:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    castbar.time:SetJustifyH("RIGHT")

    -- Icon
    if config.icon and config.icon.show then
        castbar.iconFrame = CreateFrame("Frame", nil, castbar)
        castbar.iconFrame:SetSize(config.height, config.height)
        castbar.icon = castbar.iconFrame:CreateTexture(nil, "ARTWORK")
        castbar.icon:SetAllPoints(castbar.iconFrame)
        castbar.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
        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

    self:UpdateTextPosition(castbar, config)

    if unit == "player" then
        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
        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

    -- Fade animation
    castbar.fadeOut = castbar:CreateAnimationGroup()
    local anim = castbar.fadeOut:CreateAnimation("Alpha")
    anim:SetFromAlpha(1); anim:SetToAlpha(0); anim:SetDuration(0.3); anim:SetSmoothing("IN")
    castbar.fadeOut:SetScript("OnFinished", function(self)
        local f = self:GetParent(); f:Hide(); f:SetAlpha(1)
    end)

    castbar.unit    = unit
    castbar.config  = config
    castbar.casting = false
    castbar:Hide()
    self.frames[unit] = castbar
    self:RegisterEvents(castbar)
    return castbar
end

-- ============================================================
-- Text position
-- ============================================================
function Castbar:UpdateTextPosition(castbar, config)
    castbar.text:ClearAllPoints(); castbar.time:ClearAllPoints()
    local pos        = config.textPosition or "inside"
    local showTime   = config.showTime ~= false
    local iconInside = castbar.iconFrame and config.icon and config.icon.show and not config.icon.positionOutside
    if pos == "above" then
        castbar.text:SetPoint("BOTTOM", castbar, "TOP", 0, 2)
        castbar.text:SetWidth(config.width); castbar.text:SetJustifyH("CENTER")
        if showTime then castbar.time:SetPoint("LEFT", castbar.text, "RIGHT", 4, 0); castbar.time:Show()
        else castbar.time:Hide() end
    elseif pos == "below" then
        castbar.text:SetPoint("TOP", castbar, "BOTTOM", 0, -2)
        castbar.text:SetWidth(config.width); castbar.text:SetJustifyH("CENTER")
        if showTime then castbar.time:SetPoint("LEFT", castbar.text, "RIGHT", 4, 0); castbar.time:Show()
        else castbar.time:Hide() end
    else
        if iconInside 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 iconInside 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 iconInside 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

-- ============================================================
-- hide_castbar (lokal, wie im Referenz-Addon)
-- ============================================================
local function hide_castbar(castbar)
    castbar.casting      = false
    castbar.channeling   = false
    castbar.failstart    = nil
    castbar.duration_obj = nil
    castbar.shieldOverlay:SetAlpha(0)
    if castbar.fadeOut then
        castbar.fadeOut:Play()
    else
        castbar:Hide()
    end
end

-- ============================================================
-- check_casting: flache lokale Funktion wie im Referenz-Addon.
-- Alle tainted Werte gehen NUR in WoW-API-Funktionen.
-- Lua-Logik (if/not/==) nur auf eigene nicht-tainted Variablen.
-- ============================================================
local function check_casting(castbar, event)
    local unit   = castbar.unit
    local config = castbar.config

    if not UnitExists(unit) then
        hide_castbar(castbar); return
    end

    if event == "UNIT_SPELLCAST_INTERRUPTED" then
        castbar:SetMinMaxValues(0, 100)
        castbar:SetValue(100)
        castbar:SetStatusBarColor(unpack(config.colors.failed))
        castbar.bg:SetColorTexture(
            config.colors.failed[1]*0.3, config.colors.failed[2]*0.3,
            config.colors.failed[3]*0.3, 0.5)
        castbar.time:SetText("Interrupted")
        castbar.text:SetText("")
        castbar.failstart    = GetTime()
        castbar.casting      = false
        castbar.duration_obj = nil
        castbar.shieldOverlay:SetAlpha(0)
        if castbar.fadeOut and castbar.fadeOut:IsPlaying() then
            castbar.fadeOut:Stop(); castbar:SetAlpha(1)
        end
        castbar:Show()
        return
    end

    if event == "UNIT_SPELLCAST_FAILED" then
        castbar:SetStatusBarColor(unpack(config.colors.failed))
        castbar.failstart    = GetTime()
        castbar.casting      = false
        castbar.duration_obj = nil
        return
    end

    -- Wie Referenz-Addon: erst UnitCastingInfo, bei nil UnitChannelInfo
    -- bchannel ist UNSERE Variable - nicht tainted
    local bchannel = false
    local name, _, texture, start, endTime, isTradeSkill, castID, notInterruptible, spellid =
        UnitCastingInfo(unit)

    if not name then
        name, _, texture, start, endTime, isTradeSkill, notInterruptible, spellid =
            UnitChannelInfo(unit)
        bchannel = true
    end

    if name then
        -- Duration-Objekt: WoW-Objekt, kein tainted scalar
        local duration_obj = bchannel and UnitChannelDuration(unit) or UnitCastingDuration(unit)
        castbar.duration_obj = duration_obj

        -- Tainted values NUR an WoW-Funktionen:
        if castbar.icon then castbar.icon:SetTexture(texture) end
        castbar:SetReverseFill(bchannel)        -- bchannel = unsere Variable
        castbar:SetMinMaxValues(start, endTime) -- start/endTime direkt, keine Arithmetik

        castbar.failstart  = nil
        castbar.casting    = true
        castbar.channeling = bchannel           -- bchannel = unsere Variable

        castbar.text:SetText(name)              -- name tainted, aber SetText() kann das

        -- Farbe anhand UnitClass (nicht tainted)
        local color
        if config.classColorCast then
            local _, class = UnitClass(unit)
            if class and RAID_CLASS_COLORS[class] then
                local c = RAID_CLASS_COLORS[class]
                color = {c.r, c.g, c.b}
            end
        end
        if not color then
            color = bchannel 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)

        -- notInterruptible: tainted boolean.
        -- C_CurveUtil.EvaluateColorValueFromBoolean kann tainted booleans verarbeiten.
        -- Resultat ist ein normaler Lua-number (0 oder 1).
        local shieldAlpha = C_CurveUtil.EvaluateColorValueFromBoolean(notInterruptible, 1, 0)
        castbar.shieldOverlay:SetAlpha(shieldAlpha)

        -- Safezone / Latenz fuer Player
        if unit == "player" and duration_obj then
            if castbar.safezone then
                local _, _, lagHome = GetNetStats()
                local totalDur = duration_obj:GetTotalDuration(0)
                if totalDur and totalDur > 0 then
                    local w = castbar:GetWidth()
                    castbar.safezone:SetWidth(math.min((w / totalDur) * (lagHome / 1000), w))
                end
            end
            if castbar.latency then
                local _, _, lagHome, lagWorld = GetNetStats()
                castbar.latency:SetText(string.format("%.0fms", (lagHome + lagWorld) / 2))
            end
        end

        if castbar.fadeOut and castbar.fadeOut:IsPlaying() then
            castbar.fadeOut:Stop(); castbar:SetAlpha(1)
        end
        castbar:Show()
    else
        if not castbar.failstart then
            hide_castbar(castbar)
        end
    end
end

-- ============================================================
-- on_unit_event: flache lokale Funktion (wie Referenz-Addon)
-- ============================================================
local function on_unit_event(castbar, event, ...)
    if event == "UNIT_SPELLCAST_CHANNEL_STOP" then
        local interruptedBy = select(4, ...)
        if interruptedBy ~= nil then
            event = "UNIT_SPELLCAST_INTERRUPTED"
        end
    end
    check_casting(castbar, event)
end

-- ============================================================
-- RegisterEvents
-- ============================================================
function Castbar:RegisterEvents(castbar)
    local unit = castbar.unit
    castbar:RegisterUnitEvent("UNIT_SPELLCAST_START",             unit)
    castbar:RegisterUnitEvent("UNIT_SPELLCAST_STOP",              unit)
    castbar:RegisterUnitEvent("UNIT_SPELLCAST_FAILED",            unit)
    castbar:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED",       unit)
    castbar:RegisterUnitEvent("UNIT_SPELLCAST_DELAYED",           unit)
    castbar:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START",     unit)
    castbar:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP",      unit)
    castbar:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_UPDATE",    unit)
    castbar:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTIBLE",     unit)
    castbar:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", unit)

    -- Genau wie Referenz-Addon: castbar explizit in Closure
    castbar:SetScript("OnEvent", function(_, event, ...)
        on_unit_event(castbar, event, ...)
    end)
    castbar:SetScript("OnUpdate", function(_, elapsed)
        Castbar:OnUpdate(castbar, elapsed)
    end)
end

-- ============================================================
-- OnUpdate
-- ============================================================
function Castbar:OnUpdate(castbar, elapsed)
    if not castbar:IsShown() then return end
    if castbar.failstart then
        if GetTime() - castbar.failstart > 1 then hide_castbar(castbar) end
        return
    end
    if not castbar.casting then return end
    if castbar.duration_obj then
        local remaining = castbar.duration_obj:GetRemainingDuration(0)
        local total     = castbar.duration_obj:GetTotalDuration(0)
        if remaining and total then
            castbar.time:SetText(string.format("%.1f / %.1f", remaining, total))
        end
    end
    -- GetTime()*1000 = Millisekunden wie start/endTime von UnitCastingInfo
    castbar:SetValue(GetTime() * 1000, Enum.StatusBarInterpolation.ExponentialEaseOut)
end

-- ============================================================
-- OnTargetChanged
-- ============================================================
function Castbar:OnTargetChanged(unit)
    local castbar = self.frames[unit]
    if not castbar then return end
    castbar.casting      = false
    castbar.failstart    = nil
    castbar.duration_obj = nil
    castbar:Hide(); castbar:SetAlpha(1)
    if castbar.fadeOut and castbar.fadeOut:IsPlaying() then castbar.fadeOut:Stop() end
    if not UnitExists(unit) then return end
    check_casting(castbar, "NOTHING")
end

-- ============================================================
-- ToggleAnchors
-- ============================================================
function Castbar:ToggleAnchors()
    if InCombatLockdown() then return end
    for unit, anchor in pairs(self.anchors) do
        if anchor:IsShown() then anchor:Hide() else anchor:Show() end
    end
end

-- ============================================================
-- Initialize
-- ============================================================
function Castbar:Initialize()
    ns.Debug("Initializing castbars...")
    self:HideBlizzardCastbars()
    if not self.mainFrame then
        self.mainFrame = CreateFrame("Frame")
        self.mainFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
        self.mainFrame:RegisterEvent("PLAYER_FOCUS_CHANGED")
        self.mainFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
        self.mainFrame:SetScript("OnEvent", function(_, event)
            if event == "PLAYER_TARGET_CHANGED" or event == "PLAYER_ENTERING_WORLD" then
                Castbar:OnTargetChanged("target")
            end
            if event == "PLAYER_FOCUS_CHANGED" or event == "PLAYER_ENTERING_WORLD" then
                Castbar:OnTargetChanged("focus")
            end
        end)
    end
    C_Timer.After(0.5, function()
        for _, unit in ipairs({"player", "target", "focus", "pet"}) do
            Castbar:Create(unit)
        end
        Castbar:OnTargetChanged("target")
        Castbar:OnTargetChanged("focus")
        ns.Debug("Castbars initialized")
    end)
end

function Castbar:HideBlizzardCastbars()
    if PlayerCastingBarFrame then
        PlayerCastingBarFrame:UnregisterAllEvents()
        PlayerCastingBarFrame:Hide()
        PlayerCastingBarFrame:SetAlpha(0)
    end
    if PetCastingBarFrame then
        PetCastingBarFrame:UnregisterAllEvents()
        PetCastingBarFrame:Hide()
        PetCastingBarFrame:SetAlpha(0)
    end
end

function Castbar:Cleanup()
    for unit, frame in pairs(self.frames) do
        frame:UnregisterAllEvents()
        frame:SetScript("OnEvent",  nil)
        frame:SetScript("OnUpdate", nil)
        frame:Hide()
    end
    for unit, anchor in pairs(self.anchors) do anchor:Hide() end
end

function Castbar:Reload()
    self:Cleanup()
    self.frames  = {}
    self.anchors = {}
    self:Initialize()
end

ns.Debug("Castbar.lua loaded")
