-- Castbars/MSUF_CastbarPreviews.lua
-- Zero combat path â€” only used in MSUF Edit Mode.

local function Tr(text)
    if type(text) ~= "string" then return text end
    local ns = _G.MSUF_NS
    if type(ns) == "table" and type(ns.Translate) == "function" then
        return ns.Translate(text)
    end
    local locale = (type(ns) == "table" and ns.L) or _G.MSUF_L
    if type(locale) == "table" then
        local translated = rawget(locale, text)
        if translated ~= nil then return translated end
    end
    return text
end

local L_PLAYER_CASTBAR_PREVIEW
local L_TARGET_CASTBAR_PREVIEW
local L_FOCUS_CASTBAR_PREVIEW
local L_BOSS_CASTBAR_PREVIEW
local L_TEST_CAST

local function RefreshLocaleText()
    L_PLAYER_CASTBAR_PREVIEW = Tr("Player castbar preview")
    L_TARGET_CASTBAR_PREVIEW = Tr("Target castbar preview")
    L_FOCUS_CASTBAR_PREVIEW = Tr("Focus castbar preview")
    L_BOSS_CASTBAR_PREVIEW = Tr("Boss castbar preview")
    L_TEST_CAST = Tr("Test Cast")
end

RefreshLocaleText()
do
    local ns = _G.MSUF_NS
    if type(ns) == "table" and type(ns.RegisterLocaleCallback) == "function" then
        ns.RegisterLocaleCallback("MSUF_CastbarPreviews", RefreshLocaleText)
    end
end

local _EnsureDBLazy = _G.MSUF_EnsureDBLazy or function()
    if not MSUF_DB and type(EnsureDB) == "function" then EnsureDB() end
end

-- NOTE: Core stores the unitframe table on _G.MSUF_UnitFrames (not a global "UnitFrames").
-- The LoD module must anchor Edit Mode previews against that table, otherwise previews
-- can appear missing (unanchored/off-screen).
local UnitFrames = _G.MSUF_UnitFrames

local function MSUF_CastbarTestCombatLocked()
    return _G.MSUF_InCombat == true or ((_G.InCombatLockdown and _G.InCombatLockdown()) and true or false)
end

local function MSUF_FormatPreviewCastTime(frame, current, total)
    local mode = "CURRENT"
    local unit = frame and frame.unit
    if type(_G.MSUF_GetCastbarTimeFormat) == "function" then
        mode = _G.MSUF_GetCastbarTimeFormat(unit)
    end
    if frame then frame._msufCastTimeFormat = mode end
    if type(_G.MSUF_FormatCastbarTimeText) == "function" then
        return _G.MSUF_FormatCastbarTimeText(mode, current, total)
    end
    return string.format("%.1f", tonumber(current) or 0)
end

local function MSUF_HideBlizzardPlayerCastbar()
    EnsureDB()
    local frames = {}

    if PlayerCastingBarFrame then
        table.insert(frames, PlayerCastingBarFrame)
    end

    if CastingBarFrame and CastingBarFrame ~= PlayerCastingBarFrame then
        table.insert(frames, CastingBarFrame)
    end

    if #frames == 0 then
        return
    end

    for _, frame in ipairs(frames) do
        if frame and not frame.MSUF_HideHooked then
            frame.MSUF_HideHooked = true

            hooksecurefunc(frame, "Show", function(self)
                -- As long as MSUF is running, never allow the Blizzard player castbar(s) to show.
                -- This is intentionally NOT tied to MSUF_DB.general.enablePlayerCastbar.
                -- If the user disables the MSUF player castbar, they should not silently fall back
                -- to Blizzard (which can cause edge-case "0 interaction" popups).
                self:Hide()
            end)
        end

        -- Always hide while MSUF is loaded.
        frame:Hide()
    end
end

function _G.MSUF_SetPlayerCastbarTestMode(active, keepSetting)
    EnsureDB()
    if active and MSUF_CastbarTestCombatLocked() then active = false end
    MSUF_DB.general = MSUF_DB.general or {}
    local g = MSUF_DB.general

    local want
    -- keepSetting=true means: do not persist and do not consult the stored setting.
    -- We use this for the "auto test cast while popup is open" behaviour.
    if keepSetting then
        want = active and true or false
    else
        g.playerCastbarTestMode = active and true or false
        want = g.playerCastbarTestMode and true or false
    end

    if not MSUF_UnitEditModeActive then
        want = false
    end

    if type(MSUF_InitSafePlayerCastbar) == "function" then
        MSUF_InitSafePlayerCastbar()
    end

    -- In MSUF Edit Mode the user drags/edits the *preview* castbar.
    -- For best UX, run the dummy-cast animation on the preview (if available)
    -- so you can see changes live where you're editing.
    local fReal = _G.MSUF_PlayerCastbar
    local fPrev = _G.MSUF_PlayerCastbarPreview
    local usePreview = (want and MSUF_UnitEditModeActive and g.castbarPlayerPreviewEnabled and fPrev and fPrev.statusBar)

    local function StopTest(frame, isPreview)
        if not frame or not frame.MSUF_testMode then
            return
        end
        frame.MSUF_testMode = nil
        frame.MSUF_testStart = nil
        frame.MSUF_testDuration = nil
        if frame.statusBar then
            frame.statusBar._msufTestMinMax = nil
            if isPreview then
                frame.statusBar:SetMinMaxValues(0, 1)
                frame.statusBar:SetValue(0.5)
            end
        end
        frame:SetScript("OnUpdate", nil)

        -- Reset optional visual effects when leaving dummy-cast mode.
        if type(_G.MSUF_ResetCastbarGlowFade) == "function" then
            pcall(_G.MSUF_ResetCastbarGlowFade, frame)
        end
        if frame.latencyBar and frame.latencyBar.Hide then
            frame.latencyBar:Hide()
        end

        if isPreview then
            if frame.castText then
                MSUF_SetTextIfChanged(frame.castText, L_PLAYER_CASTBAR_PREVIEW)
            end
            if frame.timeText then
                MSUF_SetTextIfChanged(frame.timeText, "")
                if frame.MSUF_testCreatedTimeText and frame.timeText.Hide then
                    frame.timeText:Hide()
                end
            end
            frame.MSUF_testCreatedTimeText = nil

            if g.castbarPlayerPreviewEnabled then
                frame:Show()
            else
                frame:Hide()
            end
        else
            -- Let normal castbar logic take over; hide if no real cast is active.
            local hasCast = UnitCastingInfo("player") or UnitChannelInfo("player")
            if not hasCast and type(UnitHasVehicleUI) == "function" and UnitHasVehicleUI("player") and type(UnitExists) == "function" and UnitExists("vehicle") then
                hasCast = UnitCastingInfo("vehicle") or UnitChannelInfo("vehicle")
            end
            if not hasCast then
                if frame.timeText then
                    MSUF_SetTextIfChanged(frame.timeText, "")
                end
                frame:Hide()
            end
        end
    end

    -- IMPORTANT: when disabling, stop BOTH preview + real bars.
    -- Otherwise the preview can keep casting if we pick the real bar as "f".
    if not want then
        StopTest(fPrev, true)
        StopTest(fReal, false)
        return
    end

    -- If we're switching to preview, ensure the real bar isn't left in test mode (and vice versa).
    if usePreview then
        StopTest(fReal, false)
    else
        StopTest(fPrev, true)
    end

    local f = usePreview and fPrev or fReal
    if not f or not f.statusBar then
        return
    end

    -- If we're animating the preview, keep the real bar hidden (unless a real cast is happening).
    if usePreview and fReal and fReal ~= fPrev then
        -- Don't fight the normal castbar driver: only hide if no real cast is active.
        local hasCast = UnitCastingInfo("player") or UnitChannelInfo("player")
        if not hasCast and type(UnitHasVehicleUI) == "function" and UnitHasVehicleUI("player") and type(UnitExists) == "function" and UnitExists("vehicle") then
            hasCast = UnitCastingInfo("vehicle") or UnitChannelInfo("vehicle")
        end
        if not hasCast then
            fReal:SetScript("OnUpdate", nil)
            if fReal.timeText then
                MSUF_SetTextIfChanged(fReal.timeText, "")
            end
            fReal:Hide()
        end
    end

    -- Ensure the preview has a timeText so the dummy cast shows duration.
    -- (We mark it so we can hide it again when test mode is disabled.)
    if usePreview and (not f.timeText) and f.statusBar and f.statusBar.CreateFontString then
        local fontPath, fontSize, flags = GameFontHighlight:GetFont()
        local tt = f.statusBar:CreateFontString(nil, "OVERLAY")
        tt:SetFont(fontPath, fontSize, flags)
        tt:SetJustifyH("RIGHT")
        tt:SetPoint("RIGHT", f.statusBar, "RIGHT", -2, 0)
        tt:SetText("")
        f.timeText = tt
        f.MSUF_testCreatedTimeText = true
    end

    -- (disable path handled above)

    -- Enable runtime test mode (dummy casting loop).
    f.MSUF_testMode = true
    if f.hideTimer and f.hideTimer.Cancel then
        f.hideTimer:Cancel()
    end
    f.hideTimer = nil
    f.interruptFeedbackEndTime = nil

    if f.castText then
        MSUF_SetTextIfChanged(f.castText, L_TEST_CAST)
    end
    if f.icon then
        f.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
    end

    local dur = 4.0
    f.MSUF_testStart = (GetTimePreciseSec and GetTimePreciseSec()) or GetTime()
    f.MSUF_testDuration = dur

    -- Respect the per-unit cast time toggle while still running the dummy cast.
    local showTime = (g and g.showPlayerCastTime ~= false)
    if f.timeText then
        if f.timeText.SetShown then
            f.timeText:SetShown(showTime)
        else
            if showTime and f.timeText.Show then
                f.timeText:Show()
            end
            if (not showTime) and f.timeText.Hide then
                f.timeText:Hide()
            end
        end
        if not showTime then
            MSUF_SetTextIfChanged(f.timeText, "")
        end
    end

    if f.statusBar then
        f.statusBar._msufTestMinMax = nil
    end

    f:Show()

    -- Apply current visual settings/anchors so edits update live.
    if type(MSUF_ReanchorPlayerCastBar) == "function" then
        MSUF_ReanchorPlayerCastBar()
    end
    if type(MSUF_UpdateCastbarVisuals) == "function" then
        MSUF_UpdateCastbarVisuals()
    end

    f:SetScript("OnUpdate", function(self, elapsed)
        if not self.MSUF_testMode or not self.statusBar then
            return
        end
        if MSUF_CastbarTestCombatLocked() then
            self.MSUF_testMode = nil
            self:SetScript("OnUpdate", nil)
            return
        end
        local now = (GetTimePreciseSec and GetTimePreciseSec()) or GetTime()
        local d = self.MSUF_testDuration or 4.0
        if d <= 0 then d = 4.0 end
        local t = now - (self.MSUF_testStart or now)
        if t < 0 then t = 0 end
        local p = t % d
        if not self.statusBar._msufTestMinMax then
            self.statusBar:SetMinMaxValues(0, d)
            self.statusBar._msufTestMinMax = true
        end
        self.statusBar:SetValue(p)

        -- Respect the user's "Show cast time" toggle while in test mode.
        local showTime = (g and g.showPlayerCastTime ~= false)
        if self.timeText then
            if self.timeText.SetShown then
                self.timeText:SetShown(showTime)
            elseif not showTime and self.timeText.Hide then
                self.timeText:Hide()
            elseif showTime and self.timeText.Show then
                self.timeText:Show()
            end
            local remain = d - p
            if remain < 0 then remain = 0 end
            if showTime then
                MSUF_SetTextIfChanged(self.timeText, MSUF_FormatPreviewCastTime(self, remain, d))
            else
                MSUF_SetTextIfChanged(self.timeText, "")
            end
        end

        -- Edit Mode visuals: latency indicator + glow effect (if enabled in options)
        if self.latencyBar then
            MSUF_PlayerCastbar_UpdateLatencyZone(self, false, d)
        end
        if type(_G.MSUF_ApplyCastbarGlowFade) == "function" then
            _G.MSUF_ApplyCastbarGlowFade(self, d - p, d)
        end
    end)
end

-- =====================================================================
-- Cluster B+E: Shared parametric TestMode for simple castbar previews.
-- Target and Focus are structurally identical; only config keys differ.
-- cfg = {
--   dbKey       = "targetCastbarTestMode",
--   flagKey     = "MSUF_targetTestMode",
--   previewName = "MSUF_TargetCastbarPreview",
--   reanchorFn  = "MSUF_ReanchorTargetCastBar",
--   showTimeKey = "showTargetCastTime",
--   label       = "Target castbar preview",
-- }
-- =====================================================================

-- Cluster E: Shared OnUpdate for simple preview dummy casts (Target, Focus).
local function _MSUF_SimplePreview_OnUpdate(self)
    if not self or not self._msufTestActive then return end
    if MSUF_CastbarTestCombatLocked() then
        self._msufTestActive = nil
        self:SetScript("OnUpdate", nil)
        return
    end

    local now = GetTime()
    local elapsed = now - (self.MSUF_testStart or now)
    local dur = self.MSUF_testDur or 4.0
    if dur <= 0 then dur = 4.0 end

    local phase = elapsed % dur
    local remaining = dur - phase

    if self.statusBar and self.statusBar.SetValue then
        self.statusBar:SetValue(phase)
    end

    if self.timeText and self.timeText.SetText then
        local showTime = self._msufTestShowTime
        if showTime then
            self.timeText:SetText(MSUF_FormatPreviewCastTime(self, remaining, dur))
            self.timeText:SetAlpha(1)
        else
            self.timeText:SetText("")
            self.timeText:SetAlpha(0)
        end
    end

    if type(_G.MSUF_ApplyCastbarGlowFade) == "function" then
        _G.MSUF_ApplyCastbarGlowFade(self, remaining, dur)
    end
end

local function _MSUF_SetSimpleCastbarTestMode(cfg, active, keepSetting)
    EnsureDB()
    if active and MSUF_CastbarTestCombatLocked() then active = false end
    MSUF_DB.general = MSUF_DB.general or {}
    local g = MSUF_DB.general

    local want
    if keepSetting then
        want = active and true or false
    else
        g[cfg.dbKey] = active and true or false
        want = g[cfg.dbKey] and true or false
    end

    if not MSUF_UnitEditModeActive then
        want = false
    end

    local fPrev = _G[cfg.previewName]
    local f = (fPrev and fPrev.statusBar) and fPrev or nil
    if not f or not f.statusBar then return end

    -- Ensure time text exists on the preview.
    if not f.timeText and f.statusBar and f.statusBar.CreateFontString then
        f.timeText = f.statusBar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        f.timeText:SetPoint("RIGHT", f.statusBar, "RIGHT", -4, 0)
        f.timeText:SetJustifyH("RIGHT")
    end

    if not want then
        if f._msufTestActive then
            f._msufTestActive = nil
            f._msufTestShowTime = nil
            f[cfg.flagKey] = nil
            f:SetScript("OnUpdate", nil)
            if f.statusBar and f.statusBar.SetMinMaxValues then
                f.statusBar:SetMinMaxValues(0, 1)
                f.statusBar:SetValue(0.5)
            end
            if f.castText and f.castText.SetText then
                f.castText:SetText(cfg.label)
            end
            if f.timeText and f.timeText.SetText then
                f.timeText:SetText("")
            end
            if type(_G.MSUF_ResetCastbarGlowFade) == "function" then
                _G.MSUF_ResetCastbarGlowFade(f)
            end
        end
        return
    end

    -- Start/refresh the dummy cast.
    f._msufTestActive = true
    f[cfg.flagKey] = true

    local reanchorFn = _G[cfg.reanchorFn]
    if type(reanchorFn) == "function" then reanchorFn() end
    if MSUF_UpdateCastbarVisuals then MSUF_UpdateCastbarVisuals() end

    if f.castText and f.castText.SetText then
        f.castText:SetText(L_TEST_CAST)
        f.castText:Show()
        f.castText:SetAlpha(1)
    end

    if f.icon and f.icon.SetTexture then
        f.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
        if f.icon.Show then f.icon:Show() end
    end

    local showTime = (g[cfg.showTimeKey] ~= false)
    f._msufTestShowTime = showTime
    if f.timeText then
        if showTime then
            f.timeText:Show()
            f.timeText:SetAlpha(1)
        else
            f.timeText:SetText("")
            f.timeText:Show()
            f.timeText:SetAlpha(0)
        end
    end

    local duration = 4.0
    f.MSUF_testStart = GetTime()
    f.MSUF_testDur = duration
    if f.statusBar and f.statusBar.SetMinMaxValues then
        f.statusBar:SetMinMaxValues(0, duration)
    end

    f:SetScript("OnUpdate", _MSUF_SimplePreview_OnUpdate)
end

-- Target castbar test mode (thin wrapper).
function _G.MSUF_SetTargetCastbarTestMode(active, keepSetting)
    _MSUF_SetSimpleCastbarTestMode({
        dbKey       = "targetCastbarTestMode",
        flagKey     = "MSUF_targetTestMode",
        previewName = "MSUF_TargetCastbarPreview",
        reanchorFn  = "MSUF_ReanchorTargetCastBar",
        showTimeKey = "showTargetCastTime",
        label       = L_TARGET_CASTBAR_PREVIEW,
    }, active, keepSetting)
end

-- Focus castbar test mode (thin wrapper).
function _G.MSUF_SetFocusCastbarTestMode(active, keepSetting)
    _MSUF_SetSimpleCastbarTestMode({
        dbKey       = "focusCastbarTestMode",
        flagKey     = "MSUF_focusTestMode",
        previewName = "MSUF_FocusCastbarPreview",
        reanchorFn  = "MSUF_ReanchorFocusCastBar",
        showTimeKey = "showFocusCastTime",
        label       = L_FOCUS_CASTBAR_PREVIEW,
    }, active, keepSetting)
end

-- Boss castbar: looping dummy cast on the BOSS castbar preview while the boss castbar popup is open.
-- We do NOT override bar colors here; MSUF_UpdateBossCastbarPreview applies the user's configured colors.
-- Cluster E: Named OnUpdate for Boss preview dummy casts (avoids per-frame closure allocation).
local function _MSUF_BossPreview_OnUpdate(self)
    if not self or not self.MSUF_bossTestMode then return end
    if MSUF_CastbarTestCombatLocked() then
        self.MSUF_bossTestMode = nil
        self:SetScript("OnUpdate", nil)
        return
    end

    local now = GetTime()
    local elapsed = now - (self.MSUF_testStart or now)
    local dur = self.MSUF_testDur or 4.0
    if dur <= 0 then dur = 4.0 end

    local phase = elapsed % dur
    local remaining = dur - phase

    -- Keep the fill texture visible while the dummy cast runs (boss preview hides it by default).
    if self.statusBar and self.statusBar.GetStatusBarTexture then
        local t = self.statusBar:GetStatusBarTexture()
        if t and t.SetAlpha then
            local a = 1
            if t.GetAlpha then a = t:GetAlpha() or 0 end
            if a < 0.9 then t:SetAlpha(1) end
        end
    end

    if self.statusBar and self.statusBar.SetValue then
        self.statusBar:SetValue(phase)
    end

    -- Keep label stable even if other refreshes happen while editing.
    if self.castText and self.castText.GetText and self.castText.SetText then
        local testLabel = L_TEST_CAST
        if self.castText:GetText() ~= testLabel then
            self.castText:SetText(testLabel)
        end
    end

    if self.timeText and self.timeText.SetText then
        if self._msufTestShowTime then
            self.timeText:SetText(MSUF_FormatPreviewCastTime(self, remaining, dur))
            self.timeText:SetAlpha(1)
        else
            self.timeText:SetText("")
            self.timeText:SetAlpha(0)
        end
    end

    if type(_G.MSUF_ApplyCastbarGlowFade) == "function" then
        _G.MSUF_ApplyCastbarGlowFade(self, remaining, dur)
    end
end

function _G.MSUF_SetBossCastbarTestMode(active, keepSetting)
    EnsureDB()
    if active and MSUF_CastbarTestCombatLocked() then active = false end
    MSUF_DB.general = MSUF_DB.general or {}
    local g = MSUF_DB.general

    local want
    if keepSetting then
        want = active and true or false
    else
        g.bossCastbarTestMode = active and true or false
        want = g.bossCastbarTestMode and true or false
    end

    -- Only while MSUF Edit Mode is active.
    if not MSUF_UnitEditModeActive then
        want = false
    end

    -- Make sure previews exist/are positioned before we try to drive a dummy cast.
    if not (_G.MSUF_InCombat == true or (InCombatLockdown and InCombatLockdown()))
        and type(_G.MSUF_UpdateBossCastbarPreview) == "function"
    then
        _G.MSUF_UpdateBossCastbarPreview()
    end

    local function IterateBossPreviews(fn)
        local f1 = _G.MSUF_BossCastbarPreview
        if f1 then fn(f1) end

        local n = tonumber(_G.MAX_BOSS_FRAMES) or 5
        if n < 1 or n > 12 then n = 5 end
        for i = 2, n do
            local f = _G["MSUF_BossCastbarPreview" .. i]
            if f then fn(f) end
        end
    end

    local found = false
    IterateBossPreviews(function(f)
        if f and f.statusBar then found = true end
    end)
    if not found then
        return
    end

    if not want then
        IterateBossPreviews(function(f)
            if not f or not f.statusBar then return end
            if f.MSUF_bossTestMode then
                f.MSUF_bossTestMode = nil
                f:SetScript("OnUpdate", nil)

                if f.statusBar.SetMinMaxValues then
                    f.statusBar:SetMinMaxValues(0, 1)
                    f.statusBar:SetValue(0.5)
                end

                -- Restore the boss preview's default (no-fill) look when leaving test mode.
                if f.statusBar.GetStatusBarTexture then
                    local tex = f.statusBar:GetStatusBarTexture()
                    if tex and tex.SetAlpha then
                        tex:SetAlpha(0)
                    end
                    f.statusBar.MSUF_hideFillTexture = true
                end

                if f.castText and f.castText.SetText then
                    f.castText:SetText(L_BOSS_CASTBAR_PREVIEW)
                end
                if f.timeText and f.timeText.SetText then
                    f.timeText:SetText("")
                end
	            if type(_G.MSUF_ResetCastbarGlowFade) == "function" then
	                _G.MSUF_ResetCastbarGlowFade(f)
	            end
            end
        end)
        return
    end

    -- Start/refresh the dummy cast for ALL boss previews.
    local duration = 4.0
    local startTime = GetTime()

    IterateBossPreviews(function(f)
        if not f or not f.statusBar then return end

        -- Ensure time text exists on the preview.
        if not f.timeText and f.statusBar and f.statusBar.CreateFontString then
            f.timeText = f.statusBar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            f.timeText:SetPoint("RIGHT", f.statusBar, "RIGHT", -4, 0)
            f.timeText:SetJustifyH("RIGHT")
        end

        f.MSUF_bossTestMode = true
        f.MSUF_testStart = startTime
        f.MSUF_testDur = duration

        -- Boss preview edit-mode setup hides the fill texture by default.
        -- For the dummy test cast we want a visible filling bar.
        if f.statusBar.GetStatusBarTexture then
            local tex = f.statusBar:GetStatusBarTexture()
            if tex and tex.SetAlpha then
                tex:SetAlpha(1)
            end
            f.statusBar.MSUF_hideFillTexture = nil
        end

        if f.castText and f.castText.SetText then
        f.castText:SetText(L_TEST_CAST)
        end

        -- IMPORTANT: Do NOT force-show the icon in test mode.
        -- Icon visibility must be controlled by the normal preview layout (Show Icon toggle).
        -- We only ensure a stable, non-secret texture is present when the icon is shown.
        if f.icon and f.icon.SetTexture then
            f.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
        end

        local showTime = (g.showBossCastTime ~= false)
        f._msufTestShowTime = showTime
        if f.timeText then
            if showTime then
                if f.timeText.Show then f.timeText:Show() end
                f.timeText:SetAlpha(1)
            else
                f.timeText:SetText("")
                if f.timeText.Show then f.timeText:Show() end
                f.timeText:SetAlpha(0)
            end
        end

        if f.statusBar.SetMinMaxValues then
            f.statusBar:SetMinMaxValues(0, duration)
        end

        f:SetScript("OnUpdate", _MSUF_BossPreview_OnUpdate)
    end)
end

-- ============================================================
-- Player castbar icon layout helper (prevents "reserved black gap")
-- Detach only when IconOffsetX ~= 0 (Y is cosmetic)
-- NOTE: Player castbar icon is positionable via Edit Mode, but visibility respects the player Icon toggle
--       (except while in Edit Mode, where we keep it visible so it can be repositioned).
-- ============================================================

local function MSUF_SyncBossCastbarSliders()
    EnsureDB()
    local g = MSUF_DB.general or {}
    local sx = _G["MSUF_CastbarBossXOffsetSlider"]
    local sy = _G["MSUF_CastbarBossYOffsetSlider"]
    local sw = _G["MSUF_CastbarBossWidthSlider"]
    local sh = _G["MSUF_CastbarBossHeightSlider"]

    if sx then MSUF_SetSliderValueSilent(sx, MSUF_ClampToSlider(sx, tonumber(g.bossCastbarOffsetX) or 0)) end
    if sy then MSUF_SetSliderValueSilent(sy, MSUF_ClampToSlider(sy, tonumber(g.bossCastbarOffsetY) or 0)) end
    if sw then MSUF_SetSliderValueSilent(sw, MSUF_ClampToSlider(sw, tonumber(g.bossCastbarWidth)  or 240)) end
    if sh then MSUF_SetSliderValueSilent(sh, MSUF_ClampToSlider(sh, tonumber(g.bossCastbarHeight) or 18)) end
end

MSUF_PlayerCastbarPreview  = MSUF_PlayerCastbarPreview  or nil
MSUF_TargetCastbarPreview  = MSUF_TargetCastbarPreview  or nil
MSUF_FocusCastbarPreview   = MSUF_FocusCastbarPreview   or nil

local function MSUF_CreateCastbarPreviewFrame(kind, frameName, opts)
    if type(_G.MSUF_CreateCastbarPreviewFrame) == "function" then
        return _G.MSUF_CreateCastbarPreviewFrame(kind, frameName, opts)
    end
    if MSUF_DevPrint then MSUF_DevPrint("MSUF: MSUF_CreateCastbarPreviewFrame missing") end
end

local function MSUF_SetupCastbarPreviewEditHandlers(frame, kind)
    local fn = _G.MSUF_SetupCastbarPreviewEditHandlers
    if type(fn) == "function" then
        return fn(frame, kind)
    end
end




local function MSUF_CreatePlayerCastbarPreview()
    -- Real implementation (no recursion). Creates the MSUF Edit Mode preview for the player castbar.
    if _G.MSUF_PlayerCastbarPreview then
        MSUF_PlayerCastbarPreview = _G.MSUF_PlayerCastbarPreview
        return MSUF_PlayerCastbarPreview
    end

    EnsureDB()
    local g = (MSUF_DB and MSUF_DB.general) or {}

    local w = tonumber(g.castbarPlayerBarWidth) or tonumber(g.castbarGlobalWidth) or 250
    local h = tonumber(g.castbarPlayerBarHeight) or tonumber(g.castbarGlobalHeight) or 18

    local f = MSUF_CreateCastbarPreviewFrame("player", "MSUF_PlayerCastbarPreview", {
        parent = UIParent,
        strata = "DIALOG",
        width  = w,
        height = h,
        label  = L_PLAYER_CASTBAR_PREVIEW,
        showIcon = true,
        showTime = true,
        bgAlpha = 0.8,
        initialValue = 0.5,
    })
    if not f then return end

    _G.MSUF_PlayerCastbarPreview = f
    MSUF_PlayerCastbarPreview = f

    MSUF_SetupCastbarPreviewEditHandlers(f, "player")

    -- Apply visuals once so the preview matches current settings.
    if type(MSUF_UpdateCastbarVisuals) == "function" then
        MSUF_UpdateCastbarVisuals()
    end
    if type(MSUF_UpdateCastbarTextures) == "function" then
        MSUF_UpdateCastbarTextures()
    end

    if type(MSUF_PositionPlayerCastbarPreview) == "function" then
        MSUF_PositionPlayerCastbarPreview()
    end

    return f
end


local function MSUF_CreateTargetCastbarPreview()
    -- Real implementation (no recursion). Creates the MSUF Edit Mode preview for the target castbar.
    if _G.MSUF_TargetCastbarPreview then
        MSUF_TargetCastbarPreview = _G.MSUF_TargetCastbarPreview
        return MSUF_TargetCastbarPreview
    end

    EnsureDB()
    local g = (MSUF_DB and MSUF_DB.general) or {}

    local w = tonumber(g.castbarTargetBarWidth) or tonumber(g.castbarGlobalWidth) or 250
    local h = tonumber(g.castbarTargetBarHeight) or tonumber(g.castbarGlobalHeight) or 18

    local f = MSUF_CreateCastbarPreviewFrame("target", "MSUF_TargetCastbarPreview", {
        parent = UIParent,
        strata = "DIALOG",
        width  = w,
        height = h,
        label  = L_TARGET_CASTBAR_PREVIEW,
        showIcon = true,
        showTime = true,
        bgAlpha = 0.8,
        initialValue = 0.5,
    })
    if not f then return end

    _G.MSUF_TargetCastbarPreview = f
    MSUF_TargetCastbarPreview = f

    MSUF_SetupCastbarPreviewEditHandlers(f, "target")

    if type(MSUF_UpdateCastbarVisuals) == "function" then
        MSUF_UpdateCastbarVisuals()
    end
    if type(MSUF_UpdateCastbarTextures) == "function" then
        MSUF_UpdateCastbarTextures()
    end

    if type(MSUF_PositionTargetCastbarPreview) == "function" then
        MSUF_PositionTargetCastbarPreview()
    end

    return f
end

local function MSUF_CreateFocusCastbarPreview()
    -- Real implementation (no recursion). Creates the MSUF Edit Mode preview for the focus castbar.
    if _G.MSUF_FocusCastbarPreview then
        MSUF_FocusCastbarPreview = _G.MSUF_FocusCastbarPreview
        return MSUF_FocusCastbarPreview
    end

    EnsureDB()
    local g = (MSUF_DB and MSUF_DB.general) or {}

    local w = tonumber(g.castbarFocusBarWidth) or tonumber(g.castbarGlobalWidth) or 250
    local h = tonumber(g.castbarFocusBarHeight) or tonumber(g.castbarGlobalHeight) or 18

    local f = MSUF_CreateCastbarPreviewFrame("focus", "MSUF_FocusCastbarPreview", {
        parent = UIParent,
        strata = "DIALOG",
        width  = w,
        height = h,
        label  = L_FOCUS_CASTBAR_PREVIEW,
        showIcon = true,
        showTime = true,
        bgAlpha = 0.8,
        initialValue = 0.5,
    })
    if not f then return end

    _G.MSUF_FocusCastbarPreview = f
    MSUF_FocusCastbarPreview = f

    MSUF_SetupCastbarPreviewEditHandlers(f, "focus")

    if type(MSUF_UpdateCastbarVisuals) == "function" then
        MSUF_UpdateCastbarVisuals()
    end
    if type(MSUF_UpdateCastbarTextures) == "function" then
        MSUF_UpdateCastbarTextures()
    end

    if type(MSUF_PositionFocusCastbarPreview) == "function" then
        MSUF_PositionFocusCastbarPreview()
    end

    return f
end


if type(_G.MSUF_UpdateBossCastbarPreview) ~= "function" then

    local function MSUF_CreateBossCastbarPreview_Fallback()
        if _G.MSUF_BossCastbarPreview then
            return _G.MSUF_BossCastbarPreview
        end

            local f = MSUF_CreateCastbarPreviewFrame("boss", "MSUF_BossCastbarPreview", {
        parent = UIParent,
        template = "BackdropTemplate",
        width = 240,
        height = 12,
        statusBarHeight = 12,
        initialValue = 0,
        hideFillTexture = true,
        showIcon = true,
        iconSize = 12,
        iconTexture = 134400, -- question mark
        showTime = true,
        timeLabel = "3.2",
    })
    f:EnableMouse(false)

    if f.SetBackdrop then
        f:SetBackdrop({
            bgFile   = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            edgeSize = 1,
        })
        f:SetBackdropColor(0, 0, 0, 0.55)
        f:SetBackdropBorderColor(0, 0, 0, 1)
    end

    f._msufIsPreview = true
    _G.MSUF_BossCastbarPreview = f
    return f
end

    local function MSUF_ApplyBossCastbarPreviewLayout_Fallback()
        local f = _G.MSUF_BossCastbarPreview
        if not f then return end

        EnsureDB()
        local g = (MSUF_DB and MSUF_DB.general) or {}

        local uf = _G["MSUF_boss1"]
        local forcedW, forcedH
        if type(_G.MSUF_GetCastbarDesiredSize) == "function" then
            forcedW, forcedH = _G.MSUF_GetCastbarDesiredSize("boss1", g, f, 240, 12)
        else
            forcedW = tonumber(g.bossCastbarWidth)
            forcedH = tonumber(g.bossCastbarHeight)
        end
        local w = (forcedW and forcedW > 10) and forcedW or (uf and uf.GetWidth and uf:GetWidth()) or 240
        local h = (forcedH and forcedH > 4) and forcedH or 12

        f:SetSize(w, h)

        local showIcon     = (g.castbarShowIcon ~= false)
        local iconOffsetX  = tonumber(g.castbarIconOffsetX) or 0
        local iconOffsetY  = tonumber(g.castbarIconOffsetY) or 0
        local iconDetached = (iconOffsetX ~= 0 or iconOffsetY ~= 0)

        if f.icon then
            f.icon:ClearAllPoints()
            f.icon:SetSize(h, h)
            f.icon:SetPoint("LEFT", f, "LEFT", iconOffsetX, iconOffsetY)
            f.icon:SetShown(showIcon)
        end

        if f.statusBar then
            f.statusBar:ClearAllPoints()

            if showIcon and f.icon and not iconDetached then
                f.statusBar:SetPoint("LEFT", f, "LEFT", h + 1, 0)
            else
                f.statusBar:SetPoint("LEFT", f, "LEFT", 1, 0)
            end

            f.statusBar:SetPoint("TOP", f, "TOP", 0, -1)
            f.statusBar:SetPoint("BOTTOM", f, "BOTTOM", 0, 1)
            f.statusBar:SetPoint("RIGHT", f, "RIGHT", -1, 0)

            if type(MSUF_GetCastbarTexture) == "function" and f.statusBar.SetStatusBarTexture then
                local ok, tex = MSUF_FastCall(MSUF_GetCastbarTexture)
                if ok and tex then
                    MSUF_FastCall(f.statusBar.SetStatusBarTexture, f.statusBar, tex)
                end
            end
        end

                local textOX = tonumber(g.bossCastTextOffsetX) or tonumber(g.bossCastbarTextOffsetX) or 0
        local textOY = tonumber(g.bossCastTextOffsetY) or tonumber(g.bossCastbarTextOffsetY) or 0

        -- Spell name show + boss-only font size override (fallback-safe)
        local showBossName = (g.showBossCastName ~= false)

        local baseSize = g.fontSize or 14
        local globalOverride = tonumber(g.castbarSpellNameFontSize) or 0
        local globalSize = (globalOverride and globalOverride > 0) and globalOverride or baseSize
        local bossSize = tonumber(g.bossCastSpellNameFontSize)
        if not bossSize or bossSize < 6 or bossSize > 72 then
            bossSize = globalSize
        else
            bossSize = math.floor(bossSize + 0.5)
        end

        if f.castText and f.timeText and f.statusBar then
            local tx = tonumber(g.bossCastTimeOffsetX)
            local ty = tonumber(g.bossCastTimeOffsetY)
            if tx == nil then tx = -2 end
            if ty == nil then ty = 0 end

            local showTime = (g.showBossCastTime ~= false)

            if type(_G.MSUF_ApplyBossCastbarTextsLayout) == "function" then
                _G.MSUF_ApplyBossCastbarTextsLayout(f, {
                    baselineTimeX = -2,
                    baselineTimeY = 0,
                    textOffsetX   = textOX,
                    textOffsetY   = textOY,
                    timeOffsetX   = tx,
                    timeOffsetY   = ty,
                    showName      = showBossName,
                    showTime      = showTime,
                    nameFontSize  = bossSize,
                })
            else
                -- Fallback (legacy)
                f.castText:ClearAllPoints()
                f.timeText:ClearAllPoints()

                f.castText:SetPoint("LEFT", f.statusBar, "LEFT", 2 + textOX, 0 + textOY)
                f.timeText:SetPoint("RIGHT", f.statusBar, "RIGHT", tx, ty)
                f.castText:SetPoint("RIGHT", f.timeText, "LEFT", -6, 0)

                f.castText:Show()
                f.castText:SetAlpha(showBossName and 1 or 0)
                if not showBossName then
                    f.castText:SetText("")
                end

                local font, _, flags = f.castText:GetFont()
                if font then
                    f.castText:SetFont(font, bossSize, flags)
                end

                f.timeText:Show()
                f.timeText:SetAlpha(showTime and 1 or 0)
            end
        end

    end

    local function MSUF_PositionBossCastbarPreview_Fallback()
        local f = _G.MSUF_BossCastbarPreview
        if not f then return end

        EnsureDB()
        local g = (MSUF_DB and MSUF_DB.general) or {}
        local ox = tonumber(g.bossCastbarOffsetX) or 0
        local oy = tonumber(g.bossCastbarOffsetY) or 0

        if f.GetParent and f:GetParent() ~= UIParent then
            f:SetParent(UIParent)
        end

        f:ClearAllPoints()
        f:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", -320 + ox, -200 + oy)
    end

    function _G.MSUF_UpdateBossCastbarPreview()
        -- Defense in depth: external callers (Options, hooks, etc.) may
        -- invoke Update during combat. Skip silently -- the preview is
        -- not user-visible during combat (Edit Mode is locked) and the
        -- positioning helpers below may touch protected layout state.
        if (InCombatLockdown and InCombatLockdown()) or _G.MSUF_InCombat then
            return
        end

        EnsureDB()
        local db = _G.MSUF_DB
        local g = db and db.general

        if not g or not g.castbarPlayerPreviewEnabled or g.enableBossCastbar == false then
            if _G.MSUF_BossCastbarPreview then
                _G.MSUF_BossCastbarPreview:Hide()
            end
            return
        end

        local f = _G.MSUF_BossCastbarPreview or MSUF_CreateBossCastbarPreview_Fallback()
        MSUF_PositionBossCastbarPreview_Fallback()

        -- Hard-sync first, then apply layout so live width/height edits show immediately.
        if type(_G.MSUF_HardSyncCastbarPreview) == "function" then
            local real = (_G.MSUF_BossCastbars and _G.MSUF_BossCastbars[1]) or _G.MSUF_BossCastbar
            _G.MSUF_HardSyncCastbarPreview(f, real)
        end

        MSUF_ApplyBossCastbarPreviewLayout_Fallback()

        f:Show()
    end
end

-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- Boss Preview shared helpers (4.22 Beta hotfix)
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
-- Module-scope helpers used by Setup/Refresh paths. Defined ONCE at file
-- load (no per-call closure allocation), and used as stable references to
-- C_Timer.After / Scheduler.ScheduleOnce so neither path produces garbage
-- on hot events (GROUP_ROSTER_UPDATE @ ~20/sec at boss + 40-man raid).
--
-- Combat lockdown semantics: Boss preview is an Edit-Mode-only feature
-- (Edit Mode cannot open during combat by Blizzard restriction). Refreshing
-- the preview during combat does nothing visible -- so we skip everything
-- for 0 overhead and replay once at PLAYER_REGEN_ENABLED if any event was
-- dropped. This kills the entire 20/sec burst path during boss combat.
-- â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

local _pendingPostCombatRefresh = false

local _MAX_BOSS_FRAMES_FALLBACK = 5

-- Iterate all boss-preview frames (boss1..bossN). One-time definition.
local function _IterateBossPreviews(fn)
    local f1 = _G.MSUF_BossCastbarPreview
    if f1 then fn(f1) end

    local n = tonumber(_G.MAX_BOSS_FRAMES) or _MAX_BOSS_FRAMES_FALLBACK
    if n < 1 or n > 12 then n = _MAX_BOSS_FRAMES_FALLBACK end
    for i = 2, n do
        local p = _G["MSUF_BossCastbarPreview" .. i]
        if p then fn(p) end
    end
end

-- "No-fill" preview setup applied to every boss preview during edit-mode setup.
-- Hoisted out of the previous inline closure (was allocated per call inside
-- MSUF_SetupBossCastbarPreviewEditMode).
local function _ApplyNoFillToBossPreview(p)
    if not p or not p.statusBar then return end
    if p.statusBar.GetStatusBarTexture then
        local t = p.statusBar:GetStatusBarTexture()
        if t and t.SetAlpha then
            t:SetAlpha(0)
        end
        if p.statusBar.SetValue then
            p.statusBar:SetValue(0)
        end
        p.statusBar.MSUF_hideFillTexture = true
    end
end

-- Read the boss-preview config once. Returns the .general table iff the
-- preview is enabled; otherwise returns nil. Replaces the previous
-- `(MSUF_DB and MSUF_DB.general) or {}` pattern (no empty-table allocation)
-- and consolidates the two enable-checks into one place.
local function _BossPreviewActiveConfig()
    local db = _G.MSUF_DB
    local g = db and db.general
    if not g then return nil end
    if not g.castbarPlayerPreviewEnabled then return nil end
    if g.enableBossCastbar == false then return nil end
    return g
end

-- Combat lockdown check. Cheap C-API call (~50ns). MSUF_InCombat is a Lua
-- bool maintained by the GF event dispatcher as a fast fallback.
local function _BossPreviewCombatLockdown()
    if InCombatLockdown and InCombatLockdown() then return true end
    if _G.MSUF_InCombat then return true end
    return false
end

-- Stable refresh callback. Used as the value passed to Scheduler.ScheduleOnce
-- and C_Timer.After -- never replaced, so no closure allocation per event.
-- The hooksecurefunc on MSUF_UpdateBossCastbarPreview will trigger Setup
-- automatically; we do NOT call Setup explicitly any more (was double-work).
local function _DoBossPreviewRefresh()
    if _BossPreviewCombatLockdown() then
        _pendingPostCombatRefresh = true
        return
    end
    if not (_G.MSUF_InCombat == true or (InCombatLockdown and InCombatLockdown()))
        and type(_G.MSUF_UpdateBossCastbarPreview) == "function"
    then
        _G.MSUF_UpdateBossCastbarPreview()
    end
end

local _bossPreviewEventsRegistered = false
local _bossPreviewEvents = {
    "INSTANCE_ENCOUNTER_ENGAGE_UNIT",
    "ENCOUNTER_START",
    "ENCOUNTER_END",
    "PLAYER_ENTERING_WORLD",
    "GROUP_ROSTER_UPDATE",
}

local function _SetBossPreviewEvents(active)
    if active then
        if _bossPreviewEventsRegistered then return end
        _bossPreviewEventsRegistered = true
        for i = 1, #_bossPreviewEvents do
            MSUF_EventBus_Register(_bossPreviewEvents[i], "MSUF_BOSS_PREVIEW", MSUF_RefreshBossPreview)
        end
        return
    end
    if not _bossPreviewEventsRegistered then return end
    _bossPreviewEventsRegistered = false
    if type(MSUF_EventBus_Unregister) ~= "function" then return end
    for i = 1, #_bossPreviewEvents do
        MSUF_EventBus_Unregister(_bossPreviewEvents[i], "MSUF_BOSS_PREVIEW")
    end
end

local function _OnBossPreviewCombatStart()
    _pendingPostCombatRefresh = true
    _SetBossPreviewEvents(false)
end

-- Replay deferred refresh after combat ends, if any event was dropped.
local function _OnBossPreviewCombatEnd()
    _SetBossPreviewEvents(_BossPreviewActiveConfig() ~= nil)
    if not _pendingPostCombatRefresh then return end
    _pendingPostCombatRefresh = false
    if type(_G.MSUF_RefreshBossPreview) == "function" then
        _G.MSUF_RefreshBossPreview("PLAYER_REGEN_ENABLED")
    end
end

local function _ScheduleBossPreviewRefresh()
    if _BossPreviewCombatLockdown() then
        _pendingPostCombatRefresh = true
        return
    end
    -- Coalesce via MSUF Scheduler. Multiple events in same frame collapse
    -- to one refresh. Stable callback ref -- no per-event closure alloc.
    if _G.MSUF_ScheduleOnce then
        _G.MSUF_ScheduleOnce("MSUF_BOSS_PREVIEW_REFRESH", _DoBossPreviewRefresh)
    elseif C_Timer and C_Timer.After then
        C_Timer.After(0, _DoBossPreviewRefresh)
    else
        _DoBossPreviewRefresh()
    end
end

local function MSUF_SetupBossCastbarPreviewEditMode()
    -- Prevent recursion:
    -- MSUF_UpdateBossCastbarPreview() is hooksecured below to call this setup function.
    -- If previews are disabled (or preview doesn't exist yet) we must NOT call Update from inside
    -- the hook chain, otherwise we can spiral into MSUF_Update -> hook -> Setup -> Update ...
    if _G.MSUF_BossPreviewSetupInProgress then return end

    -- Combat lockdown: nothing to set up while in combat (Edit Mode is
    -- unavailable to the user during combat anyway). Return immediately.
    if _BossPreviewCombatLockdown() then return end

    EnsureDB()
    if not _BossPreviewActiveConfig() then return end

    local f = _G.MSUF_BossCastbarPreview
    if not f and type(_G.MSUF_UpdateBossCastbarPreview) == "function" then
        local prev = _G.MSUF_BossPreviewSetupInProgress
        _G.MSUF_BossPreviewSetupInProgress = true
        _G.MSUF_UpdateBossCastbarPreview()
        _G.MSUF_BossPreviewSetupInProgress = prev
        f = _G.MSUF_BossCastbarPreview
    end

    -- Apply the "no-fill" preview setup to ALL boss previews (boss1..bossN).
    -- Both the iterator and the per-frame callback are module-scope helpers
    -- (no per-call closure allocation).
    _IterateBossPreviews(_ApplyNoFillToBossPreview)

    -- Only boss1 needs edit handlers (settings are shared for all boss castbars).
    if f then
        MSUF_SetupCastbarPreviewEditHandlers(f, "boss")
    end
end

_G.MSUF_SetupBossCastbarPreviewEditMode = MSUF_SetupBossCastbarPreviewEditMode

if not _G.MSUF_BossPreviewSetupHooked then
    _G.MSUF_BossPreviewSetupHooked = true
    if type(_G.MSUF_UpdateBossCastbarPreview) == "function" and type(hooksecurefunc) == "function" then
        -- Stable hook callback (one definition, reused by hooksecurefunc).
        local function _onUpdateHook()
            if _G.MSUF_BossPreviewSetupInProgress then return end
            -- Combat lockdown: hooksecurefunc still fires if Update is called
            -- in combat (e.g. via Options change). Skip the heavy Setup call;
            -- it will be replayed at REGEN_ENABLED if needed.
            if _BossPreviewCombatLockdown() then return end
            EnsureDB()
            if not _BossPreviewActiveConfig() then return end
            if _G.MSUF_SetupBossCastbarPreviewEditMode then
                _G.MSUF_SetupBossCastbarPreviewEditMode()
            end
        end
        hooksecurefunc("MSUF_UpdateBossCastbarPreview", _onUpdateHook)
    end
end

if not _G.MSUF_BossPreviewEventDriver then
    _G.MSUF_BossPreviewEventDriver = true

    -- â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    -- MSUF_RefreshBossPreview (4.22 Beta hotfix)
    -- â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    -- Original trace (60s boss + 40-man raid) showed this handler firing
    -- 1234x at 20/sec, allocating a fresh closure per call into C_Timer.After
    -- (~1.2k closures/min) and double-calling Setup (once via hooksecurefunc,
    -- once explicitly).
    --
    -- Changes:
    --  1. Combat lockdown short-circuit. Boss preview is Edit-Mode-only,
    --     and Edit Mode cannot open during combat. So during combat we set
    --     a pending flag and return -- 0 EnsureDB, 0 DB lookup, 0 schedule,
    --     0 closure alloc.
    --  2. PLAYER_REGEN_ENABLED replays a single deferred refresh if any
    --     event was dropped during combat.
    --  3. Out of combat: stable callback ref + Scheduler.ScheduleOnce
    --     coalescing so multiple events in one frame collapse to one refresh.
    --  4. Removed the explicit Setup call in the deferred body (the
    --     hooksecurefunc on Update calls it; was duplicate work).
    --  5. Removed `(MSUF_DB and MSUF_DB.general) or {}` allocation pattern.
    -- â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

    function MSUF_RefreshBossPreview(event, ...)
        -- Combat fast-path: ~50ns C-call + 1 bool test, then return.
        -- This is the entire cost of the handler during a 40-man boss
        -- combat, where the original implementation paid ~1.2k closure
        -- allocations/min and 1234 redundant deferred refreshes/min.
        if _BossPreviewCombatLockdown() then
            _pendingPostCombatRefresh = true
            _SetBossPreviewEvents(false)
            return
        end

        if type(_G.MSUF_UpdateBossCastbarPreview) ~= "function" then return end
        EnsureDB()
        if not _BossPreviewActiveConfig() then return end

        _ScheduleBossPreviewRefresh()
    end

    _SetBossPreviewEvents((not _BossPreviewCombatLockdown()) and _BossPreviewActiveConfig() ~= nil)

    -- Replay channel: fires once after combat ends, only if any event was
    -- deferred during combat. Stable callback ref.
    MSUF_EventBus_Register("PLAYER_REGEN_DISABLED",          "MSUF_BOSS_PREVIEW_COMBAT_START", _OnBossPreviewCombatStart)
    MSUF_EventBus_Register("PLAYER_REGEN_ENABLED",           "MSUF_BOSS_PREVIEW_COMBAT_END", _OnBossPreviewCombatEnd)
end

if not _G.MSUF_BossPreviewApplyHooked and type(hooksecurefunc) == "function" then
    _G.MSUF_BossPreviewApplyHooked = true

    if type(_G.MSUF_ApplyBossCastbarPositionSetting) == "function" then
        hooksecurefunc("MSUF_ApplyBossCastbarPositionSetting", function()
            if _BossPreviewCombatLockdown() then return end
            EnsureDB()
            _SetBossPreviewEvents(_BossPreviewActiveConfig() ~= nil)
            if type(_G.MSUF_UpdateBossCastbarPreview) == "function" then
                _G.MSUF_UpdateBossCastbarPreview()
            end
        end)
    end

    if type(_G.MSUF_ApplyBossCastbarsEnabled) == "function" then
        hooksecurefunc("MSUF_ApplyBossCastbarsEnabled", function()
            if _BossPreviewCombatLockdown() then return end
            EnsureDB()
            _SetBossPreviewEvents(_BossPreviewActiveConfig() ~= nil)
            if type(_G.MSUF_UpdateBossCastbarPreview) == "function" then
                _G.MSUF_UpdateBossCastbarPreview()
            end
        end)
    end
end

function MSUF_PositionPlayerCastbarPreview()
    if not MSUF_PlayerCastbarPreview then
        return
    end

    -- Hard-sync preview to the real bar (runtime truth) to avoid profile apply/import timing drift.
    if type(_G.MSUF_HardSyncCastbarPreview) == "function" then
        _G.MSUF_HardSyncCastbarPreview(MSUF_PlayerCastbarPreview, _G.MSUF_PlayerCastbar)
    end

    -- Core owns the unitframe table; refresh our reference (safe, edit-mode only).
    UnitFrames = UnitFrames or _G.MSUF_UnitFrames

    EnsureDB()
    local g = MSUF_DB.general or {}

    local offsetX = g.castbarPlayerOffsetX or 0
    local offsetY = g.castbarPlayerOffsetY or 5

    local anchorFrame
    if g.castbarPlayerDetached then
        anchorFrame = UIParent
    else
        if not UnitFrames or not UnitFrames["player"] then
            return
        end
        anchorFrame = UnitFrames["player"]
    end

    if not anchorFrame then
        return
    end

    -- Keep preview cast time text in sync with CastTime X/Y offsets (Edit Mode expects live feedback)
    if MSUF_PlayerCastbarPreview.timeText and MSUF_PlayerCastbarPreview.statusBar then
        local tx = tonumber(g.castbarPlayerTimeOffsetX)
        local ty = tonumber(g.castbarPlayerTimeOffsetY)
        if tx == nil then tx = -2 end
        if ty == nil then ty = 0 end
        MSUF_PlayerCastbarPreview.timeText:ClearAllPoints()
        MSUF_PlayerCastbarPreview.timeText:SetPoint("RIGHT", MSUF_PlayerCastbarPreview.statusBar, "RIGHT", tx, ty)
    end


    if type(_G.MSUF_ApplyCastbarTimeTextLayout) == "function" then
        pcall(_G.MSUF_ApplyCastbarTimeTextLayout, MSUF_PlayerCastbarPreview, "player")
    end

    MSUF_PlayerCastbarPreview:ClearAllPoints()
    if g.castbarPlayerDetached then
        MSUF_PlayerCastbarPreview:SetPoint("CENTER", anchorFrame, "CENTER", offsetX, offsetY)
    else
        MSUF_PlayerCastbarPreview:SetPoint("BOTTOM", anchorFrame, "TOP", offsetX, offsetY)
    end
end

function MSUF_PositionTargetCastbarPreview()
    if not MSUF_TargetCastbarPreview then
        return
    end

    -- Hard-sync preview to the real bar (runtime truth) to avoid profile apply/import timing drift.
    if type(_G.MSUF_HardSyncCastbarPreview) == "function" then
        _G.MSUF_HardSyncCastbarPreview(MSUF_TargetCastbarPreview, _G.MSUF_TargetCastbar)
    end

    UnitFrames = UnitFrames or _G.MSUF_UnitFrames

    EnsureDB()
    local g = MSUF_DB.general or {}

    if MSUF_TargetCastbarPreview and MSUF_TargetCastbarPreview.timeText then
        if g.showTargetCastTime ~= false then
            MSUF_TargetCastbarPreview.timeText:Show()
            MSUF_TargetCastbarPreview.timeText:SetAlpha(1)
        else
            MSUF_TargetCastbarPreview.timeText:SetText("")
            MSUF_TargetCastbarPreview.timeText:Show()
            MSUF_TargetCastbarPreview.timeText:SetAlpha(0)
        end
    end

    -- Apply CastTime X/Y offsets to preview time text so popup sliders visibly work
    if MSUF_TargetCastbarPreview and MSUF_TargetCastbarPreview.timeText and MSUF_TargetCastbarPreview.statusBar then
        local tx = tonumber(g.castbarTargetTimeOffsetX)
        local ty = tonumber(g.castbarTargetTimeOffsetY)
        if tx == nil then tx = tonumber(g.castbarPlayerTimeOffsetX) end
        if ty == nil then ty = tonumber(g.castbarPlayerTimeOffsetY) end
        if tx == nil then tx = -2 end
        if ty == nil then ty = 0 end
        MSUF_TargetCastbarPreview.timeText:ClearAllPoints()
        MSUF_TargetCastbarPreview.timeText:SetPoint("RIGHT", MSUF_TargetCastbarPreview.statusBar, "RIGHT", tx, ty)
    end

    if type(_G.MSUF_ApplyCastbarTimeTextLayout) == "function" then
        pcall(_G.MSUF_ApplyCastbarTimeTextLayout, MSUF_TargetCastbarPreview, "target")
    end


    local offsetX = g.castbarTargetOffsetX or 65
    local offsetY = g.castbarTargetOffsetY or -15

    local anchorFrame
    if g.castbarTargetDetached then
        anchorFrame = UIParent
    else
        if not UnitFrames or not UnitFrames["target"] then
            return
        end
        anchorFrame = UnitFrames["target"]
    end

    if not anchorFrame then
        return
    end

    MSUF_TargetCastbarPreview:ClearAllPoints()
    if g.castbarTargetDetached then
        MSUF_TargetCastbarPreview:SetPoint("CENTER", anchorFrame, "CENTER", offsetX, offsetY)
    else
        MSUF_TargetCastbarPreview:SetPoint("BOTTOMLEFT", anchorFrame, "TOPLEFT", offsetX, offsetY)
    end
end
function MSUF_PositionFocusCastbarPreview()
    if not MSUF_FocusCastbarPreview then
        return
    end

    -- Hard-sync preview to the real bar (runtime truth) to avoid profile apply/import timing drift.
    if type(_G.MSUF_HardSyncCastbarPreview) == "function" then
        _G.MSUF_HardSyncCastbarPreview(MSUF_FocusCastbarPreview, _G.MSUF_FocusCastbar)
    end

    UnitFrames = UnitFrames or _G.MSUF_UnitFrames

    EnsureDB()
    local g = MSUF_DB.general or {}

    -- Apply CastTime X/Y offsets to preview time text (needed for live Edit Mode preview)
    if MSUF_FocusCastbarPreview and MSUF_FocusCastbarPreview.timeText and MSUF_FocusCastbarPreview.statusBar then
        local tx = tonumber(g.castbarFocusTimeOffsetX)
        local ty = tonumber(g.castbarFocusTimeOffsetY)
        if tx == nil then tx = tonumber(g.castbarPlayerTimeOffsetX) end
        if ty == nil then ty = tonumber(g.castbarPlayerTimeOffsetY) end
        if tx == nil then tx = -2 end
        if ty == nil then ty = 0 end
        MSUF_FocusCastbarPreview.timeText:ClearAllPoints()
        MSUF_FocusCastbarPreview.timeText:SetPoint("RIGHT", MSUF_FocusCastbarPreview.statusBar, "RIGHT", tx, ty)
    end
    
    if type(_G.MSUF_ApplyCastbarTimeTextLayout) == "function" then
        pcall(_G.MSUF_ApplyCastbarTimeTextLayout, MSUF_FocusCastbarPreview, "focus")
    end


    local offsetX = g.castbarFocusOffsetX or (g.castbarTargetOffsetX or 65)
    local offsetY = g.castbarFocusOffsetY or (g.castbarTargetOffsetY or -15)

    local anchorFrame
    if g.castbarFocusDetached then
        anchorFrame = UIParent
    else
        if not UnitFrames or not UnitFrames["focus"] then
            return
        end
        anchorFrame = UnitFrames["focus"]
    end

    if not anchorFrame then
        return
    end

    MSUF_FocusCastbarPreview:ClearAllPoints()
    if g.castbarFocusDetached then
        MSUF_FocusCastbarPreview:SetPoint("CENTER", anchorFrame, "CENTER", offsetX, offsetY)
    else
        MSUF_FocusCastbarPreview:SetPoint("BOTTOMLEFT", anchorFrame, "TOPLEFT", offsetX, offsetY)
    end
end

function MSUF_UpdatePlayerCastbarPreview()
    EnsureDB()
    local g = MSUF_DB.general or {}

    UnitFrames = UnitFrames or _G.MSUF_UnitFrames

    if not g.castbarPlayerPreviewEnabled then
        if MSUF_PlayerCastbarPreview then
            MSUF_PlayerCastbarPreview:Hide()
        end
        if MSUF_TargetCastbarPreview then
            MSUF_TargetCastbarPreview:Hide()
        end
        if MSUF_FocusCastbarPreview then
            MSUF_FocusCastbarPreview:Hide()
        end
if not (_G.MSUF_InCombat == true or (InCombatLockdown and InCombatLockdown()))
    and type(_G.MSUF_UpdateBossCastbarPreview) == "function"
then
    _G.MSUF_UpdateBossCastbarPreview()
elseif _G.MSUF_BossCastbarPreview then
    _G.MSUF_BossCastbarPreview:Hide()
end

-- Stop any running popup test casts when previews are disabled.
if type(_G.MSUF_SetPlayerCastbarTestMode) == "function" then
    _G.MSUF_SetPlayerCastbarTestMode(false, true)
end
if type(_G.MSUF_SetTargetCastbarTestMode) == "function" then
    _G.MSUF_SetTargetCastbarTestMode(false, true)
end
if type(_G.MSUF_SetFocusCastbarTestMode) == "function" then
    _G.MSUF_SetFocusCastbarTestMode(false, true)
end
if type(_G.MSUF_SetBossCastbarTestMode) == "function" then
    _G.MSUF_SetBossCastbarTestMode(false, true)
end
        return
    end

    local playerPreview = MSUF_PlayerCastbarPreview or MSUF_CreatePlayerCastbarPreview()
    if playerPreview and MSUF_PositionPlayerCastbarPreview then
        MSUF_PositionPlayerCastbarPreview()
        playerPreview:Show()
        -- Keep player preview size synced to edit-mode size keys
        local w, h = MSUF_GetPlayerCastbarDesiredSize(g, MSUF_PlayerCastbarPreview, 250, 18)
        MSUF_ApplyPlayerCastbarSizeAndLayout(playerPreview, g, w, h)

    end

    -- Helper: compute preview size the same way the real bar does (supports "auto width" when attached).
    local function GetAttachedWidthOrDB(widthKey, detachedFlag, unitKey, fallback)
        local wv = tonumber(g[widthKey])
        if not wv or wv <= 0 then
            if not detachedFlag and UnitFrames and UnitFrames[unitKey] and UnitFrames[unitKey].GetWidth then
                wv = UnitFrames[unitKey]:GetWidth()
            end
        end
        if not wv or wv <= 0 then
            wv = tonumber(g.castbarGlobalWidth) or fallback or 250
        end
        return wv
    end
    local function GetHeightOrDB(heightKey, fallback)
        local hv = tonumber(g[heightKey])
        if not hv or hv <= 0 then
            hv = tonumber(g.castbarGlobalHeight)
        end
        if not hv or hv <= 0 then
            hv = fallback or 18
        end
        return hv
    end
    local function GetCastbarDesiredPreviewSize(unitKey, frame, widthKey, heightKey, detachedFlag, fallbackW, fallbackH)
        if type(_G.MSUF_GetCastbarDesiredSize) == "function" then
            return _G.MSUF_GetCastbarDesiredSize(unitKey, g, frame, fallbackW or 250, fallbackH or 18)
        end
        return GetAttachedWidthOrDB(widthKey, detachedFlag, unitKey, fallbackW), GetHeightOrDB(heightKey, fallbackH)
    end

    if UnitFrames and UnitFrames["target"] then
        local targetPreview = MSUF_TargetCastbarPreview or MSUF_CreateTargetCastbarPreview()
        if targetPreview and MSUF_PositionTargetCastbarPreview then
            if type(_G.MSUF_ApplyPlayerCastbarSizeAndLayout) == "function" then
                local tw, th = GetCastbarDesiredPreviewSize("target", targetPreview, "castbarTargetBarWidth", "castbarTargetBarHeight", g.castbarTargetDetached, 250, 18)
                _G.MSUF_ApplyPlayerCastbarSizeAndLayout(targetPreview, g, tw, th)
            end
            MSUF_PositionTargetCastbarPreview()
            targetPreview:Show()
        end
    elseif MSUF_TargetCastbarPreview then
        MSUF_TargetCastbarPreview:Hide()
    end

    if UnitFrames and UnitFrames["focus"] then
        local focusPreview = MSUF_FocusCastbarPreview or MSUF_CreateFocusCastbarPreview()
        if focusPreview and MSUF_PositionFocusCastbarPreview then
            if type(_G.MSUF_ApplyPlayerCastbarSizeAndLayout) == "function" then
                local fw, fh = GetCastbarDesiredPreviewSize("focus", focusPreview, "castbarFocusBarWidth", "castbarFocusBarHeight", g.castbarFocusDetached, 250, 18)
                -- If focus is attached but has no unitframe yet, mirror target as a pragmatic fallback.
                if (not fw or fw <= 0) and (UnitFrames and UnitFrames["target"] and UnitFrames["target"].GetWidth) then
                    fw = UnitFrames["target"]:GetWidth()
                end
                _G.MSUF_ApplyPlayerCastbarSizeAndLayout(focusPreview, g, fw, fh)
            end
            MSUF_PositionFocusCastbarPreview()
            focusPreview:Show()
        end
    elseif MSUF_FocusCastbarPreview then
        MSUF_FocusCastbarPreview:Hide()
    end
if not (_G.MSUF_InCombat == true or (InCombatLockdown and InCombatLockdown()))
    and type(_G.MSUF_UpdateBossCastbarPreview) == "function"
then
    _G.MSUF_UpdateBossCastbarPreview()
    MSUF_SetupBossCastbarPreviewEditMode()
end

    if MSUF_UpdateCastbarVisuals then
        MSUF_UpdateCastbarVisuals()
    end
    if MSUF_UpdateCastbarTextures then
        MSUF_UpdateCastbarTextures()
    end
end

---------------------------------------------------------------------------
-- _G exports (most TestMode functions already defined directly on _G)
---------------------------------------------------------------------------
_G.MSUF_HideBlizzardPlayerCastbar      = MSUF_HideBlizzardPlayerCastbar
_G.MSUF_SyncBossCastbarSliders         = MSUF_SyncBossCastbarSliders
if type(_G.MSUF_CreatePlayerCastbarPreview) ~= 'function' then _G.MSUF_CreatePlayerCastbarPreview = MSUF_CreatePlayerCastbarPreview end
if type(_G.MSUF_CreateTargetCastbarPreview) ~= 'function' then _G.MSUF_CreateTargetCastbarPreview = MSUF_CreateTargetCastbarPreview end
if type(_G.MSUF_CreateFocusCastbarPreview) ~= 'function' then _G.MSUF_CreateFocusCastbarPreview = MSUF_CreateFocusCastbarPreview end
_G.MSUF_PositionPlayerCastbarPreview   = MSUF_PositionPlayerCastbarPreview
_G.MSUF_PositionTargetCastbarPreview   = MSUF_PositionTargetCastbarPreview
_G.MSUF_PositionFocusCastbarPreview    = MSUF_PositionFocusCastbarPreview
_G.MSUF_UpdatePlayerCastbarPreview     = MSUF_UpdatePlayerCastbarPreview
