CowmeraDB = CowmeraDB or {}

local function TrinketPrint(msg)
    if _G.CowmeraPrint then
        _G.CowmeraPrint(msg)
    else
        print("|cff22c55e[Cowmera]|r " .. tostring(msg))
    end
end

local function EnsureTrinketOptions()
    CowmeraDB.trinketOverlay = CowmeraDB.trinketOverlay or {}
    local o = CowmeraDB.trinketOverlay

    if o.enabled == nil then o.enabled = true end
    if o.locked == nil then o.locked = true end
    if o.scale == nil then o.scale = 1 end
    if type(o.position) ~= "table" then
        o.position = {
            point = "CENTER",
            relativePoint = "CENTER",
            x = 520,
            y = -120,
        }
    end

    o.stats = o.stats or {}
    local s = o.stats
    if s.enabled == nil then s.enabled = true end
    if s.locked == nil then s.locked = true end
    if s.scale == nil then s.scale = 1 end
    if s.formatMode == nil then s.formatMode = "both" end
    if type(s.position) ~= "table" then
        s.position = {
            point = "CENTER",
            relativePoint = "CENTER",
            x = 430,
            y = -120,
        }
    end
    if type(s.shown) ~= "table" then
        s.shown = {}
    end
    if s.shown.crit == nil then s.shown.crit = true end
    if s.shown.haste == nil then s.shown.haste = true end
    if s.shown.mastery == nil then s.shown.mastery = true end
    if s.shown.versatility == nil then s.shown.versatility = true end
    if s.shown.leech == nil then s.shown.leech = false end
    if s.shown.avoidance == nil then s.shown.avoidance = false end
    if s.shown.speed == nil then s.shown.speed = false end
    if s.shown.dodge == nil then s.shown.dodge = false end
    if s.shown.parry == nil then s.shown.parry = false end

    return o
end

local TrinketOverlay = nil
local StatsOverlay = nil
local TrinketConfigHost = nil

local SLOT_IDS = {
    13,
    14,
}

local STAT_ORDER = {
    "crit",
    "haste",
    "mastery",
    "versatility",
    "leech",
    "avoidance",
    "speed",
    "dodge",
    "parry",
}


local function ResolveCombatRatingId(...)
    for i = 1, select("#", ...) do
        local value = select(i, ...)
        if type(value) == "number" and value > 0 then
            return value
        end
    end
    return nil
end

local function GetCombatRatingValue(...)
    if type(GetCombatRating) ~= "function" then
        return 0
    end
    local ratingId = ResolveCombatRatingId(...)
    if not ratingId then
        return 0
    end
    local ok, value = pcall(GetCombatRating, ratingId)
    if ok and type(value) == "number" then
        return value
    end
    return 0
end

local function GetCombatRatingBonusValue(...)
    if type(GetCombatRatingBonus) ~= "function" then
        return 0
    end
    local ratingId = ResolveCombatRatingId(...)
    if not ratingId then
        return 0
    end
    local ok, value = pcall(GetCombatRatingBonus, ratingId)
    if ok and type(value) == "number" then
        return value
    end
    return 0
end

local STAT_META = {
    crit = {
        label = "Crit",
        color = { 1.0, 0.15, 0.15 },
        getPercent = function()
            return GetCritChance and GetCritChance() or 0
        end,
        getRating = function()
            return GetCombatRatingValue(CR_CRIT_MELEE, CR_CRIT_SPELL, 9, 11)
        end,
    },
    haste = {
        label = "Haste",
        color = { 0.2, 0.55, 1.0 },
        getPercent = function()
            return GetHaste and GetHaste() or 0
        end,
        getRating = function()
            return GetCombatRatingValue(CR_HASTE_MELEE, CR_HASTE_SPELL, 18, 20)
        end,
    },
    mastery = {
        label = "Mastery",
        color = { 0.72, 0.35, 1.0 },
        getPercent = function()
            return GetMasteryEffect and GetMasteryEffect() or 0
        end,
        getRating = function()
            return GetCombatRatingValue(CR_MASTERY, 26)
        end,
    },
    versatility = {
        label = "Vers",
        color = { 1.0, 0.9, 0.2 },
        getPercent = function()
            return GetCombatRatingBonusValue(CR_VERSATILITY_DAMAGE_DONE, 29)
        end,
        getRating = function()
            return GetCombatRatingValue(CR_VERSATILITY_DAMAGE_DONE, 29)
        end,
    },
    leech = {
        label = "Leech",
        color = { 0.55, 0.05, 0.05 },
        getPercent = function()
            return GetLifesteal and GetLifesteal() or 0
        end,
        getRating = function()
            return GetCombatRatingValue(CR_LIFESTEAL, 17)
        end,
    },
    avoidance = {
        label = "Avoid",
        color = { 0.35, 0.35, 0.35 },
        getPercent = function()
            return GetAvoidance and GetAvoidance() or 0
        end,
        getRating = function()
            return GetCombatRatingValue(CR_AVOIDANCE, 21)
        end,
    },
    speed = {
        label = "Speed",
        color = { 1.0, 0.95, 0.55 },
        getPercent = function()
            return GetCombatRatingBonusValue(CR_SPEED, 14)
        end,
        getRating = function()
            return GetCombatRatingValue(CR_SPEED, 14)
        end,
    },
    dodge = {
        label = "Dodge",
        color = { 0.2, 0.9, 0.3 },
        getPercent = function()
            return GetDodgeChance and GetDodgeChance() or 0
        end,
        getRating = function()
            return GetCombatRatingValue(CR_DODGE, 3)
        end,
    },
    parry = {
        label = "Parry",
        color = { 1.0, 0.2, 0.2 },
        getPercent = function()
            return GetParryChance and GetParryChance() or 0
        end,
        getRating = function()
            return GetCombatRatingValue(CR_PARRY, 4)
        end,
    },
}

local function FormatRemaining(seconds)
    if not seconds or seconds <= 0 then
        return ""
    end
    if seconds >= 60 then
        local mins = math.floor(seconds / 60)
        local secs = math.floor(seconds % 60)
        return string.format("%d:%02d", mins, secs)
    end
    if seconds >= 10 then
        return tostring(math.floor(seconds + 0.5))
    end
    return string.format("%.1f", seconds)
end

local function SaveOverlayPosition(frame, pathKey)
    if not frame then return end
    local centerX, centerY = frame:GetCenter()
    local parentX, parentY = UIParent:GetCenter()
    local o = EnsureTrinketOptions()
    local target = o
    if pathKey == "stats" then
        target = o.stats
    end

    if centerX and centerY and parentX and parentY then
        target.position = {
            point = "CENTER",
            relativePoint = "CENTER",
            x = math.floor((centerX - parentX) + 0.5),
            y = math.floor((centerY - parentY) + 0.5),
        }
    else
        target.position = {
            point = "CENTER",
            relativePoint = "CENTER",
            x = 0,
            y = 0,
        }
    end
end

local function ApplyOverlayPosition(frame, pathKey)
    if not frame then return end
    local o = EnsureTrinketOptions()
    local source = o
    if pathKey == "stats" then
        source = o.stats
    end
    local pos = source.position or {}
    frame:ClearAllPoints()
    frame:SetPoint(
        pos.point or "CENTER",
        UIParent,
        pos.relativePoint or "CENTER",
        pos.x or 0,
        pos.y or 0
    )
    frame:SetScale(tonumber(source.scale) or 1)
end


local function UpdateTrinketOverlayInteractivity()
    local o = EnsureTrinketOptions()
    if not TrinketOverlay then return end

    local unlocked = not o.locked
    TrinketOverlay:EnableMouse(unlocked)

    for _, btn in ipairs(TrinketOverlay.buttons or {}) do
        btn:EnableMouse(unlocked)
    end

end

local function UpdateStatsOverlayInteractivity()
    local s = EnsureTrinketOptions().stats
    if not StatsOverlay then return end

    local unlocked = not s.locked
    StatsOverlay:EnableMouse(unlocked)
end

local function CreateSlotButton(parent, index, slotId)
    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    btn:SetSize(42, 42)
    btn:SetPoint("TOP", parent, "TOP", 0, -((index - 1) * 48))
    btn:SetFrameStrata("HIGH")
    btn:SetFrameLevel(parent:GetFrameLevel() + 2)
    btn.slotId = slotId

    if btn.SetBackdrop then
        btn:SetBackdrop({
            bgFile = "Interface/ChatFrame/ChatFrameBackground",
            edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
            tile = true,
            tileSize = 16,
            edgeSize = 12,
            insets = { left = 3, right = 3, top = 3, bottom = 3 },
        })
        btn:SetBackdropColor(0, 0, 0, 0.65)
        btn:SetBackdropBorderColor(0.65, 0.4, 0.08, 0.95)
    end

    local icon = btn:CreateTexture(nil, "ARTWORK")
    icon:SetPoint("TOPLEFT", 4, -4)
    icon:SetPoint("BOTTOMRIGHT", -4, 4)
    icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
    btn.icon = icon

    local cooldown = CreateFrame("Cooldown", nil, btn, "CooldownFrameTemplate")
    cooldown:SetAllPoints(icon)
    btn.cooldown = cooldown

    local text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    text:SetPoint("CENTER", 0, -1)
    text:SetText("")
    btn.timerText = text

    btn:SetScript("OnEnter", function(self)
        local o = EnsureTrinketOptions()
        if o.locked then
            GameTooltip:Hide()
            return
        end
        GameTooltip:SetOwner(self, "ANCHOR_LEFT")
        GameTooltip:SetInventoryItem("player", self.slotId)
        GameTooltip:Show()
    end)

    btn:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    return btn
end

local function RefreshTrinketButton(btn)
    if not btn then return end

    local texture = GetInventoryItemTexture("player", btn.slotId)
    local itemLink = GetInventoryItemLink("player", btn.slotId)

    if not texture or not itemLink then
        btn.icon:SetTexture(nil)
        btn.timerText:SetText("")
        btn.cooldown:Clear()
        btn:Hide()
        btn.remaining = 0
        return
    end

    btn.icon:SetTexture(texture)
    btn:Show()

    local start, duration, enable = GetInventoryItemCooldown("player", btn.slotId)
    start = tonumber(start) or 0
    duration = tonumber(duration) or 0
    enable = tonumber(enable) or 0

    if enable == 1 and duration and duration > 1.5 and start and start > 0 then
        btn.cooldown:SetCooldown(start, duration)
        local remaining = math.max(0, (start + duration) - GetTime())
        btn.remaining = remaining
        btn.timerText:SetText(FormatRemaining(remaining))
    else
        btn.cooldown:Clear()
        btn.remaining = 0
        btn.timerText:SetText("")
    end
end

local function RefreshTrinketOverlay()
    local o = EnsureTrinketOptions()
    if not TrinketOverlay then return end

    if not TrinketOverlay.isDragging then
        ApplyOverlayPosition(TrinketOverlay)
    end

    if not o.enabled then
        TrinketOverlay:Hide()
        return
    end

    local shownCount = 0
    for _, btn in ipairs(TrinketOverlay.buttons or {}) do
        RefreshTrinketButton(btn)
        if btn:IsShown() then
            shownCount = shownCount + 1
        end
    end

    if shownCount > 0 then
        TrinketOverlay:Show()
    else
        TrinketOverlay:Hide()
    end

    UpdateTrinketOverlayInteractivity()

    if TrinketConfigHost and TrinketConfigHost.refreshState then
        TrinketConfigHost:refreshState()
    end
end

local function EnsureTrinketOverlay()
    EnsureTrinketOptions()

    if TrinketOverlay then
        RefreshTrinketOverlay()
        return TrinketOverlay
    end

    local holder = CreateFrame("Frame", "CowmeraTrinketOverlay", UIParent, "BackdropTemplate")
    holder:SetSize(50, 96)
    holder:SetMovable(true)
    holder:EnableMouse(true)
    holder:RegisterForDrag("LeftButton")
    holder:SetClampedToScreen(false)
    holder:SetFrameStrata("HIGH")
    holder:SetFrameLevel(900)

    if holder.SetBackdrop then
        holder:SetBackdrop({
            bgFile = "Interface/ChatFrame/ChatFrameBackground",
            edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
            tile = true,
            tileSize = 16,
            edgeSize = 12,
            insets = { left = 3, right = 3, top = 3, bottom = 3 },
        })
        holder:SetBackdropColor(0, 0, 0, 0.25)
        holder:SetBackdropBorderColor(0.65, 0.4, 0.08, 0.75)
    end

    holder.buttons = {
        CreateSlotButton(holder, 1, 13),
        CreateSlotButton(holder, 2, 14),
    }

    holder:SetScript("OnDragStart", function(self)
        local o = EnsureTrinketOptions()
        if o.locked then return end
        self.isDragging = true
        self:StartMoving()
    end)

    holder:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        self.isDragging = nil
        SaveOverlayPosition(self)
        RefreshTrinketOverlay()
    end)


    TrinketOverlay = holder
    ApplyOverlayPosition(holder)
    UpdateTrinketOverlayInteractivity()
    RefreshTrinketOverlay()
    return holder
end

local function SafePercent(func)
    if type(func) ~= "function" then return 0 end
    local ok, value = pcall(func)
    if ok and type(value) == "number" then
        return value
    end
    return 0
end

local function SafeRating(func)
    if type(func) ~= "function" then return 0 end
    local ok, value = pcall(func)
    if ok and type(value) == "number" then
        return value
    end
    return 0
end

local function SafeStatText(value, fmt, suffix)
    if value == nil then
        return "--"
    end

    if fmt then
        local ok, text = pcall(string.format, fmt, value)
        if ok and text then
            return text
        end
    end

    return tostring(value) .. (suffix or "")
end

local function FormatStatText(key)
    local o = EnsureTrinketOptions()
    local statsOpt = o.stats or {}
    local meta = STAT_META[key]
    if not meta then
        return "", 1, 1, 1
    end

    -- Avoid doing arithmetic on stat values here.
    -- Some WoW stat APIs can return secret/tainted number values; arithmetic
    -- such as +, -, *, /, math.floor, math.max, etc. can throw even when
    -- type(value) reports "number". Display the API values directly instead.
    local rating = SafeRating(meta.getRating)
    local percent = SafePercent(meta.getPercent)
    local mode = tostring(statsOpt.formatMode or "both")
    local percentText = SafeStatText(percent, "%.2f%%", "%")
    local ratingText = SafeStatText(rating)
    local body = percentText

    if mode == "rating" then
        body = ratingText
    elseif mode == "both" then
        body = ratingText .. " | " .. percentText
    end

    local r, g, b = unpack(meta.color or { 1, 1, 1 })
    return string.format("%s: %s", meta.label, body), r, g, b
end

local function RefreshStatsOverlay()
    local o = EnsureTrinketOptions()
    local s = o.stats
    if not StatsOverlay then return end


    if not s.enabled then
        StatsOverlay:Hide()
        return
    end

    local shownCount = 0

    -- Do not call FontString:GetStringWidth() here.
    -- On current WoW clients it can return a secret number when this addon is tainted,
    -- and comparing/rounding that value throws "attempt to compare ... secret number".
    -- Use a conservative fixed width instead; the stat text is short and predictable.
    local displayWidth = 170
    if tostring(s.formatMode or "both") == "both" then
        displayWidth = 210
    end

    for _, key in ipairs(STAT_ORDER) do
        local line = StatsOverlay.lines and StatsOverlay.lines[key]
        local enabled = s.shown and s.shown[key]
        if line then
            if enabled then
                local text, r, g, b = FormatStatText(key)
                line:SetText(text)
                line:SetTextColor(r, g, b, 1)
                line:Show()
                shownCount = shownCount + 1
            else
                line:SetText("")
                line:Hide()
            end
        end
    end

    if shownCount == 0 then
        StatsOverlay:Hide()
    else
        local displayHeight = 8 + (shownCount * 15)
        StatsOverlay:SetSize(displayWidth, displayHeight)
        StatsOverlay:Show()
    end

    UpdateStatsOverlayInteractivity()

    if TrinketConfigHost and TrinketConfigHost.refreshState then
        TrinketConfigHost:refreshState()
    end
end

local function EnsureStatsOverlay()
    EnsureTrinketOptions()

    if StatsOverlay then
        RefreshStatsOverlay()
        return StatsOverlay
    end

    local holder = CreateFrame("Frame", "CowmeraStatsOverlay", UIParent, "BackdropTemplate")
    holder:SetSize(130, 72)
    holder:SetMovable(true)
    holder:EnableMouse(true)
    holder:RegisterForDrag("LeftButton")
    holder:SetClampedToScreen(false)
    holder:SetFrameStrata("HIGH")
    holder:SetFrameLevel(901)

    if holder.SetBackdrop then
        holder:SetBackdrop({
            bgFile = "Interface/ChatFrame/ChatFrameBackground",
            edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
            tile = true,
            tileSize = 16,
            edgeSize = 12,
            insets = { left = 3, right = 3, top = 3, bottom = 3 },
        })
        holder:SetBackdropColor(0, 0, 0, 0.35)
        holder:SetBackdropBorderColor(0.65, 0.4, 0.08, 0.75)
    end

    holder.lines = {}
    local lastLine = nil
    for _, key in ipairs(STAT_ORDER) do
        local fs = holder:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        fs:SetJustifyH("LEFT")
        if not lastLine then
            fs:SetPoint("TOPLEFT", 8, -6)
        else
            fs:SetPoint("TOPLEFT", lastLine, "BOTTOMLEFT", 0, -2)
        end
        holder.lines[key] = fs
        lastLine = fs
    end

    holder:SetScript("OnDragStart", function(self)
        local s = EnsureTrinketOptions().stats
        if s.locked then return end
        self.isDragging = true
        self:StartMoving()
    end)

    holder:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        self.isDragging = nil
        SaveOverlayPosition(self, "stats")
        RefreshStatsOverlay()
    end)


    StatsOverlay = holder
    ApplyOverlayPosition(holder, "stats")
    UpdateStatsOverlayInteractivity()
    RefreshStatsOverlay()
    return holder
end

local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
eventFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
eventFrame:RegisterEvent("BAG_UPDATE_COOLDOWN")
eventFrame:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN")
eventFrame:RegisterEvent("SPELL_UPDATE_COOLDOWN")
eventFrame:RegisterEvent("COMBAT_RATING_UPDATE")
eventFrame:RegisterEvent("MASTERY_UPDATE")
eventFrame:RegisterEvent("UNIT_STATS")
eventFrame:RegisterEvent("PLAYER_DAMAGE_DONE_MODS")
eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
eventFrame:RegisterEvent("PLAYER_REGEN_DISABLED")

eventFrame:SetScript("OnEvent", function(_, event, unit)
    if event == "UNIT_STATS" and unit and unit ~= "player" then
        return
    end

    if event == "PLAYER_LOGIN" then
        EnsureTrinketOverlay()
        EnsureStatsOverlay()
    else
        RefreshTrinketOverlay()
        RefreshStatsOverlay()
    end
end)

local function BuildCheckbox(parent, label, tooltip, onClick)
    local cb = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
    cb.Text:SetText(label or "")
    cb.Text:SetTextColor(1, 0.82, 0, 1)

    cb:SetScript("OnClick", function(self)
        if onClick then
            onClick(self:GetChecked() and true or false)
        end
    end)

    if tooltip and tooltip ~= "" then
        cb:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText(label or "Trinkets")
            GameTooltip:AddLine(tooltip, 1, 1, 1, true)
            GameTooltip:Show()
        end)
        cb:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)
    end

    return cb
end

local function BuildActionButton(parent, w, h, text, x, y, onClick)
    local btn = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
    btn:SetSize(w, h)
    btn:SetPoint("TOPLEFT", x, y)
    btn:SetText(text or "Button")
    btn:SetScript("OnClick", function()
        if onClick then onClick() end
    end)
    return btn
end

local function BuildTrinketPanel(host)
    if not host or host._cowmeraTrinketBuilt then
        return
    end
    host._cowmeraTrinketBuilt = true
    TrinketConfigHost = host

    local title = host:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    title:SetPoint("TOP", 0, -18)
    title:SetText("Trinket + Stats Overlay")

    local subtitle = host:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    subtitle:SetPoint("TOP", title, "BOTTOM", 0, -10)
    subtitle:SetWidth(620)
    subtitle:SetJustifyH("CENTER")
    subtitle:SetText("Trinket cooldowns and a separate movable stats overlay live here in the same settings tab.")

    local trinketHeader = host:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    trinketHeader:SetPoint("TOPLEFT", 22, -62)
    trinketHeader:SetText("Trinket Overlay")

    local cbEnable = BuildCheckbox(host, "Enable trinket overlay", "Shows the trinket cooldown overlay.", function(v)
        EnsureTrinketOptions().enabled = v
        RefreshTrinketOverlay()
    end)
    cbEnable:SetPoint("TOPLEFT", 22, -84)

    local cbLock = BuildCheckbox(host, "Lock trinket overlay position", "Unlock this to drag the trinket overlay.", function(v)
        EnsureTrinketOptions().locked = v
        RefreshTrinketOverlay()
    end)
    cbLock:SetPoint("TOPLEFT", cbEnable, "BOTTOMLEFT", 0, -6)

    BuildActionButton(host, 150, 24, "Reset Trinkets", 22, -142, function()
        local o = EnsureTrinketOptions()
        o.position = {
            point = "CENTER",
            relativePoint = "CENTER",
            x = 520,
            y = -120,
        }
        RefreshTrinketOverlay()
        TrinketPrint("Trinket overlay reset.")
    end)

    BuildActionButton(host, 90, 24, "Refresh", 180, -142, function()
        RefreshTrinketOverlay()
        RefreshStatsOverlay()
    end)

    local statsHeader = host:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    statsHeader:SetPoint("TOPLEFT", 360, -62)
    statsHeader:SetText("Stats Overlay")

    local cbStatsEnable = BuildCheckbox(host, "Enable stats overlay", "Shows your stats in a separate movable overlay.", function(v)
        EnsureTrinketOptions().stats.enabled = v
        RefreshStatsOverlay()
    end)
    cbStatsEnable:SetPoint("TOPLEFT", 360, -84)

    local cbStatsLock = BuildCheckbox(host, "Lock stats overlay position", "Unlock this to drag the stats overlay.", function(v)
        EnsureTrinketOptions().stats.locked = v
        RefreshStatsOverlay()
    end)
    cbStatsLock:SetPoint("TOPLEFT", cbStatsEnable, "BOTTOMLEFT", 0, -6)

    BuildActionButton(host, 150, 24, "Reset Stats Overlay", 360, -142, function()
        local s = EnsureTrinketOptions().stats
        s.position = {
            point = "CENTER",
            relativePoint = "CENTER",
            x = 430,
            y = -120,
        }
        RefreshStatsOverlay()
        TrinketPrint("Stats overlay reset.")
    end)

    local modeLabel = host:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    modeLabel:SetPoint("TOPLEFT", 360, -178)
    modeLabel:SetText("Display mode")

    local modePercent = BuildCheckbox(host, "Percent", "Show only percentage values.", function(v)
        if v then
            EnsureTrinketOptions().stats.formatMode = "percent"
            if host.refreshState then host:refreshState() end
            RefreshStatsOverlay()
        elseif EnsureTrinketOptions().stats.formatMode == "percent" then
            EnsureTrinketOptions().stats.formatMode = "both"
            if host.refreshState then host:refreshState() end
            RefreshStatsOverlay()
        end
    end)
    modePercent:SetPoint("TOPLEFT", 360, -198)

    local modeRating = BuildCheckbox(host, "Whole number", "Show only the rating value.", function(v)
        if v then
            EnsureTrinketOptions().stats.formatMode = "rating"
            if host.refreshState then host:refreshState() end
            RefreshStatsOverlay()
        elseif EnsureTrinketOptions().stats.formatMode == "rating" then
            EnsureTrinketOptions().stats.formatMode = "both"
            if host.refreshState then host:refreshState() end
            RefreshStatsOverlay()
        end
    end)
    modeRating:SetPoint("TOPLEFT", 360, -220)

    local modeBoth = BuildCheckbox(host, "Both", "Show both rating and percentage.", function(v)
        if v then
            EnsureTrinketOptions().stats.formatMode = "both"
            if host.refreshState then host:refreshState() end
            RefreshStatsOverlay()
        elseif EnsureTrinketOptions().stats.formatMode == "both" then
            EnsureTrinketOptions().stats.formatMode = "percent"
            if host.refreshState then host:refreshState() end
            RefreshStatsOverlay()
        end
    end)
    modeBoth:SetPoint("TOPLEFT", 360, -242)

    local statsToggleLabel = host:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    statsToggleLabel:SetPoint("TOPLEFT", 22, -192)
    statsToggleLabel:SetText("Stats shown")

    local statCheckboxes = {}
    local function addStatToggle(key, label, tooltip, x, y)
        local cb = BuildCheckbox(host, label, tooltip, function(v)
            EnsureTrinketOptions().stats.shown[key] = v
            RefreshStatsOverlay()
        end)
        cb:SetPoint("TOPLEFT", x, y)
        statCheckboxes[key] = cb
    end

    addStatToggle("crit", "Crit", "Critical Strike.", 22, -214)
    addStatToggle("haste", "Haste", "Haste.", 22, -236)
    addStatToggle("mastery", "Mastery", "Mastery.", 22, -258)
    addStatToggle("versatility", "Versatility", "Versatility.", 22, -280)
    addStatToggle("leech", "Leech", "Leech.", 160, -214)
    addStatToggle("avoidance", "Avoidance", "Avoidance.", 160, -236)
    addStatToggle("speed", "Speed", "Speed.", 160, -258)
    addStatToggle("dodge", "Dodge", "Dodge.", 160, -280)
    addStatToggle("parry", "Parry", "Parry.", 160, -302)

    local status = host:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
    status:SetPoint("TOPLEFT", 22, -350)
    status:SetWidth(620)
    status:SetJustifyH("LEFT")
    status:SetJustifyV("TOP")

    local help = host:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    help:SetPoint("TOPLEFT", status, "BOTTOMLEFT", 0, -10)
    help:SetWidth(620)
    help:SetJustifyH("LEFT")
    help:SetText("When unlocked, drag each overlay itself to move it. Trinkets and stats keep separate snap points.")

    host.refreshState = function()
        local o = EnsureTrinketOptions()
        local s = o.stats or {}
        local sp = s.position or {}
        local tp = o.position or {}

        cbEnable:SetChecked(o.enabled)
        cbLock:SetChecked(o.locked)
        cbStatsEnable:SetChecked(s.enabled)
        cbStatsLock:SetChecked(s.locked)

        modePercent:SetChecked((s.formatMode or "both") == "percent")
        modeRating:SetChecked((s.formatMode or "both") == "rating")
        modeBoth:SetChecked((s.formatMode or "both") == "both")

        for key, cb in pairs(statCheckboxes) do
            cb:SetChecked(s.shown and s.shown[key] and true or false)
        end

        status:SetText(string.format(
            "Trinkets: %s  |  Anchor %s / %s  |  Offset %d, %d\nStats: %s  |  Anchor %s / %s  |  Offset %d, %d  |  Mode: %s",
            o.enabled and "|cff22c55eEnabled|r" or "|cffff4444Disabled|r",
            tostring(tp.point or "CENTER"),
            tostring(tp.relativePoint or "CENTER"),
            tonumber(tp.x) or 0,
            tonumber(tp.y) or 0,
            s.enabled and "|cff22c55eEnabled|r" or "|cffff4444Disabled|r",
            tostring(sp.point or "CENTER"),
            tostring(sp.relativePoint or "CENTER"),
            tonumber(sp.x) or 0,
            tonumber(sp.y) or 0,
            tostring(s.formatMode or "both")
        ))
    end

    host:refreshState()
end

_G.Cowmera = _G.Cowmera or {}
_G.Cowmera.BuildTrinketPanel = function(host)
    EnsureTrinketOverlay()
    EnsureStatsOverlay()
    BuildTrinketPanel(host)
end

_G.Cowmera.RefreshTrinketOverlay = RefreshTrinketOverlay
_G.Cowmera.EnsureTrinketOverlay = EnsureTrinketOverlay
_G.Cowmera.RefreshStatsOverlay = RefreshStatsOverlay
_G.Cowmera.EnsureStatsOverlay = EnsureStatsOverlay
