local addonName, ns = ...
local W = ns.Widgets

local BRIGHT_FONT = "Interface\\AddOns\\BrightQoL\\Assets\\Fonts\\Bright.ttf"
local BAR_TEXTURE = "Interface\\Buttons\\WHITE8x8"
local TIMER_ID    = 1

local FRAME_BACKDROP = {
    bgFile  = "Interface\\Tooltips\\UI-Tooltip-Background",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    tile = true, tileSize = 16, edgeSize = 16,
    insets = { left = 4, right = 4, top = 4, bottom = 4 },
}

---------------------------------------------------------------------------
-- Helpers
---------------------------------------------------------------------------
local function FormatTime(seconds)
    local m = math.floor(seconds / 60)
    local s = math.floor(seconds % 60)
    return string.format("%d:%02d", m, s)
end

local function RGBHex(r, g, b)
    if type(r) == "table" then r, g, b = r.r, r.g, r.b end
    return string.format("|cff%02x%02x%02x", (r or 1)*255, (g or 1)*255, (b or 1)*255)
end

local function IsInMythicPlus()
    local _, _, difficultyID = GetInstanceInfo()
    return difficultyID == 8
end

local function HasAttackableTarget()
    return UnitExists("target") and UnitCanAttack("player", "target") and not UnitIsDeadOrGhost("target")
end

---------------------------------------------------------------------------
-- MythicTimer module
---------------------------------------------------------------------------
local MT = {}
ns.MythicTimer = MT

-- State
MT.running         = false
MT.totalDuration   = 0
MT.frozenElapsed   = nil
MT.lastElapsed     = 0
MT.deathCount      = 0
MT.deathPenalty    = 0
MT.isCompleted     = false
MT.testMode        = false
MT.cachedForces    = 0
MT.activeLevel     = 0
MT.bossFrames      = {}
MT.bossKillTimes   = {}

---------------------------------------------------------------------------
-- Timer engine
---------------------------------------------------------------------------
function MT:GetAPITime()
    local getTimer = GetWorldElapsedTime or (C_Timer and C_Timer.GetWorldElapsedTime)
    if getTimer then
        local _, elapsed = getTimer(TIMER_ID)
        if elapsed and elapsed > 0 then return elapsed end
    end
    return 0
end

function MT:GetTimeState()
    local elapsed
    if self.frozenElapsed then
        elapsed = self.frozenElapsed
    elseif self.running then
        elapsed = self:GetAPITime()
        if elapsed > 0 then self.lastElapsed = elapsed
        elseif self.lastElapsed > 0 then elapsed = self.lastElapsed
        end
    else
        elapsed = 0
    end
    return elapsed, self.totalDuration - elapsed, self.totalDuration
end

function MT:StartTimer(duration)
    self.totalDuration = duration
    self.running = true
    self.frozenElapsed = nil
    self.lastElapsed = 0
end

function MT:ResumeTimer(duration)
    self.totalDuration = duration or 0
    self.running = true
    self.frozenElapsed = nil
end

function MT:StopTimer()
    if self.running then
        local t = self:GetAPITime()
        self.frozenElapsed = (t <= 0 and self.lastElapsed > 0) and self.lastElapsed or t
        self.running = false
    end
end

function MT:ForceFrozenTime(seconds)
    self.running = false
    self.frozenElapsed = seconds
    self.lastElapsed = seconds
end

function MT:ResetTimer()
    self.totalDuration = 0
    self.running = false
    self.frozenElapsed = nil
    self.lastElapsed = 0
end

function MT:GetThresholds()
    if self.totalDuration == 0 then return 0, 0 end
    return self.totalDuration * 0.8, self.totalDuration * 0.6
end

---------------------------------------------------------------------------
-- Death tracker
---------------------------------------------------------------------------
function MT:UpdateDeaths()
    if C_ChallengeMode and C_ChallengeMode.GetDeathCount then
        self.deathCount = C_ChallengeMode.GetDeathCount() or 0
        self:CalcDeathPenalty()
    end
end

function MT:CalcDeathPenalty()
    local level = C_ChallengeMode.GetActiveKeystoneInfo()
    local penalty = 5
    if level then
        if level < 4 then penalty = 0
        elseif level >= 12 then penalty = 15
        end
    end
    self.deathPenalty = self.deathCount * penalty
end

---------------------------------------------------------------------------
-- Dungeon data helpers
---------------------------------------------------------------------------
function MT:GetMapInfo()
    local mapID = C_ChallengeMode.GetActiveChallengeMapID()
    if not mapID then return nil end
    local name, _, timeLimit = C_ChallengeMode.GetMapUIInfo(mapID)
    return { name = name or "Unknown", time = timeLimit or 1800, mapID = mapID }
end

function MT:GetAffixes()
    local _, affixIDs = C_ChallengeMode.GetActiveKeystoneInfo()
    local out = {}
    if affixIDs then
        for _, id in ipairs(affixIDs) do
            local name = C_ChallengeMode.GetAffixInfo(id)
            if name then table.insert(out, name) end
        end
    end
    return out
end

function MT:CacheDungeonForces()
    if self.cachedForces > 0 then return end
    local _, _, numCriteria = C_Scenario.GetStepInfo()
    for i = 1, (numCriteria or 0) do
        local info = C_ScenarioInfo.GetCriteriaInfo(i)
        if info and info.isWeightedProgress and info.totalQuantity and info.totalQuantity > 0 then
            self.cachedForces = info.totalQuantity
            return
        end
    end
end

function MT:GetActiveLevel()
    return C_ChallengeMode.GetActiveKeystoneInfo() or 0
end

---------------------------------------------------------------------------
-- HUD frame
---------------------------------------------------------------------------
local hud

function MT:BuildHUD()
    hud = CreateFrame("Frame", "BrightQoL_MythicTimerHUD", UIParent, "BackdropTemplate")
    hud:SetSize(285, 300)
    hud:SetMovable(true)
    hud:SetClampedToScreen(true)
    hud:Hide()

    -- Dungeon name
    hud.dungeonName = hud:CreateFontString(nil, "OVERLAY")
    hud.dungeonName:SetFont(BRIGHT_FONT, 14, "OUTLINE")
    hud.dungeonName:SetPoint("TOPRIGHT", -10, -10)
    hud.dungeonName:SetJustifyH("RIGHT")
    hud.dungeonName:SetText("Waiting for Key...")

    -- Timer
    hud.timerText = hud:CreateFontString(nil, "OVERLAY")
    hud.timerText:SetFont(BRIGHT_FONT, 22, "OUTLINE")
    hud.timerText:SetPoint("TOPRIGHT", hud.dungeonName, "BOTTOMRIGHT", 0, -5)
    hud.timerText:SetJustifyH("RIGHT")

    -- Upgrade (+2/+3 remaining)
    hud.upgradeText = hud:CreateFontString(nil, "OVERLAY")
    hud.upgradeText:SetFont(BRIGHT_FONT, 13, "OUTLINE")
    hud.upgradeText:SetPoint("TOPRIGHT", hud.timerText, "BOTTOMRIGHT", 0, -2)
    hud.upgradeText:SetJustifyH("RIGHT")

    -- Deaths
    hud.deathText = hud:CreateFontString(nil, "OVERLAY")
    hud.deathText:SetFont(BRIGHT_FONT, 13, "OUTLINE")
    hud.deathText:SetPoint("TOPRIGHT", hud.upgradeText, "BOTTOMRIGHT", 0, -5)
    hud.deathText:SetJustifyH("RIGHT")

    -- Affixes
    hud.affixText = hud:CreateFontString(nil, "OVERLAY")
    hud.affixText:SetFont(BRIGHT_FONT, 10, "OUTLINE")
    hud.affixText:SetPoint("TOPRIGHT", hud.deathText, "BOTTOMRIGHT", 0, -5)
    hud.affixText:SetJustifyH("RIGHT")

    -- Enemy bar container
    hud.enemyBarContainer = CreateFrame("Frame", nil, hud)
    hud.enemyBarContainer:SetSize(270, 16)
    hud.enemyBarContainer:SetPoint("TOPRIGHT", hud.affixText, "BOTTOMRIGHT", 0, -5)

    hud.enemyBarBG = hud.enemyBarContainer:CreateTexture(nil, "BACKGROUND")
    hud.enemyBarBG:SetAllPoints()
    hud.enemyBarBG:SetColorTexture(0, 0, 0, 0.8)

    hud.enemyBar = CreateFrame("StatusBar", nil, hud.enemyBarContainer)
    hud.enemyBar:SetAllPoints()
    hud.enemyBar:SetStatusBarTexture(BAR_TEXTURE)
    hud.enemyBar:SetStatusBarColor(0.45, 0, 0.85, 1)
    hud.enemyBar:SetMinMaxValues(0, 100)

    hud.enemyText = hud.enemyBar:CreateFontString(nil, "OVERLAY")
    hud.enemyText:SetFont(BRIGHT_FONT, 12, "OUTLINE")
    hud.enemyText:SetPoint("CENTER")

    -- Boss list container
    hud.bossContainer = CreateFrame("Frame", nil, hud)
    hud.bossContainer:SetSize(270, 200)
    hud.bossContainer:SetPoint("TOPRIGHT", hud.enemyBar, "BOTTOMRIGHT", 0, -12)

    self.hud = hud
end

function MT:ApplyHUDConfig()
    local db = BrightQoL.mythicTimer
    if not db or not hud then return end

    -- Colors
    local c = db.colors
    if c then
        if c.enemyBar then hud.enemyBar:SetStatusBarColor(c.enemyBar.r, c.enemyBar.g, c.enemyBar.b, 1) end
        if c.dungeonName then hud.dungeonName:SetTextColor(c.dungeonName.r, c.dungeonName.g, c.dungeonName.b, 1) end
    end

    -- Scale
    hud:SetScale(db.scale or 1.0)

    -- Position
    if db.point then
        hud:ClearAllPoints()
        hud:SetPoint(db.point, UIParent, db.relPoint or db.point, db.x or -70, db.y or 20)
    else
        hud:ClearAllPoints()
        hud:SetPoint("RIGHT", UIParent, "RIGHT", -70, 20)
    end
end

function MT:UpdateLayout()
    local db = BrightQoL.mythicTimer
    if not db or not hud then return end

    local last = hud.upgradeText
    local gap  = -5

    local showDeath  = self.deathCount > 0 or self.testMode
    local hasAffix   = (hud.affixText:GetText() or "") ~= ""

    hud.deathText:ClearAllPoints()
    if showDeath then
        hud.deathText:SetPoint("TOPRIGHT", last, "BOTTOMRIGHT", 0, gap)
        last = hud.deathText
    else
        hud.deathText:SetPoint("TOPRIGHT", last, "BOTTOMRIGHT", 0, 0)
    end
    hud.deathText:SetShown(showDeath)

    hud.affixText:ClearAllPoints()
    if hasAffix then
        hud.affixText:SetPoint("TOPRIGHT", last, "BOTTOMRIGHT", 0, gap)
        last = hud.affixText
    else
        hud.affixText:SetPoint("TOPRIGHT", last, "BOTTOMRIGHT", 0, 0)
    end
    hud.affixText:SetShown(hasAffix)

    hud.enemyBarContainer:ClearAllPoints()
    hud.enemyBarContainer:SetPoint("TOPRIGHT", last, "BOTTOMRIGHT", 0, gap)
end

---------------------------------------------------------------------------
-- Display update helpers
---------------------------------------------------------------------------
function MT:GetColor(key, default_r, default_g, default_b)
    local db = BrightQoL.mythicTimer
    local c = db and db.colors and db.colors[key]
    if c then return c.r, c.g, c.b end
    return default_r or 1, default_g or 1, default_b or 1
end

function MT:FormatTimer(current, total, level)
    local db = BrightQoL.mythicTimer
    local overtime = current > total
    local cr, cg, cb = self:GetColor(overtime and "timerOvertime" or "timerText", 1, 1, 1)
    local lr, lg, lb = self:GetColor("keyLevel", 1, 1, 1)
    return string.format("%s(+%d)|r %s%s / %s|r",
        RGBHex(lr, lg, lb), level,
        RGBHex(cr, cg, cb), FormatTime(current), FormatTime(total))
end

function MT:FormatUpgrade(r2, r3)
    local c2r, c2g, c2b = self:GetColor(r2 > 0 and "upgradeTwo" or "upgradeDepleted", 1, 1, 0)
    local c3r, c3g, c3b = self:GetColor(r3 > 0 and "upgradeThree" or "upgradeDepleted", 1, 1, 0)
    return string.format("%s%s|r  %s%s|r",
        RGBHex(c3r, c3g, c3b), FormatTime(math.max(0, r3)),
        RGBHex(c2r, c2g, c2b), FormatTime(math.max(0, r2)))
end

function MT:FormatDeaths(count, penalty)
    local dr, dg, db_ = self:GetColor("deathText", 1, 1, 1)
    local pr, pg, pb  = self:GetColor("deathPenalty", 1, 0, 0)
    if count > 0 then
        return string.format("%s%d Deaths|r %s(-%ds)|r",
            RGBHex(dr, dg, db_), count, RGBHex(pr, pg, pb), penalty)
    end
    return string.format("%s0 Deaths|r", RGBHex(dr, dg, db_))
end

function MT:FormatEnemyForces(killed, total)
    local db = BrightQoL.mythicTimer
    local pct = total > 0 and (killed / total * 100) or 0
    local complete = pct >= 100

    local parts = {}
    if db and db.showEnemyCount then table.insert(parts, string.format("%d/%d", killed, total)) end
    if db and db.showEnemyPercent then table.insert(parts, string.format("%.2f%%", pct)) end
    local txt = table.concat(parts, " - ")
    if txt == "" then txt = string.format("%.2f%%", pct) end

    local cr, cg, cb
    if complete then cr, cg, cb = self:GetColor("enemyTextComplete", 0, 1, 0)
    else             cr, cg, cb = self:GetColor("enemyText", 1, 1, 1)
    end
    return string.format("%s%s|r", RGBHex(cr, cg, cb), txt), pct
end

function MT:UpdateEnemyForcesFromInfo(info)
    if self.cachedForces <= 0 then self.cachedForces = info.totalQuantity or 0 end
    if self.cachedForces <= 0 then return end
    local killed = tonumber(string.match(info.quantityString or "0", "(%d+)")) or 0
    local txt, pct = self:FormatEnemyForces(killed, self.cachedForces)
    hud.enemyText:SetText(txt)
    hud.enemyBar:SetValue(pct)
end

function MT:UpdateBossLine(idx, name, completed, elapsed)
    local db = BrightQoL.mythicTimer
    if completed and not self.bossKillTimes[name] then
        self.bossKillTimes[name] = (elapsed > 0) and elapsed or -1
    end
    local isDead = completed or (self.bossKillTimes[name] ~= nil)

    local line = self.bossFrames[idx]
    if not line then
        line = hud.bossContainer:CreateFontString(nil, "OVERLAY")
        line:SetFont(BRIGHT_FONT, 12, "OUTLINE")
        line:SetJustifyH("RIGHT")
        self.bossFrames[idx] = line
    end
    line:ClearAllPoints()
    line:SetPoint("TOPRIGHT", hud.bossContainer, "TOPRIGHT", 0, -((idx-1) * 20))

    local cr, cg, cb
    if isDead then cr, cg, cb = self:GetColor("bossDead", 0, 1, 0)
    else           cr, cg, cb = self:GetColor("bossAlive", 1, 1, 1)
    end

    local killTime = self.bossKillTimes[name]
    if isDead and db and db.showBossKillTimes and killTime then
        local timeStr = (killTime == -1) and "--:--" or FormatTime(killTime)
        line:SetText(string.format("%s[%s] %s|r", RGBHex(cr, cg, cb), timeStr, name))
    else
        line:SetText(string.format("%s%s|r", RGBHex(cr, cg, cb), name))
    end
    line:Show()
end

function MT:UpdateScenario(elapsed)
    if self.testMode then return end
    self:CacheDungeonForces()
    local _, _, numCriteria = C_Scenario.GetStepInfo()
    local bossIdx = 0
    local current, _, _ = self:GetTimeState()

    for i = 1, (numCriteria or 0) do
        local info = C_ScenarioInfo.GetCriteriaInfo(i)
        if info then
            if info.isWeightedProgress then
                self:UpdateEnemyForcesFromInfo(info)
            elseif info.description and info.description ~= "" then
                bossIdx = bossIdx + 1
                self:UpdateBossLine(bossIdx, info.description, info.completed, current)
            end
        end
    end
end

function MT:UpdateAffixes()
    local affixes = self:GetAffixes()
    local cr, cg, cb = self:GetColor("affixText", 0.5, 0.5, 0.5)
    local str = table.concat(affixes, ", ")
    hud.affixText:SetText(string.format("%s%s|r", RGBHex(cr, cg, cb), str))
    self:UpdateLayout()
end

---------------------------------------------------------------------------
-- Demo mode
---------------------------------------------------------------------------
function MT:ShowDemo()
    if self:IsInActiveRun() then return end
    self.testMode = true
    local db = BrightQoL.mythicTimer
    if not db or not db.enabled then return end

    hud.dungeonName:SetText("Demo Dungeon")
    hud.timerText:SetText(self:FormatTimer(1554, 1800, 10))
    hud.upgradeText:SetText(self:FormatUpgrade(246, 60))
    hud.deathText:SetText(self:FormatDeaths(2, 10))
    hud.affixText:SetText("|cff888888Fortified, Bursting|r")

    local txt, pct = self:FormatEnemyForces(36, 300)
    hud.enemyText:SetText(txt)
    hud.enemyBar:SetValue(pct)

    for _, f in pairs(self.bossFrames) do f:Hide() end
    local demoBosses = {
        { name = "First Boss",  dead = true,  t = 760 },
        { name = "Second Boss", dead = false, t = nil },
        { name = "Third Boss",  dead = false, t = nil },
    }
    for i, b in ipairs(demoBosses) do
        local line = self.bossFrames[i]
        if not line then
            line = hud.bossContainer:CreateFontString(nil, "OVERLAY")
            line:SetFont(BRIGHT_FONT, 12, "OUTLINE")
            line:SetJustifyH("RIGHT")
            self.bossFrames[i] = line
        end
        line:ClearAllPoints()
        line:SetPoint("TOPRIGHT", hud.bossContainer, "TOPRIGHT", 0, -((i-1)*20))
        local cr, cg, cb = self:GetColor(b.dead and "bossDead" or "bossAlive", b.dead and 0 or 1, b.dead and 1 or 1, 0)
        if b.dead and db and db.showBossKillTimes and b.t then
            line:SetText(string.format("%s[%s] %s|r", RGBHex(cr, cg, cb), FormatTime(b.t), b.name))
        else
            line:SetText(string.format("%s%s|r", RGBHex(cr, cg, cb), b.name))
        end
        line:Show()
    end

    self:UpdateLayout()
    hud:Show()
end

function MT:HideDemo()
    self.testMode = false
    if not self:IsInActiveRun() then hud:Hide() end
end

function MT:ToggleDemo()
    if self.testMode then self:HideDemo() else self:ShowDemo() end
end

function MT:IsInActiveRun()
    local _, _, diffID = GetInstanceInfo()
    local mapID = C_ChallengeMode.GetActiveChallengeMapID()
    return (diffID == 8) and (mapID ~= nil and mapID > 0)
end

---------------------------------------------------------------------------
-- Run lifecycle
---------------------------------------------------------------------------
function MT:FullReset()
    self.isCompleted = false
    self.deathCount  = 0
    self.deathPenalty = 0
    self.activeLevel = 0
    self.cachedForces = 0
    self.bossKillTimes = {}
    for _, f in pairs(self.bossFrames) do f:Hide() end
    self:ResetTimer()
    if hud then
        hud.enemyText:SetText("")
        hud.enemyBar:SetValue(0)
        hud.timerText:SetText("")
        hud.upgradeText:SetText("")
        hud.deathText:SetText("")
        hud.affixText:SetText("")
    end
end

function MT:HideObjectiveTracker()
    local db = BrightQoL.mythicTimer
    if not db or not db.hideObjectiveTracker then return end
    if ObjectiveTrackerFrame then ObjectiveTrackerFrame:SetShown(false) end
    if ScenarioBlocksFrame   then ScenarioBlocksFrame:SetShown(false)   end
end

function MT:RestoreObjectiveTracker()
    if ObjectiveTrackerFrame then ObjectiveTrackerFrame:SetShown(true) end
    if ScenarioBlocksFrame   then ScenarioBlocksFrame:SetShown(true)  end
end

function MT:ShowHUD()
    local db = BrightQoL.mythicTimer
    if db and db.enabled then
        hud:Show()
        self:HideObjectiveTracker()
    end
end

function MT:HideHUD()
    if hud then hud:Hide() end
    self:RestoreObjectiveTracker()
end

function MT:OnStart()
    self.testMode = false
    self:FullReset()
    self.activeLevel = self:GetActiveLevel()
    local mapInfo = self:GetMapInfo()
    if mapInfo then
        hud.dungeonName:SetText(mapInfo.name)
        self:ResumeTimer(mapInfo.time)
    end
    self:CacheDungeonForces()
    self:UpdateAffixes()
    self:UpdateDeaths()
    self:ShowHUD()
end

function MT:OnComplete()
    self.isCompleted = true
    local _, _, time = C_ChallengeMode.GetCompletionInfo()
    if time and time > 0 then self:ForceFrozenTime(time / 1000)
    else self:StopTimer() end
    self:UpdateScenario(0)
    hud.enemyText:SetText(self:FormatEnemyForces(self.cachedForces, self.cachedForces))
    hud.enemyBar:SetValue(100)
end

function MT:CheckActiveRun()
    if not self:IsInActiveRun() then self:HideHUD(); return end
    local mapInfo = self:GetMapInfo()
    if not mapInfo then return end

    self.activeLevel = self:GetActiveLevel()
    hud.dungeonName:SetText(mapInfo.name)

    local compInfo = C_ChallengeMode.GetChallengeCompletionInfo()
    if compInfo and compInfo.time and compInfo.time > 0 then
        self.isCompleted = true
        self:ForceFrozenTime(compInfo.time / 1000)
    else
        self:ResumeTimer(mapInfo.time)
        self:CacheDungeonForces()
    end

    self:UpdateAffixes()
    self:UpdateDeaths()
    self:ShowHUD()
end

---------------------------------------------------------------------------
-- OnUpdate tick
---------------------------------------------------------------------------
local tickAcc = 0
local TICK_RATE = 0.05

local tickFrame = CreateFrame("Frame")
tickFrame:SetScript("OnUpdate", ns.PerfMonitor:Wrap("M+ Timer", function(self, elapsed)
    tickAcc = tickAcc + elapsed
    if tickAcc < TICK_RATE then return end
    tickAcc = 0

    local db = BrightQoL.mythicTimer
    if not db or not db.enabled or not hud then return end
    if MT.testMode then return end
    if db.unlock then return end  -- frame is being dragged, don't interfere
    if not IsInMythicPlus() then MT:HideHUD(); return end

    -- Re-enforce objective tracker hide every tick (Blizzard can re-show it)
    if db.hideObjectiveTracker then MT:HideObjectiveTracker() end

    local current, remaining, total = MT:GetTimeState()
    local isStarted = current > 0

    if isStarted or MT.isCompleted then
        hud.upgradeText:SetShown(true)
        hud.enemyBarContainer:SetShown(true)
        hud.bossContainer:SetShown(true)

        if total > 0 then
            hud.timerText:SetText(MT:FormatTimer(current, total, MT.activeLevel))
            local t2, t3 = MT:GetThresholds()
            hud.upgradeText:SetText(MT:FormatUpgrade(t2 - current, t3 - current))
        end

        MT:UpdateScenario(elapsed)

        if MT.deathCount > 0 then
            hud.deathText:SetText(MT:FormatDeaths(MT.deathCount, MT.deathPenalty))
        end
        MT:UpdateLayout()
        MT:ShowHUD()
    else
        hud.timerText:SetText(string.format("|cffffffff(+%d) Starting...|r", MT.activeLevel))
        hud.upgradeText:SetText("")
        hud.upgradeText:SetShown(false)
        hud.enemyBarContainer:SetShown(false)
        hud.bossContainer:SetShown(false)
    end
end))

---------------------------------------------------------------------------
-- Unlock / drag
---------------------------------------------------------------------------
function MT:SetUnlocked(unlocked)
    if not hud then return end
    local db = BrightQoL.mythicTimer
    hud:SetMovable(unlocked)
    hud:EnableMouse(unlocked)
    if unlocked then
        hud:SetBackdrop(FRAME_BACKDROP)
        hud:SetBackdropColor(0.1, 0.1, 0.1, 0.7)
        hud:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)
        hud:SetScript("OnMouseDown", function(f, btn)
            if btn == "LeftButton" then f:StartMoving() end
        end)
        hud:SetScript("OnMouseUp", function(f)
            f:StopMovingOrSizing()
            local p, _, rp, x, y = f:GetPoint()
            if db then db.point = p; db.relPoint = rp; db.x = x; db.y = y end
        end)
        hud:Show()
        hud.dungeonName:SetText("Drag to Move")
    else
        hud:SetBackdrop(nil)
        hud:EnableMouse(false)
        hud:SetScript("OnMouseDown", nil)
        hud:SetScript("OnMouseUp", nil)
        if not self.testMode and not self:IsInActiveRun() then hud:Hide() end
    end
end

function MT:ResetPosition()
    local db = BrightQoL.mythicTimer
    if db then db.point = nil; db.relPoint = nil; db.x = nil; db.y = nil end
    if hud then
        hud:ClearAllPoints()
        hud:SetPoint("RIGHT", UIParent, "RIGHT", -70, 20)
    end
end

---------------------------------------------------------------------------
-- Public refresh (called from config)
---------------------------------------------------------------------------
function MT:Refresh()
    self:ApplyHUDConfig()
    if self.testMode then self:ShowDemo() end
end

---------------------------------------------------------------------------
-- Events
---------------------------------------------------------------------------
local loader = CreateFrame("Frame")
loader:RegisterEvent("PLAYER_LOGIN")
loader:RegisterEvent("CHALLENGE_MODE_START")
loader:RegisterEvent("CHALLENGE_MODE_COMPLETED")
loader:RegisterEvent("CHALLENGE_MODE_RESET")
loader:RegisterEvent("CHALLENGE_MODE_DEATH_COUNT_UPDATED")
loader:RegisterEvent("PLAYER_ENTERING_WORLD")
loader:RegisterEvent("ZONE_CHANGED_NEW_AREA")
loader:RegisterEvent("SCENARIO_CRITERIA_UPDATE")

loader:SetScript("OnEvent", function(self, event, ...)
    local db = BrightQoL.mythicTimer
    if not db then return end

    if event == "PLAYER_LOGIN" then
        MT:BuildHUD()
        MT:ApplyHUDConfig()
        MT:CheckActiveRun()
        ns.SettingsIO:RegisterRefresh("mythicTimer", function()
            MT:Refresh()
        end)

    elseif event == "CHALLENGE_MODE_START" then
        MT.testMode = false
        MT:FullReset()
        MT:OnStart()

    elseif event == "CHALLENGE_MODE_COMPLETED" then
        MT:OnComplete()

    elseif event == "CHALLENGE_MODE_RESET" then
        MT:FullReset()
        MT:HideHUD()

    elseif event == "CHALLENGE_MODE_DEATH_COUNT_UPDATED" then
        MT:UpdateDeaths()
        if hud and hud:IsShown() then
            hud.deathText:SetText(MT:FormatDeaths(MT.deathCount, MT.deathPenalty))
            MT:UpdateLayout()
        end

    elseif event == "PLAYER_ENTERING_WORLD" or event == "ZONE_CHANGED_NEW_AREA" then
        local _, instanceType = GetInstanceInfo()
        if instanceType == "party" then
            C_Timer.After(0.5, function() MT:CheckActiveRun() end)
        else
            MT:HideHUD()
        end

    elseif event == "SCENARIO_CRITERIA_UPDATE" then
        MT:UpdateScenario(0)
    end
end)
