local CritMaticGoldYellow = "|cffffd700"
local CritMaticGold = "|cffed9d09"
local CritMaticWhite = "|cffe8e7e3"
local CritMaticGray = "|cffc2bfb6"
local CritMaticRed = "|cffd41313"
local CritMaticBrown = "|cffada27f"

local function CritMaticColorEscape(colorTable)
    if type(colorTable) ~= "table" then
        return "|cffffffff"
    end
    local r = math.floor((colorTable[1] or 1) * 255 + 0.5)
    local g = math.floor((colorTable[2] or 1) * 255 + 0.5)
    local b = math.floor((colorTable[3] or 1) * 255 + 0.5)
    return string.format("|cff%02x%02x%02x", r, g, b)
end

-- Retail (Midnight 12.0+) removed the global GetSpellInfo. Restore the
-- classic multi-value signature on top of C_Spell.GetSpellInfo so the rest
-- of this file (which targets every flavor) keeps working unchanged.
if not _G.GetSpellInfo and _G.C_Spell and _G.C_Spell.GetSpellInfo then
    _G.GetSpellInfo = function(spellID)
        if not spellID then return nil end
        local info = _G.C_Spell.GetSpellInfo(spellID)
        if not info then return nil end
        return info.name, nil, info.iconID, info.castTime, info.minRange, info.maxRange, info.originalIconID or info.spellID
    end
end

Critmatic = LibStub("AceAddon-3.0"):NewAddon(CritMaticGold .. "CritMatic|r", "AceConsole-3.0", "AceTimer-3.0",
        "AceEvent-3.0",
        "AceComm-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("CritMatic")

local MAX_HIT = 1e9

local spellAggregateCache = {}
local aggregateDirty = true

local function InvalidateAggregateCache()
    aggregateDirty = true
end

local function ShouldAnnounceRecord(oldAmount, newAmount)
    if not Critmatic.db then return true end
    local pct = Critmatic.db.profile.generalSettings.recordAnnounceThreshold or 0
    if pct <= 0 then return true end
    if not oldAmount or oldAmount <= 0 or not newAmount then return true end
    return newAmount >= oldAmount * (1 + pct / 100)
end

local function BindRecordsTable()
    local rdb = Critmatic.recordsDB
    if not rdb then return end
    local p = rdb.profile
    if type(rawget(p, "crits")) ~= "table" then
        rawset(p, "crits", {})
    end
    _G.CritMaticData = p.crits
end

local RECORD_MERGE_FIELDS = { "highestCrit", "highestNormal", "highestHealCrit", "highestHeal" }
local function MergeRecords(dest, src)
    if type(dest) ~= "table" or type(src) ~= "table" then return end
    for spellID, data in pairs(src) do
        if type(data) == "table" then
            local d = dest[spellID]
            if d == nil then
                dest[spellID] = CopyTable(data)
            else
                for i = 1, #RECORD_MERGE_FIELDS do
                    local f = RECORD_MERGE_FIELDS[i]
                    if (data[f] or 0) > (d[f] or 0) then
                        d[f] = data[f]
                    end
                end
                if not d.spellIcon and data.spellIcon then
                    d.spellIcon = data.spellIcon
                end
            end
        end
    end
end

local function GetRecordName(key, record)
    if record and record.displayName then
        return record.displayName
    end
    if type(key) == "number" then
        return GetSpellInfo(key)
    end
    return nil
end

local function GetRecordIcon(key, record)
    if record and record.spellIcon then
        return record.spellIcon
    end
    if type(key) == "number" then
        return select(3, GetSpellInfo(key))
    end
    return nil
end

Critmatic.GetRecordName = GetRecordName
Critmatic.GetRecordIcon = GetRecordIcon

local function PetRecordKeyFromGUID(guid)
    if not guid then return nil end
    local id = select(6, strsplit("-", guid))
    id = tonumber(id)
    if not id then return nil end
    return "petauto:" .. id
end

local function GetPetAttackIcon()
    if not (GetPetActionInfo and IsPetAttackAction) then return nil end
    local numSlots = _G.NUM_PET_ACTION_SLOTS or 10
    for i = 1, numSlots do
        if IsPetAttackAction(i) then
            local _, texture, isToken = GetPetActionInfo(i)
            if isToken and type(texture) == "string" then
                texture = _G[texture] or texture
            end
            return texture
        end
    end
    return nil
end

local function GetSpellAggregate(localizedSpellName)
    if aggregateDirty then
        wipe(spellAggregateCache)
        for sID, data in pairs(CritMaticData) do
            local name = GetRecordName(sID, data)
            if name then
                if not spellAggregateCache[name] then
                    spellAggregateCache[name] = {
                        highestCrit = data.highestCrit or 0,
                        highestNormal = data.highestNormal or 0,
                        highestHealCrit = data.highestHealCrit or 0,
                        highestHeal = data.highestHeal or 0,
                    }
                else
                    local c = spellAggregateCache[name]
                    c.highestCrit = math.max(c.highestCrit, data.highestCrit or 0)
                    c.highestNormal = math.max(c.highestNormal, data.highestNormal or 0)
                    c.highestHealCrit = math.max(c.highestHealCrit, data.highestHealCrit or 0)
                    c.highestHeal = math.max(c.highestHeal, data.highestHeal or 0)
                end
            end
        end
        aggregateDirty = false
    end
    return spellAggregateCache[localizedSpellName]
end

local function _GetSpellCooldown(spellID)
    if C_Spell and C_Spell.GetSpellCooldown then
        local info = C_Spell.GetSpellCooldown(spellID)
        if info then
            return info.startTime, info.duration, info.isEnabled
        end
    end
    if _G.GetSpellCooldown then
        return _G.GetSpellCooldown(spellID)
    end
    return 0, 0, false
end

local function GetGCD()
    local _, gcdDuration = _GetSpellCooldown(78) -- 78 is the spell ID for Warrior's Heroic Strike
    if not gcdDuration or gcdDuration == 0 then
        return 1.5 -- Default GCD duration if not available
    end
    return gcdDuration
end

local function RenderHighestHitsForSpellID(self, recordKey)
    local localizedSpellName = GetRecordName(recordKey, CritMaticData and CritMaticData[recordKey])
    if not localizedSpellName then
        return
    end

    local agg = GetSpellAggregate(localizedSpellName)
    if not agg then
        return
    end
    local highestCrit = agg.highestCrit
    local highestNormal = agg.highestNormal
    local highestHealCrit = agg.highestHealCrit
    local highestHeal = agg.highestHeal

    if highestCrit == 0 and highestNormal == 0 and highestHealCrit == 0 and highestHeal == 0 then
        return
    end

    local cooldown, castTime = 0, nil
    if type(recordKey) == "number" then
        cooldown = (GetSpellBaseCooldown(recordKey) or 0) / 1000
        castTime = select(4, GetSpellInfo(recordKey))
    end
    local effectiveCastTime = (castTime and castTime > 0) and (castTime / 1000) or GetGCD()
    local effectiveTime = max(effectiveCastTime, cooldown)

    local critDPS = highestCrit / effectiveTime
    local normalDPS = highestNormal / effectiveTime
    local critHPS = highestHealCrit / effectiveTime
    local normalHPS = highestHeal / effectiveTime

    local CritMaticLeft = L["action_bar_crit"] .. ": "
    local DPS = L["action_bar_dps"] .. ") "
    local CritMaticRight = tostring(highestCrit) .. " (" .. format("%.1f", critDPS) .. DPS
    local normalMaticLeft = L["action_bar_hit"] .. ": "
    local normalMaticRight = tostring(highestNormal) .. " (" .. format("%.1f", normalDPS) .. DPS

    local CritMaticHealLeft = L["action_bar_crit_heal"] .. ": "
    local HPS = L["action_bar_hps"] .. ") "
    local CritMaticHealRight = tostring(highestHealCrit) .. " (" .. format("%.1f", critHPS) .. HPS
    local normalMaticHealLeft = L["action_bar_heal"] .. ": "
    local normalMaticHealRight = tostring(highestHeal) .. " (" .. format("%.1f", normalHPS) .. HPS

    local critMaticHealExists = false
    local normalMaticHealExists = false
    local critMaticExists = false
    local normalMaticExists = false

    for i = 1, self:NumLines() do
        local gtl = _G["GameTooltipTextLeft" .. i]
        local gtr = _G["GameTooltipTextRight" .. i]

        if gtl and gtr then
            if gtl:GetText() == CritMaticHealLeft and gtr:GetText() == CritMaticHealRight then
                critMaticHealExists = true
            elseif gtl:GetText() == normalMaticHealLeft and gtr:GetText() == normalMaticHealRight then
                normalMaticHealExists = true
            end
            if gtl:GetText() == CritMaticLeft and gtr:GetText() == CritMaticRight then
                critMaticExists = true
            elseif gtl:GetText() == normalMaticLeft and gtr:GetText() == normalMaticRight then
                normalMaticExists = true
            end
        end
    end

    local addedLine = false

    if highestCrit > 0 and not critMaticExists then
        self:AddDoubleLine(CritMaticLeft, CritMaticRight, 0.9, 0.9, 0.9, 0.9, 0.82, 0)
        addedLine = true
    end

    if highestNormal > 0 and not normalMaticExists then
        self:AddDoubleLine(normalMaticLeft, normalMaticRight, 0.9, 0.9, 0.9, 0.9, 0.82, 0)
        addedLine = true
    end

    if highestHealCrit > 0 and not critMaticHealExists then
        self:AddDoubleLine(CritMaticHealLeft, CritMaticHealRight, 0.9, 0.9, 0.9, 0.9, 0.82, 0)
        addedLine = true
    end

    if highestHeal > 0 and not normalMaticHealExists then
        self:AddDoubleLine(normalMaticHealLeft, normalMaticHealRight, 0.9, 0.9, 0.9, 0.9, 0.82, 0)
        addedLine = true
    end

    if addedLine then
        self:Show()
    end
end

local function AddHighestHitsToTooltip(self, slot, isSpellBook)
    if (not slot) then
        return
    end
    local actionType, id, spellID

    if isSpellBook then
        if _G.GetSpellBookItemInfo then
            local itemType, itemID = _G.GetSpellBookItemInfo(slot, BOOKTYPE_SPELL)
            if itemType == "SPELL" or itemType == "FUTURESPELL" then
                spellID = itemID
            end
        elseif C_SpellBook and C_SpellBook.GetSpellBookItemInfo then
            local bank = Enum and Enum.SpellBookSpellBank and Enum.SpellBookSpellBank.Player
            local info = C_SpellBook.GetSpellBookItemInfo(slot, bank)
            if info and info.spellID then
                spellID = info.spellID
            end
        else
            spellID = select(3, GetSpellBookItemName(slot, BOOKTYPE_SPELL))
        end
        actionType = "spell"
    else
        actionType, id = GetActionInfo(slot)
        if actionType == "spell" then
            spellID = id
        elseif actionType == "macro" and id then
            if _G.GetMacroSpell then
                spellID = _G.GetMacroSpell(id)
            end
        end
    end

    if not spellID then
        return
    end

    RenderHighestHitsForSpellID(self, spellID)
end

local function AddHighestHitsToPetTooltip(self, slot)
    if not slot then
        return
    end
    if not GetPetActionInfo then
        return
    end
    local spellID = select(7, GetPetActionInfo(slot))
    if spellID then
        RenderHighestHitsForSpellID(self, spellID)
        return
    end
    if IsPetAttackAction and IsPetAttackAction(slot) then
        local key = PetRecordKeyFromGUID(UnitGUID("pet"))
        if key then
            local petType = (UnitCreatureFamily and UnitCreatureFamily("pet")) or (UnitName and UnitName("pet"))
            local tname = self.GetName and self:GetName()
            local line1 = tname and _G[tname .. "TextLeft1"]
            if petType and line1 then
                local cur = line1:GetText()
                if cur and cur ~= "" and not cur:find(petType, 1, true) then
                    line1:SetText(petType .. " " .. cur)
                end
            end
            RenderHighestHitsForSpellID(self, key)
        end
    end
end
-- Function to create a new frame based on the template
Critmatic.CreateNewMessageFrame = function()
    local f = CreateFrame("Frame", nil, UIParent)
    f:SetSize(1000, 30)
    
    -- Get position settings from database
    local xPos = Critmatic.db.profile.alertNotificationFormat.position.xPos
    local yPos = Critmatic.db.profile.alertNotificationFormat.position.yPos
    
    -- Convert percentages to pixel offsets
    local screenWidth = UIParent:GetWidth()
    local screenHeight = UIParent:GetHeight()
    local xOffset = (xPos - 50) * screenWidth / 100
    local yOffset = (yPos - 50) * screenHeight / 100
    
    f:SetPoint("CENTER", UIParent, "CENTER", xOffset, yOffset)

    f.text = f:CreateFontString(nil, "ARTWORK", "GameFontNormalHuge")
    f.text:SetAllPoints()

    local bounceStyle = Critmatic.db.profile.alertNotificationFormat.global.bounceStyle or "legacy"
    local bouncePeak, bounceUp, bouncePause, bounceDown
    if bounceStyle == "mop" then
        bouncePeak, bounceUp, bouncePause, bounceDown = 1.5, 0.06, 0, 0.10
    else
        bouncePeak, bounceUp, bouncePause, bounceDown = 1.5, 0.15, 0.12, 0.15
    end

    f.bounce = f:CreateAnimationGroup()
    local scaleUp = f.bounce:CreateAnimation("Scale")
    scaleUp:SetScale(bouncePeak, bouncePeak)
    scaleUp:SetDuration(bounceUp)
    scaleUp:SetOrder(1)
    local bounceOrder = 2
    if bouncePause > 0 then
        local pause = f.bounce:CreateAnimation("Pause")
        pause:SetDuration(bouncePause)
        pause:SetOrder(bounceOrder)
        bounceOrder = bounceOrder + 1
    end
    local scaleDown = f.bounce:CreateAnimation("Scale")
    scaleDown:SetScale(1 / bouncePeak, 1 / bouncePeak)
    scaleDown:SetDuration(bounceDown)
    scaleDown:SetOrder(bounceOrder)
    local LSM = LibStub("LibSharedMedia-3.0")
    local fontPath = LSM:Fetch("font", Critmatic.db.profile.fontSettings.font)
    f.text:SetFont(fontPath, Critmatic.db.profile.fontSettings.fontSize, Critmatic.db.profile.fontSettings.fontOutline)
    local LMAShadow = LibStub("LibMatic-AlertShadow-1.0", true)
    local fset = Critmatic.db.profile.fontSettings
    if LMAShadow then
        LMAShadow:ApplyTextShadow(f.text, fset.shadowStrength, { color = fset.fontShadowColor, offset = fset.fontShadowSize })
    else
        f.text:SetShadowOffset(unpack(fset.fontShadowSize))
        f.text:SetShadowColor(unpack(fset.fontShadowColor))
    end

    return f
end

function Critmatic:OnInitialize()
    -- Initialization code here.
    self.db = LibStub("AceDB-3.0"):New("CritMaticDB", defaults, true)
    self.recordsDB = LibStub("AceDB-3.0"):New("CritMaticRecordsDB", { profile = { crits = {} } })

    if _G.CritMaticDB14 and _G.CritMaticDB14.profile and not _G.CritMaticDB._cm_migrated then
        self.db.profiles["Default"] = CopyTable(_G.CritMaticDB14.profile)
        _G.CritMaticDB._cm_migrated = true
    end

    local legacyPerCharRecords = _G.CritMaticData
    BindRecordsTable()

    if not self.db.char._recordsSplit then
        local target = _G.CritMaticData
        MergeRecords(target, self.db.profile.crits)
        if type(legacyPerCharRecords) == "table" and legacyPerCharRecords ~= target then
            MergeRecords(target, legacyPerCharRecords)
        end
        self.db.char._recordsSplit = true
    end

    if not self.recordsDB.global._importedSettingsProfiles then
        for profName, profData in pairs(self.db.profiles) do
            if type(profData) == "table" and type(profData.crits) == "table" and next(profData.crits) then
                local destProfile = self.recordsDB.profiles[profName]
                if type(destProfile) ~= "table" then
                    destProfile = {}
                    self.recordsDB.profiles[profName] = destProfile
                end
                if type(destProfile.crits) ~= "table" then
                    destProfile.crits = {}
                end
                MergeRecords(destProfile.crits, profData.crits)
            end
        end
        self.recordsDB.global._importedSettingsProfiles = true
    end

    local opts = Critmatic:CreateOptionsTable()
    local Settings = LibStub("LibMatic-Settings-1.0", true)
    if Settings then
        opts.args.profiles_tab = Settings:BuildProfilesPage({ db = self.db, name = L["settings_profiles_tab"], order = 90 })
        opts.args.record_profiles_tab = Settings:BuildProfilesPage({ db = self.recordsDB, name = L["record_profiles_tab"], order = 91 })
    end
    LibStub("AceConfig-3.0"):RegisterOptionsTable("CritMaticOptions", opts)

    self.db.RegisterCallback(self, "OnProfileChanged", "OnProfileChanged")
    self.db.RegisterCallback(self, "OnProfileCopied", "OnProfileChanged")
    self.db.RegisterCallback(self, "OnProfileReset", "OnProfileChanged")

    self.recordsDB.RegisterCallback(self, "OnProfileChanged", "OnRecordsProfileChanged")
    self.recordsDB.RegisterCallback(self, "OnProfileCopied", "OnRecordsProfileChanged")
    self.recordsDB.RegisterCallback(self, "OnProfileReset", "OnRecordsProfileChanged")

    local Broadcast = LibStub("LibMatic-Broadcast-1.0", true)
    if Broadcast then
        Broadcast:Register({
            name = "CritMatic",
            commPrefix = "CRITMATICBC",
            electionEnabled = false,
        })
    end

    -- Some Classic/TBC clients may not expose GetAddOnMetadata as a global.
    -- Newer clients provide addon metadata via C_AddOns.
    local function _GetAddOnMetadata(addonName, field)
        if _G.GetAddOnMetadata then
            return _G.GetAddOnMetadata(addonName, field)
        end
        if _G.C_AddOns and _G.C_AddOns.GetAddOnMetadata then
            return _G.C_AddOns.GetAddOnMetadata(addonName, field)
        end
        return nil
    end

    local version = _GetAddOnMetadata("CritMatic", "Version") or "0"

    -- Some Classic/TBC clients may not expose IsAddOnLoaded as a global.
    -- Use the C_AddOns API when available, otherwise fall back to a safe default.
    local function _IsAddOnLoaded(addonName)
        if _G.IsAddOnLoaded then
            return _G.IsAddOnLoaded(addonName)
        end
        if _G.C_AddOns and _G.C_AddOns.IsAddOnLoaded then
            return _G.C_AddOns.IsAddOnLoaded(addonName)
        end
        return false
    end

    local function compareVersions(version1, version2)
        local function splitVersion(str)
            local parts = {}
            for part in string.gmatch(str, "[%d]+") do
                table.insert(parts, tonumber(part))
            end
            return parts
        end

        local parts1 = splitVersion(version1)
        local parts2 = splitVersion(version2)

        for i = 1, math.max(#parts1, #parts2) do
            local num1 = parts1[i] or 0
            local num2 = parts2[i] or 0
            if num1 > num2 then
                return true -- version1 is newer
            elseif num1 < num2 then
                return false -- version2 is newer
            end
        end

        return false
    end

    function Critmatic:OnCommReceived(prefix, message, distribution, sender)
        if message and version then
            local isNewerVersion = compareVersions(tostring(message), tostring(version))
            if isNewerVersion and not Critmatic.hasDisplayedUpdateMessage then
                Critmatic:Print(CritMaticRed .. L["new_version_notification"] .. "|r" .. CritMaticGray .. L["new_version_notification_part"] .. "|r")
                Critmatic.hasDisplayedUpdateMessage = true
            end
        end
    end

    --if self.db.profile.isCritLogFrameShown then
    toggleCritMaticCritLog() -- This will show the frame if it was shown before
    --end

    Critmatic.oldVersion = Critmatic.db.profile.oldVersion
    Critmatic.newVersion = tostring(version)
    if Critmatic.newVersion and Critmatic.oldVersion then

        local isNewerVersion = compareVersions(Critmatic.newVersion, Critmatic.oldVersion)
        if isNewerVersion then
            if Critmatic.db.profile.generalSettings.isChangeLogAutoPopUpEnabled then
                Critmatic.showChangeLog()
            end

            Critmatic.db.profile.oldVersion = Critmatic.newVersion

        end

    end
    -- Function to broadcast your version
    function Critmatic:BroadcastVersion()
        if IsInGuild() then
            self:SendCommMessage("Critmatic", version, "GUILD")
        end

        local inInstance, instanceType = IsInInstance()
        if inInstance and (instanceType == "pvp" or instanceType == "arena") then
            return
        end

        if IsInGroup() then
            if IsPartyLFG and IsPartyLFG() then
                self:SendCommMessage("Critmatic", version, "INSTANCE_CHAT")
            elseif IsInRaid() then
                self:SendCommMessage("Critmatic", version, "RAID")
            else
                self:SendCommMessage("Critmatic", version, "PARTY")
            end
        end
    end

    -- GROUP_ROSTER_UPDATE fires frequently; coalesce broadcasts within a short window.
    function Critmatic:GROUP_ROSTER_UPDATE()
        if self._versionBroadcastPending then
            return
        end
        self._versionBroadcastPending = true
        self:ScheduleTimer(function()
            self._versionBroadcastPending = false
            self:BroadcastVersion()
        end, 5)
    end

    -- Register the slash commands
    Critmatic:RegisterChatCommand(L["slash_cmlog"], "OpenChangeLog")
    Critmatic:RegisterChatCommand(L["slash_cmcritlog"], function()
        toggleCritMaticCritLog()
    end)

    local function capitalizeFirstLetterOfEachWord(str)
        return (str:gsub("(%a)([%w_']*)", function(first, rest)
            return first:upper() .. rest:lower()
        end))
    end
    -- Function to handle the slash command input
    local function resetSingleSpellDataSlashCommand(input)
        if not input or input:trim() == "" then
            Critmatic:Print(CritMaticRed .. "Please provide a spell name!" .. "|r")
            return
        end

        local capitalizedInput = capitalizeFirstLetterOfEachWord(input)
        local inputSpellName = capitalizedInput:lower() -- Standardize for comparison

        local spellFound = false
        for spellID, data in pairs(CritMaticData) do
            local spellName = GetRecordName(spellID, data)
            if spellName and spellName:lower() == inputSpellName then
                -- Reset data for the found spell
                CritMaticData[spellID] = nil -- or reset to initial state if needed
                spellFound = true
                Critmatic:Print(CritMaticGoldYellow .. capitalizedInput .. "|r" .. CritMaticRed .. " data has been reset." .. "|r")
                RedrawCritMaticWidget()
                break
            end
        end

        if not spellFound then
            Critmatic:Print(CritMaticGoldYellow .. capitalizedInput .. "|r" .. CritMaticRed .. " is not a tracked spell currently" .. "|r")
        end

    end

    local function ignoredSpellSlashCommand(input)
        if not input or input:trim() == "" then
            Critmatic:Print(CritMaticRed .. "Please provide a spell name!" .. "|r")
            return
        end

        local capitalizedInput = capitalizeFirstLetterOfEachWord(input)
        local inputSpellName = capitalizedInput:lower() -- Standardize for comparison

        local spellFound = false
        for spellID, data in pairs(CritMaticData) do
            local spellName = GetRecordName(spellID, data)
            if spellName and spellName:lower() == inputSpellName then
                spellFound = true
                break
            end
        end

        if not spellFound then
            Critmatic:Print(CritMaticRed .. capitalizedInput .. "|r" .. CritMaticWhite .. " is not a tracked spell currently" .. "|r")
            return
        end

        -- Add the spell name to the ignoredSpells table
        Critmatic.ignoredSpells[inputSpellName] = true
        Critmatic:Print(CritMaticGoldYellow .. capitalizedInput .. "|r" .. CritMaticWhite .. " added to " .. CritMaticRed .. "ignored" .. "|r " .. CritMaticWhite .. "spells." .. "|r")
        RedrawCritMaticWidget()
    end

    local function ListIgnoredSpells()
        if not Critmatic.ignoredSpells or next(Critmatic.ignoredSpells) == nil then
            Critmatic:Print(CritMaticRed .. "No spells are currently being ignored." .. "|r")
            return
        end

        Critmatic:Print(CritMaticRed .. "Ignored" .. "|r " .. "Spells:")
        for spellName, _ in pairs(Critmatic.ignoredSpells) do
            Critmatic:Print(CritMaticGoldYellow .. "- " .. capitalizeFirstLetterOfEachWord(spellName) .. "|r")
        end
    end

    local function RemoveIgnoredSpell(input)
        if not input or input:trim() == "" then
            Critmatic:Print(CritMaticRed .. "Please provide a spell name." .. "|r")
            return
        end

        local spellName = input:lower()  -- assuming spell names are stored in lowercase
        if Critmatic.ignoredSpells and Critmatic.ignoredSpells[spellName] then
            Critmatic.ignoredSpells[spellName] = nil
            Critmatic:Print(CritMaticGoldYellow .. capitalizeFirstLetterOfEachWord(spellName) .. "|r" .. CritMaticRed .. " has been removed from ignored spells." .. "|r")
            RedrawCritMaticWidget()
        else
            Critmatic:Print(CritMaticRed .. "Spell not found in ignored spells." .. "|r")
        end
    end
    -- Function to wipe all ignored spells
    local function WipeIgnoredSpells()
        if not Critmatic.ignoredSpells or next(Critmatic.ignoredSpells) == nil then
            Critmatic:Print(CritMaticRed .. "The ignored spells list is already empty!" .. "|r")
            return
        end

        wipe(Critmatic.ignoredSpells)  -- Clear the table
        Critmatic:Print(CritMaticRed .. "All ignored spells have been removed." .. "|r")
        RedrawCritMaticWidget()
    end
    local function ignoredTargetSlashCommand(input)
        local inputTargetID = tonumber(input)
        if not input or not inputTargetID  then
            Critmatic:Print(CritMaticRed .. "Please provide a target id!" .. "|r")
            return
        end

        -- Add the target id to the ignoredTargets table
        Critmatic.ignoredTargets[inputTargetID] = true
        Critmatic:Print(CritMaticGoldYellow .. input .. "|r" .. CritMaticWhite .. " added to " .. CritMaticRed .. "ignored" .. "|r " .. CritMaticWhite .. "targets." .. "|r")
        RedrawCritMaticWidget()
    end

    local function ListIgnoredTargets()
        if not Critmatic.ignoredTargets or next(Critmatic.ignoredTargets) == nil then
            Critmatic:Print(CritMaticRed .. "No targets are currently being ignored." .. "|r")
            return
        end

        Critmatic:Print(CritMaticRed .. "Ignored" .. "|r " .. "Targets:")
        for targetID, _ in pairs(Critmatic.ignoredTargets) do
            Critmatic:Print(CritMaticGoldYellow .. "- " .. tostring(targetID) .. "|r")
        end
    end
    local function RemoveIgnoredTarget(input)
        local targetID = tonumber(input)
        if not input or not targetID then
            Critmatic:Print(CritMaticRed .. "Please provide a target id." .. "|r")
            return
        end

        if Critmatic.ignoredTargets and Critmatic.ignoredTargets[targetID] then
            Critmatic.ignoredTargets[targetID] = nil
            Critmatic:Print(CritMaticGoldYellow .. tostring(targetID) .. "|r" .. CritMaticRed .. " has been removed from ignored targets." .. "|r")
            RedrawCritMaticWidget()
        else
            Critmatic:Print(CritMaticRed .. "Target not found in ignored targets." .. "|r")
        end
    end
    -- Function to wipe all ignored targets
    local function WipeIgnoredTargets()
        if not Critmatic.ignoredTargets or next(Critmatic.ignoredTargets) == nil then
            Critmatic:Print(CritMaticRed .. "The ignored targets list is already empty!" .. "|r")
            return
        end

        wipe(Critmatic.ignoredTargets)  -- Clear the table
        Critmatic:Print(CritMaticRed .. "All ignored targets have been removed." .. "|r")
        RedrawCritMaticWidget()
    end

    local function printHelp()
        self:Print(CritMaticBrown .. "Commands:|r")
        self:Print(CritMaticBrown .. "There can be a short calibration period if you don't have any crit data yet, level up, or get multiple new gear upgrades." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic" .. "|r " .. CritMaticGray .. "- Open the CritMatic options menu." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic help" .. "|r " .. CritMaticGray .. "- Show this command list." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic changelog" .. "|r " .. CritMaticGray .. "- Open the CritMatic changelog." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic critlog" .. "|r " .. CritMaticGray .. "- Open the CritMatic crit log." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic critlogdefaultpos" .. "|r " .. CritMaticGray .. "- Resets the Crit Log position. Causes a Reload." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic deletespelldata spell name" .. "|r " .. CritMaticGray .. "- Reset a single spell's data." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic reset" .. "|r " .. CritMaticGray .. "- Reset all CritMatic data." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic ignore spell name" .. "|r " .. CritMaticGray .. "- Ignore a spell." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic ignoredspells" .. "|r " .. CritMaticGray .. "- List all ignored spells." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic removeignoredspell spell name" .. "|r  " .. CritMaticGray .. "- Remove a spell from the ignored spells list." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic wipeignoredspells" .. "|r  " .. CritMaticGray .. "- Remove all spells from the ignored spells list." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic ignoretarget target id" .. "|r " .. CritMaticGray .. "- Ignore a target." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic ignoredtargets" .. "|r " .. CritMaticGray .. "- List all ignored targets." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic removeignoredtarget target id" .. "|r  " .. CritMaticGray .. "- Remove a target from the ignored target list." .. "|r")
        self:Print(CritMaticGoldYellow .. "/critmatic wipeignoredtargets" .. "|r  " .. CritMaticGray .. "- Remove all targets from the ignored targets list." .. "|r")
    end

    local function SlashDispatch(input)
        local sub, rest = (input or ""):match("^(%S*)%s*(.-)%s*$")
        sub = (sub or ""):lower()
        if sub == "" then
            Critmatic:OpenOptions()
        elseif sub == "help" then
            printHelp()
        elseif sub == "changelog" or sub == "log" then
            Critmatic:OpenChangeLog()
        elseif sub == "critlog" then
            toggleCritMaticCritLog()
        elseif sub == "critlogdefaultpos" then
            if Critmatic.ResetCritLogPosition then Critmatic.ResetCritLogPosition() end
        elseif sub == "reset" then
            Critmatic:CritMaticReset()
        elseif sub == "deletespelldata" then
            resetSingleSpellDataSlashCommand(rest)
        elseif sub == "ignore" then
            ignoredSpellSlashCommand(rest)
        elseif sub == "ignoredspells" then
            ListIgnoredSpells()
        elseif sub == "removeignoredspell" then
            RemoveIgnoredSpell(rest)
        elseif sub == "wipeignoredspells" then
            WipeIgnoredSpells()
        elseif sub == "ignoretarget" then
            ignoredTargetSlashCommand(rest)
        elseif sub == "ignoredtargets" then
            ListIgnoredTargets()
        elseif sub == "removeignoredtarget" then
            RemoveIgnoredTarget(rest)
        elseif sub == "wipeignoredtargets" then
            WipeIgnoredTargets()
        elseif sub == "test" then
            local style = (rest or ""):lower()
            if style == "levelup" or style == "renown" or style == "cinematic" then
                local guard = LibStub("LibMatic-OverlayGuard-1.0", true)
                if guard and guard.SimulateOverlay then guard:SimulateOverlay(style, 4) end
            end
            if not Critmatic.db.profile.soundSettings.muteAllSounds then
                local LSM = LibStub("LibSharedMedia-3.0", true)
                if LSM then Critmatic:DeferAlertSound(LSM:Fetch("sound", Critmatic.db.profile.soundSettings.damageCrit)) end
            end
            if Critmatic.MessageFrame and Critmatic.MessageFrame.CreateMessage then
                Critmatic.MessageFrame:CreateMessage("Test Crit 12345!", 1, 0.82, 0, "Interface\\Icons\\Spell_Fire_Fireball", 12345)
            end
        else
            self:Print(CritMaticRed .. "Unknown command. Type " .. "|r" .. CritMaticGoldYellow .. "/critmatic help" .. "|r")
        end
    end

    Critmatic:RegisterChatCommand("critmatic", SlashDispatch)
    Critmatic:RegisterChatCommand("cm", SlashDispatch)
    Critmatic:RegisterChatCommand("cmhelp", printHelp)
    Critmatic:RegisterChatCommand("cmdeletespelldata", resetSingleSpellDataSlashCommand)
    Critmatic:RegisterChatCommand("cmwipeignoredspells", WipeIgnoredSpells)
    Critmatic:RegisterChatCommand("cmremoveignoredspell", RemoveIgnoredSpell)
    Critmatic:RegisterChatCommand("cmignoredspells", ListIgnoredSpells)
    Critmatic:RegisterChatCommand("cmwipeignoredtargets", WipeIgnoredTargets)
    Critmatic:RegisterChatCommand("cmignoredtargets", ListIgnoredTargets)
    Critmatic:RegisterChatCommand(L["slash_cmignore"], ignoredSpellSlashCommand)
    Critmatic:RegisterChatCommand(L["slash_cmreset"], "CritMaticReset")
    Critmatic:RegisterChatCommand(L["slash_cmignoretarget"], ignoredTargetSlashCommand)

    self:RegisterComm("Critmatic")
    Critmatic:RegisterEvent("GROUP_ROSTER_UPDATE")
    -- Function to handle incoming messages

    if GameTooltip and GameTooltip.SetAction then
        hooksecurefunc(GameTooltip, "SetAction", AddHighestHitsToTooltip)
    end
    local SpellbookTooltip = _IsAddOnLoaded("ElvUI") and _G.ElvUISpellBookTooltip or _G.GameTooltip
    if SpellbookTooltip and SpellbookTooltip.SetSpellBookItem then
        hooksecurefunc(SpellbookTooltip, "SetSpellBookItem", AddHighestHitsToTooltip)
    end
    if GameTooltip and GameTooltip.SetPetAction then
        hooksecurefunc(GameTooltip, "SetPetAction", AddHighestHitsToPetTooltip)
    end

    local startup = LibStub("LibMatic-Startup-1.0", true)
    if startup then
        startup:Register({
            name            = "CritMatic",
            version         = version,
            optionsCommand  = "/critmatic",
            helpCommand     = "/critmatic help",
            useFactionColors = false,
            brandColor      = "ffd700",
        })
    end

    function Critmatic:CritMaticLoaded()
        if startup then
            startup:PrintBanner("CritMatic")
        end
    end

    local f = Critmatic.CreateNewMessageFrame()
    -- Ensure Ace3 and AceGUI are loaded

    function Critmatic:TimerCritMaticLoaded()
        Critmatic:CritMaticLoaded()
    end

    if _IsAddOnLoaded("ElvUI") then
        Critmatic:ScheduleTimer("TimerCritMaticLoaded", 8)
    else
        Critmatic:ScheduleTimer("TimerCritMaticLoaded", 4)
    end


    -- Flag to indicate whether the message has been displayed
    Critmatic.hasDisplayedUpdateMessage = false

end

function Critmatic:OnEnable()
    -- Called when the addon is fully loaded and all saved variables are available.
    BindRecordsTable()
    CritMatic_ignoredSpells = CritMatic_ignoredSpells or {}
    Critmatic.ignoredSpells = CritMatic_ignoredSpells
    CritMatic_ignoredTargets = CritMatic_ignoredTargets or {}
    Critmatic.ignoredTargets = CritMatic_ignoredTargets
    -- Combat-event registration. Retail Midnight 12.0 blocks
    -- COMBAT_LOG_EVENT_UNFILTERED for unprivileged addons (Ace3
    -- maintainer nevcairiel confirmed in WowAce issue #697:
    -- "Blizzard blocked the ability to use the
    -- COMBAT_LOG_EVENT_UNFILTERED event"). Attempting to register it
    -- fires ADDON_ACTION_FORBIDDEN and taints the addon so every
    -- subsequent RegisterEvent also gets FORBIDDEN. So on retail
    -- Midnight we skip combat-event registration entirely and tell the
    -- user once. Classic flavors work as before.
    local _, _, _, ifaceVer = GetBuildInfo()
    local isRetailMidnight = (_G.WOW_PROJECT_ID == _G.WOW_PROJECT_MAINLINE)
            and ifaceVer and ifaceVer >= 120000
    if isRetailMidnight then
        Critmatic:Print(CritMaticGray .. "Combat tracking disabled on retail Midnight 12.0+ (Blizzard's combat-addon restriction). Classic flavors are unaffected.|r")
    elseif Critmatic._combatHandler then
        local cf = Critmatic._combatLogFrame
        local function dispatch(event, ...)
            Critmatic._combatHandler(cf, event, ...)
        end
        Critmatic:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED", dispatch)
        Critmatic:RegisterEvent("PLAYER_REGEN_ENABLED", dispatch)
        Critmatic:RegisterEvent("NAME_PLATE_UNIT_ADDED", function(_, unit) Critmatic.TrackUnit(unit) end)
        Critmatic:RegisterEvent("NAME_PLATE_UNIT_REMOVED", function(_, unit)
            if unit then
                local g = UnitGUID(unit)
                if g then Critmatic._guidToUnit[g] = nil end
            end
        end)
        Critmatic:RegisterEvent("PLAYER_TARGET_CHANGED", function() Critmatic.TrackUnit("target") end)
        Critmatic:RegisterEvent("UPDATE_MOUSEOVER_UNIT", function() Critmatic.TrackUnit("mouseover") end)
    end
    -- Now that we know our data is available, we can safely draw the widget.
    if RedrawCritMaticWidget then RedrawCritMaticWidget() end
    if Critmatic.SetupMinimapIcon then Critmatic.SetupMinimapIcon() end
    if Critmatic.UpdateBroker then Critmatic.UpdateBroker() end
end

function Critmatic:OnDisable()
    -- Code to run when the addon is disabled.
end
function Critmatic:OpenOptions()
    local s = LibStub("LibMatic-Settings-1.0", true)
    if s then s:OpenPanel("CritMaticOptions") else LibStub("AceConfigDialog-3.0"):Open("CritMaticOptions") end
end

function Critmatic:DeferAlertSound(snd)
    if not snd then return end
    local guard = LibStub("LibMatic-OverlayGuard-1.0", true)
    if guard and guard.RunWhenClear then
        guard:RunWhenClear(function() PlaySoundFile(snd) end)
    else
        PlaySoundFile(snd)
    end
end
function Critmatic:OpenCritLog()
    toggleCritMaticCritLog()
end
function Critmatic:OpenChangeLog()
    self.showChangeLog()
end

function Critmatic:CritMaticReset()
    wipe(CritMaticData)
    InvalidateAggregateCache()
    Critmatic:Print(CritMaticRed .. L["critmatic_reset"] .. "|r")
    RedrawCritMaticWidget()

end

function Critmatic:OnProfileChanged()
    if RedrawCritMaticWidget then RedrawCritMaticWidget() end
    if Critmatic.UpdateBroker then Critmatic.UpdateBroker() end
    LibStub("AceConfigRegistry-3.0"):NotifyChange("CritMaticOptions")
    Critmatic:Print(CritMaticGoldYellow .. L["profile_changed_reload"] .. "|r")
end

function Critmatic:OnRecordsProfileChanged()
    BindRecordsTable()
    InvalidateAggregateCache()
    if RedrawCritMaticWidget then RedrawCritMaticWidget() end
    if Critmatic.UpdateBroker then Critmatic.UpdateBroker() end
    LibStub("AceConfigRegistry-3.0"):NotifyChange("CritMaticOptions")
    Critmatic:Print(CritMaticGoldYellow .. L["records_profile_changed"] .. "|r")
end

-- Register an event that fires when the player hits an enemy.
-- Frame is created here at load time, but RegisterEvent is deferred to
-- Critmatic:OnEnable() to dodge a retail Midnight 12.0 main-chunk
-- ADDON_ACTION_FORBIDDEN fired against pre-ADDON_LOADED registrations.
local f = CreateFrame("Frame")
Critmatic._combatLogFrame = f

Critmatic._guidToUnit = {}
local GUILD_BROADCAST_COOLDOWN = 60

function Critmatic.TrackUnit(unit)
    if unit and UnitExists(unit) then
        local guid = UnitGUID(unit)
        if guid then
            Critmatic._guidToUnit[guid] = unit
        end
    end
end

local function CritMaticGreyLevel(level)
    if level <= 5 then
        return 0
    elseif level <= 39 then
        return level - math.floor(level / 10) - 5
    elseif level <= 59 then
        return level - math.floor(level / 5) - 1
    else
        return level - 9
    end
end

local function CritMaticIsTrivialGUID(destGUID)
    if not destGUID then
        return false
    end
    local unit = Critmatic._guidToUnit[destGUID]
    if not (unit and UnitExists(unit) and UnitGUID(unit) == destGUID) then
        Critmatic._guidToUnit[destGUID] = nil
        return false
    end
    local ulvl = UnitLevel(unit)
    if not ulvl or ulvl <= 0 then
        return false
    end
    return ulvl <= CritMaticGreyLevel(UnitLevel("player") or 0)
end

-- Variables to hold the highest values during combat
local highestCritDuringCombat = 0
local highestCritHealDuringCombat = 0
local highestCritSpellName = ""
local highestCritHealSpellName = ""
local function combatHandler(self, event, ...)


    if event == "COMBAT_LOG_EVENT_UNFILTERED" then
        local eventInfo = { CombatLogGetCurrentEventInfo() }

        local _, eventType, _, sourceGUID, _, _, _, destGUID = unpack(eventInfo)
        local _, spellID, spellName, spellSchool, amount, overhealing, absorbed, critical
        local petAutoNameResolved = false
        if eventType == "SWING_DAMAGE" then
            if sourceGUID and sourceGUID == UnitGUID("pet") then
                spellID = PetRecordKeyFromGUID(sourceGUID) or 6603
                local petType = (UnitCreatureFamily and UnitCreatureFamily("pet")) or (UnitName and UnitName("pet"))
                if petType then
                    spellName = petType .. " " .. L["attack"]
                    petAutoNameResolved = true
                else
                    spellName = L["pet_attack"]
                end
            else
                spellID = 6603
                spellName = L["auto_attack"]
            end
            amount, _, _, _, _, _, critical = unpack(eventInfo, 12, 18)
        elseif eventType == "SPELL_HEAL" or eventType == "SPELL_PERIODIC_HEAL" then
            spellID, spellName, spellSchool = unpack(eventInfo, 12, 14)
            amount, overhealing, absorbed, critical = unpack(eventInfo, 15, 18)
        elseif eventType == "SPELL_DAMAGE" or eventType == "SPELL_PERIODIC_DAMAGE" or eventType == "RANGE_DAMAGE" then
            spellID, spellName, spellSchool = unpack(eventInfo, 12, 14)
            amount, overhealing, _, _, _, absorbed, critical = unpack(eventInfo, 15, 21)
        end

        local localizedSpellName = GetSpellInfo(spellID)
        local baseSpellName = localizedSpellName or spellName

        local LSM = LibStub("LibSharedMedia-3.0")
        local soundCrit = LSM:Fetch("sound", Critmatic.db.profile.soundSettings.damageCrit)
        local soundNormal = LSM:Fetch("sound", Critmatic.db.profile.soundSettings.damageNormal)
        local soundHealCrit = LSM:Fetch("sound", Critmatic.db.profile.soundSettings.healCrit)
        local soundHealNormal = LSM:Fetch("sound", Critmatic.db.profile.soundSettings.healNormal)

        local isHealEvent = eventType == "SPELL_HEAL" or eventType == "SPELL_PERIODIC_HEAL"
        if (sourceGUID == UnitGUID("player") or sourceGUID == UnitGUID("pet"))
                and (isHealEvent or destGUID ~= UnitGUID("player"))
                and (eventType == "SPELL_DAMAGE" or eventType == "SWING_DAMAGE" or eventType == "RANGE_DAMAGE" or eventType == "SPELL_HEAL" or eventType == "SPELL_PERIODIC_HEAL" or eventType == "SPELL_PERIODIC_DAMAGE")
                and amount and amount > 0 then

            if spellID then
                local recordIcon
                if type(spellID) == "number" then
                    recordIcon = select(3, GetSpellInfo(spellID))
                else
                    recordIcon = GetPetAttackIcon() or select(3, GetSpellInfo(6603)) or "Interface\\Icons\\INV_Misc_QuestionMark"
                end
                CritMaticData[spellID] = CritMaticData[spellID] or {
                    highestCrit = 0,
                    highestCritOld = 0,
                    highestNormal = 0,
                    highestNormalOld = 0,
                    highestHealCrit = 0,
                    highestHealCritOld = 0,
                    highestHeal = 0,
                    highestHealOld = 0,
                    spellIcon = recordIcon,
                }
                if type(spellID) ~= "number" then
                    local rec = CritMaticData[spellID]
                    local changed = false
                    if recordIcon and rec.spellIcon ~= recordIcon then
                        rec.spellIcon = recordIcon
                        changed = true
                    end
                    if (petAutoNameResolved or not rec.displayName) and rec.displayName ~= baseSpellName then
                        rec.displayName = baseSpellName
                        InvalidateAggregateCache()
                        changed = true
                    end
                    if changed and RedrawCritMaticWidget then RedrawCritMaticWidget() end
                elseif not CritMaticData[spellID].spellIcon then
                    CritMaticData[spellID].spellIcon = recordIcon
                end

                if baseSpellName and Critmatic.ignoredSpells and Critmatic.ignoredSpells[baseSpellName:lower()] then
                    return
                end
                if destGUID and Critmatic.ignoredTargets then
                    local _, _, _, _, _, destID = strsplit("-", destGUID)
                    destID = tonumber(destID)
                    if destID and Critmatic.ignoredTargets[destID] then
                        return
                    end
                end
                if Critmatic.db.profile.generalSettings.ignoreTrivialTargets and CritMaticIsTrivialGUID(destGUID) then
                    return
                end
                --print(CombatLogGetCurrentEventInfo())



                if eventType == "SPELL_HEAL" or eventType == "SPELL_PERIODIC_HEAL" then
                    if critical then

                        -- When the event is a heal and it's a critical heal.
                        if amount > CritMaticData[spellID].highestHealCrit and amount <= MAX_HIT then
                            CritMaticData[spellID].highestHealCritOld = CritMaticData[spellID].highestHealCrit
                            CritMaticData[spellID].highestHealCrit = amount

                            if ShouldAnnounceRecord(CritMaticData[spellID].highestHealCritOld, amount) then
                            highestCritHealDuringCombat = amount
                            highestCritHealSpellName = baseSpellName
                            if not Critmatic.db.profile.soundSettings.muteAllSounds then
                                Critmatic:DeferAlertSound(soundHealCrit)
                            end

                            --PlaySoundFile("Interface\\AddOns\\CritMatic\\Media\\Sounds\\LevelUp.ogg", "SFX")

                            if Critmatic.db.profile.generalSettings.alertNotificationsEnabled then
                                Critmatic.ShowNewHealCritMessage(baseSpellName, amount, CritMaticData[spellID].spellIcon, CritMaticData[spellID].highestHealCritOld)
                            end

                            if Critmatic.db.profile.generalSettings.chatNotificationsEnabled then
                                Critmatic:Print(CritMaticColorEscape(Critmatic.db.profile.fontSettings.fontColorCrit) .. Critmatic.BuildRecordChatLine(L["chat_crit_heal"], baseSpellName, CritMaticData[spellID].highestHealCrit, Critmatic.FormatPreviousRecord(CritMaticData[spellID].highestHealCritOld, CritMaticData[spellID].highestHealCrit)) .. "|r")
                            end
                            end
                            InvalidateAggregateCache()
                            RecordEvent(spellID)
                            RedrawCritMaticWidget()
                        end
                    elseif not critical and not Critmatic.db.profile.generalSettings.critsOnlyEnabled then
                        if amount > CritMaticData[spellID].highestHeal and amount <= MAX_HIT then
                            CritMaticData[spellID].highestHealOld = CritMaticData[spellID].highestHeal
                            CritMaticData[spellID].highestHeal = amount

                            if ShouldAnnounceRecord(CritMaticData[spellID].highestHealOld, amount) then
                            if not Critmatic.db.profile.soundSettings.muteAllSounds then
                                Critmatic:DeferAlertSound(soundHealNormal)
                            end

                            --PlaySoundFile("Interface\\AddOns\\CritMatic\\Media\\Sounds\\Heaven.ogg", "SFX")

                            if Critmatic.db.profile.generalSettings.alertNotificationsEnabled then
                                Critmatic.ShowNewHealMessage(baseSpellName, amount, CritMaticData[spellID].spellIcon, CritMaticData[spellID].highestHealOld)
                            end

                            if Critmatic.db.profile.generalSettings.chatNotificationsEnabled then
                                Critmatic:Print(" " .. CritMaticColorEscape(Critmatic.db.profile.fontSettings.fontColor) .. Critmatic.BuildRecordChatLine(L["chat_heal"], baseSpellName, CritMaticData[spellID].highestHeal, Critmatic.FormatPreviousRecord(CritMaticData[spellID].highestHealOld, CritMaticData[spellID].highestHeal)) .. "|r")
                            end
                            end
                            InvalidateAggregateCache()
                            RecordEvent(spellID)
                            RedrawCritMaticWidget()
                        end
                    end
                elseif eventType == "SPELL_DAMAGE" or eventType == "SWING_DAMAGE" or eventType == "SPELL_PERIODIC_DAMAGE" or eventType == "RANGE_DAMAGE" then
                    if critical then
                        -- When the event is damage and it's a critical hit.
                        if amount > CritMaticData[spellID].highestCrit and amount <= MAX_HIT then
                            CritMaticData[spellID].highestCritOld = CritMaticData[spellID].highestCrit
                            CritMaticData[spellID].highestCrit = amount
                            --PlaySound(888, "SFX")
                            if ShouldAnnounceRecord(CritMaticData[spellID].highestCritOld, amount) then
                            highestCritDuringCombat = amount
                            highestCritSpellName = baseSpellName
                            if not Critmatic.db.profile.soundSettings.muteAllSounds then
                                Critmatic:DeferAlertSound(soundCrit)
                            end

                            --PlaySoundFile("Interface\\AddOns\\CritMatic\\Media\\Sounds\\LevelUp.ogg", "SFX")
                            if Critmatic.db.profile.generalSettings.alertNotificationsEnabled then
                                Critmatic.ShowNewCritMessage(baseSpellName, amount, CritMaticData[spellID].spellIcon, CritMaticData[spellID].highestCritOld)
                            end

                            if Critmatic.db.profile.generalSettings.chatNotificationsEnabled then
                                Critmatic:Print(CritMaticColorEscape(Critmatic.db.profile.fontSettings.fontColorCrit) .. Critmatic.BuildRecordChatLine(L["chat_crit"], baseSpellName, CritMaticData[spellID].highestCrit, Critmatic.FormatPreviousRecord(CritMaticData[spellID].highestCritOld, CritMaticData[spellID].highestCrit)) .. "|r")
                            end
                            end
                            InvalidateAggregateCache()
                            RecordEvent(spellID)
                            RedrawCritMaticWidget()
                        end
                    elseif not critical and not Critmatic.db.profile.generalSettings.critsOnlyEnabled then
                        -- When the event is damage but it's not a critical hit.
                        if amount > CritMaticData[spellID].highestNormal and amount <= MAX_HIT then
                            CritMaticData[spellID].highestNormalOld = CritMaticData[spellID].highestNormal
                            CritMaticData[spellID].highestNormal = amount
                            if ShouldAnnounceRecord(CritMaticData[spellID].highestNormalOld, amount) then
                            if not Critmatic.db.profile.soundSettings.muteAllSounds then
                                Critmatic:DeferAlertSound(soundNormal)
                            end

                            --PlaySoundFile("Interface\\AddOns\\CritMatic\\Media\\Sounds\\Heroism_Cast.ogg", "SFX")
                            if Critmatic.db.profile.generalSettings.alertNotificationsEnabled then
                                Critmatic.ShowNewNormalMessage(baseSpellName, amount, CritMaticData[spellID].spellIcon, CritMaticData[spellID].highestNormalOld)
                            end

                            if Critmatic.db.profile.generalSettings.chatNotificationsEnabled then
                                Critmatic:Print(CritMaticColorEscape(Critmatic.db.profile.fontSettings.fontColor) .. Critmatic.BuildRecordChatLine(L["chat_hit"], baseSpellName, CritMaticData[spellID].highestNormal, Critmatic.FormatPreviousRecord(CritMaticData[spellID].highestNormalOld, CritMaticData[spellID].highestNormal)) .. "|r")
                            end
                            end
                            InvalidateAggregateCache()
                            RecordEvent(spellID)
                            RedrawCritMaticWidget()
                        end
                    end
                end

                -- end


            end
        end
    elseif event == "PLAYER_REGEN_ENABLED" then
        -- Questie-style channel selection: pick exactly ONE group-context
        -- channel based on where the player is, and optionally also send to
        -- guild as a separate concern. /say is intentionally not an option:
        -- /say is muted in instances and mixes badly with combat-end timing,
        -- so removing it eliminates a class of "did it send?" bugs.
        local inInstance, instanceType = IsInInstance()

        local function sendChat(chatType)
            local Broadcast = LibStub("LibMatic-Broadcast-1.0", true)
            if highestCritDuringCombat > 0 then
                local text = "CritMatic: " .. L["social_crit"] .. highestCritSpellName .. ": " .. highestCritDuringCombat
                if Broadcast then
                    Broadcast:Broadcast("CritMatic", { text = text, marker = 8, channel = chatType })
                else
                    SendChatMessage("{rt1}" .. text, chatType)
                end
            end
            if highestCritHealDuringCombat > 0 then
                local text = "CritMatic: " .. L["social_crit_heal"] .. highestCritHealSpellName .. ": " .. highestCritHealDuringCombat
                if Broadcast then
                    Broadcast:Broadcast("CritMatic", { text = text, marker = 8, channel = chatType })
                else
                    SendChatMessage("{rt1}" .. text, chatType)
                end
            end
        end

        if IsInGroup() then
            local social = Critmatic.db.profile.social
            local channel, allowed
            if inInstance and instanceType == "pvp" then
                channel = "INSTANCE_CHAT"
                allowed = social.canSendCritsToBattleGrounds
            elseif IsPartyLFG and IsPartyLFG() then
                channel = "INSTANCE_CHAT"
                if IsInRaid() then
                    allowed = social.canSendCritsToRaid
                else
                    allowed = social.canSendCritsToParty
                end
            elseif IsInRaid() then
                channel = "RAID"
                allowed = social.canSendCritsToRaid
            else
                channel = "PARTY"
                allowed = social.canSendCritsToParty
            end
            if allowed then
                sendChat(channel)
            end
        end

        if IsInGuild() and Critmatic.db.profile.social.canSendCritsToGuild then
            local hasContent = highestCritDuringCombat > 0 or highestCritHealDuringCombat > 0
            local now = GetTime()
            if hasContent and (now - (Critmatic._lastGuildBroadcast or 0)) >= GUILD_BROADCAST_COOLDOWN then
                Critmatic._lastGuildBroadcast = now
                sendChat("GUILD")
            end
        end

        highestCritDuringCombat = 0
        highestCritHealDuringCombat = 0
        highestCritSpellName = ""
        highestCritHealSpellName = ""
    end
end

f:SetScript("OnEvent", combatHandler)
Critmatic._combatHandler = combatHandler
