local L = LibStub("AceLocale-3.0"):GetLocale("CritMatic")
local AceConsole = LibStub("AceConsole-3.0")

-- Retail 11.0 removed EasyMenu / UIDropDownMenuTemplate. Classic/TBC/Wrath still have them.
local function ShowContextMenu(menuData, anchorFrame)
    if MenuUtil and MenuUtil.CreateContextMenu then
        MenuUtil.CreateContextMenu(anchorFrame, function(_, rootDescription)
            for _, item in ipairs(menuData) do
                if item.isTitle then
                    rootDescription:CreateTitle(item.text)
                elseif item.menuList then
                    local submenu = rootDescription:CreateButton(item.text)
                    for _, child in ipairs(item.menuList) do
                        local childBtn = submenu:CreateButton(child.text, child.func)
                        if child.disabled and childBtn and childBtn.SetEnabled then
                            childBtn:SetEnabled(false)
                        end
                        if child.tooltipText and childBtn and childBtn.SetTooltip then
                            local reasonTitle, reasonText = child.text, child.tooltipText
                            childBtn:SetTooltip(function(tooltip)
                                if GameTooltip_SetTitle then
                                    GameTooltip_SetTitle(tooltip, reasonTitle)
                                else
                                    tooltip:SetText(reasonTitle)
                                end
                                if GameTooltip_AddNormalLine then
                                    GameTooltip_AddNormalLine(tooltip, reasonText)
                                else
                                    tooltip:AddLine(reasonText, 1, 1, 1, true)
                                end
                            end)
                        end
                    end
                elseif item.func then
                    rootDescription:CreateButton(item.text, item.func)
                end
            end
        end)
    elseif EasyMenu then
        -- Reuse a single named holder across invocations; naming is required for
        -- UIDropDownMenuTemplate's $parent XML substitutions, and re-CreateFrame
        -- on an existing name errors on Classic.
        local holder = _G["CritMaticContextMenu"]
            or CreateFrame("Frame", "CritMaticContextMenu", UIParent, "UIDropDownMenuTemplate")
        EasyMenu(menuData, holder, "cursor", 0, 0, "MENU")
    end
end

local function CritMaticShareText(spellName, spellData)
    if not spellData then return nil end
    local label, value
    if spellData.highestCrit and spellData.highestCrit > 0 then
        label, value = L["crit_log_crit"], spellData.highestCrit
    elseif spellData.highestNormal and spellData.highestNormal > 0 then
        label, value = L["crit_log_hit"], spellData.highestNormal
    elseif spellData.highestHealCrit and spellData.highestHealCrit > 0 then
        label, value = L["crit_log_crit"] .. " " .. L["crit_log_heal"], spellData.highestHealCrit
    elseif spellData.highestHeal and spellData.highestHeal > 0 then
        label, value = L["crit_log_heal"], spellData.highestHeal
    else
        return nil
    end
    return "{rt8} CritMatic: " .. spellName .. " " .. label .. ": " .. value
end

local function CritMaticSendShare(text, chatType, target)
    local send = (C_ChatInfo and C_ChatInfo.SendChatMessage) or SendChatMessage
    send(text, chatType, nil, target)
end

StaticPopupDialogs["CRITMATIC_SHARE_WHISPER"] = {
    text = L["crit_log_share_whisper_prompt"],
    button1 = ACCEPT,
    button2 = CANCEL,
    hasEditBox = true,
    OnAccept = function(self)
        local name = self.editBox:GetText()
        if name and name ~= "" then
            CritMaticSendShare(self.data, "WHISPER", name)
        end
    end,
    EditBoxOnEnterPressed = function(self)
        local parent = self:GetParent()
        local name = parent.editBox:GetText()
        if name and name ~= "" then
            CritMaticSendShare(parent.data, "WHISPER", name)
        end
        parent:Hide()
    end,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
}

local function CritMaticBuildShareMenu(spellName, spellData)
    local text = CritMaticShareText(spellName, spellData)
    if not text then return nil end
    local list = {}
    local function add(label, chatType, enabled, disabledReason)
        local item = { text = label, notCheckable = true }
        if enabled then
            item.func = function() CritMaticSendShare(text, chatType) end
        else
            item.disabled = true
            item.tooltipTitle = label
            item.tooltipText = disabledReason
            item.tooltipOnButton = 1
        end
        list[#list + 1] = item
    end
    local inInstance, instanceType = IsInInstance()
    local inParty = IsInGroup() and not IsInRaid()
    local inRaid = IsInRaid()
    local inPveInstance = inInstance and instanceType ~= "pvp" and IsInGroup()
    local inPvpInstance = inInstance and instanceType == "pvp"
    add(L["crit_log_share_party"], "PARTY", inParty, L["crit_log_share_disabled_party"])
    add(L["crit_log_share_raid"], "RAID", inRaid, L["crit_log_share_disabled_raid"])
    add(L["crit_log_share_instance"], "INSTANCE_CHAT", inPveInstance, L["crit_log_share_disabled_instance"])
    add(L["crit_log_share_battleground"], "INSTANCE_CHAT", inPvpInstance, L["crit_log_share_disabled_battleground"])
    add(L["crit_log_share_guild"], "GUILD", IsInGuild(), L["crit_log_share_disabled_guild"])
    add(L["crit_log_share_yell"], "YELL", true)
    add(L["crit_log_share_emote"], "EMOTE", true)
    list[#list + 1] = {
        text = L["crit_log_share_whisper"],
        notCheckable = true,
        func = function()
            StaticPopup_Show("CRITMATIC_SHARE_WHISPER", nil, nil, text)
        end,
    }
    return list
end

local function OpenCritMaticSettings()
    if Critmatic and Critmatic.OpenOptions then
        Critmatic:OpenOptions()
    end
end

function toggleCritMaticCritLog()
    local db = Critmatic.db.profile
    local sizePos = Critmatic.db.profile.critLogWidgetPos
    if not Critmatic.crit_log_frame or not Critmatic.crit_log_frame.frame then
        local Type, Version = "CritMatic_CritLog", 30
        local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
        if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then
            return
        end

        -- Empty font path makes FontString:SetFont throw "Invalid font file
        -- asset" on retail Midnight 12.0, aborting Constructor before
        -- RedrawCritMaticWidget is defined. Use the Blizzard standard text
        -- font as a safe default that exists on every flavor.
        local main_font = _G.STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF"

        local pairs, assert, type = pairs, assert, type
        local wipe = table.wipe

        local PlaySound = PlaySound
        local CreateFrame, UIParent = CreateFrame, UIParent

        local function Button_OnClick(frame)
            PlaySound(799) -- SOUNDKIT.GS_TITLE_OPTION_EXIT
            frame.obj:Hide()
        end

        local function Frame_OnShow(frame)
            frame.obj:Fire("OnShow")
        end

        local function Frame_OnClose(frame)
            frame.obj:Fire("OnClose")
        end

        local function Frame_OnMouseDown(frame)
            AceGUI:ClearFocus()
        end

        local function Title_OnMouseDown(frame)
            AceGUI:ClearFocus()
        end

        local function MoverSizer_OnMouseUp(mover)
            local frame = mover:GetParent()
            frame:StopMovingOrSizing()
            local self = frame.obj
            local status = self.status or self.localstatus
            status.width = frame:GetWidth()
            status.height = frame:GetHeight()
            status.top = frame:GetTop()
            status.left = frame:GetLeft()
        end

        local function PinTopLeftForSizing(f)
            local l, t = f:GetLeft(), f:GetTop()
            if l and t then
                f:ClearAllPoints()
                f:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", l, t)
            end
        end

        local function SizerSE_OnMouseDown(frame)
            local f = frame:GetParent()
            PinTopLeftForSizing(f)
            f:StartSizing("BOTTOMRIGHT")
            AceGUI:ClearFocus()
        end

        local function SizerS_OnMouseDown(frame)
            local f = frame:GetParent()
            PinTopLeftForSizing(f)
            f:StartSizing("BOTTOM")
            AceGUI:ClearFocus()
        end

        local function SizerE_OnMouseDown(frame)
            local f = frame:GetParent()
            PinTopLeftForSizing(f)
            f:StartSizing("RIGHT")
            AceGUI:ClearFocus()
        end

        local function StatusBar_OnEnter(frame)
            frame.obj:Fire("OnEnterStatusBar")
        end

        local function StatusBar_OnLeave(frame)
            frame.obj:Fire("OnLeaveStatusBar")
        end

        local methods = {
            ["OnAcquire"] = function(self)
                self.frame:SetParent(UIParent)
                self.frame:SetFrameStrata("MEDIUM")
                self.frame:SetFrameLevel(100) -- Lots of room to draw under it
                self:SetTitle()
                self:SetStatusText()
                self:ApplyStatus()
                self:Show()
                self:EnableResize(true)
            end,

            ["OnRelease"] = function(self)
                self.status = nil
                wipe(self.localstatus)
            end,

            ["OnWidthSet"] = function(self, width)
                local content = self.content
                local contentwidth = width - 34
                if contentwidth < 0 then
                    contentwidth = 0
                end
                content:SetWidth(contentwidth)
                content.width = contentwidth
                self.titlebg:SetWidth(contentwidth - 35)
            end,

            ["OnHeightSet"] = function(self, height)
                local content = self.content
                local contentheight = height
                if contentheight < 0 then
                    contentheight = 0
                end
                content:SetHeight(contentheight)
                content.height = contentheight
            end,

            ["SetTitle"] = function(self, title)
                self.titletext:SetText(title)

            end,

            ["SetStatusText"] = function(self, text)
                self.statustext:SetText(text)
            end,

            ["Hide"] = function(self)
                self.frame:Hide()
            end,

            ["Show"] = function(self)
                self.frame:Show()
            end,

            ["EnableResize"] = function(self, state)
                local func = state and "Show" or "Hide"
                self.sizer_se[func](self.sizer_se)
                self.sizer_s[func](self.sizer_s)
                self.sizer_e[func](self.sizer_e)
            end,

            -- called to set an external table to store status in
            -- Function to set an external table to store status in
            ["SetStatusTable"] = function(self, status)
                assert(type(status) == "table")
                self.status = status
                self:ApplyStatus()
            end,

            -- Function to apply the status to the frame
            ["ApplyStatus"] = function(self)
                local sizePos = Critmatic.db.profile.critLogWidgetPos
                local frame = self.frame

                -- Set the width and height from sizePos or use default values
                self:SetWidth(sizePos.size_x or 700)
                self:SetHeight(sizePos.size_y or 500)

                frame:ClearAllPoints()

                -- If sizePos has position data, use it to set the frame's position
                if sizePos.pos_x and sizePos.pos_y then
                    frame:SetPoint("CENTER", UIParent, "CENTER", sizePos.pos_x, sizePos.pos_y)

                end
            end,
        }

        local FrameBackdrop = {
            bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
            edgeFile = "Interface\\CHATFRAME\\ChatFrameBorder",
            tile = false,
            tileSize = 32,
            edgeSize = 32,
            insets = { left = 8, right = 8, top = 8, bottom = 8 },
        }

        local PaneBackdrop = {
            bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
            edgeFile = "Interface\\Glues\\COMMON\\TextPanel-Border",
            tile = true,
            tileSize = 16,
            edgeSize = 32,
            insets = { left = 3, right = 3, top = 5, bottom = 3 },
        }

        local function Constructor()
            local frame = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
            frame:Hide()

            frame:EnableMouse(true)
            frame:SetMovable(true)
            frame:SetResizable(true)
            frame:SetFrameStrata("MEDIUM")
            frame:SetFrameLevel(100) -- Lots of room to draw under it
            frame:SetBackdrop(PaneBackdrop)
            frame:SetBackdropColor(0, 0, 0, 0.6)
            frame:SetBackdropBorderColor(1, 1, 1, 1)
            frame:SetSize(250, 150)

            if frame.SetResizeBounds then
                frame:SetResizeBounds(100, 50)
            else
                frame:SetMinResize(150, 100)
            end
            frame:SetToplevel(true)
            frame:SetScript("OnShow", Frame_OnShow)
            frame:SetScript("OnHide", Frame_OnClose)
            frame:SetScript("OnMouseDown", Frame_OnMouseDown)

            local closebutton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
            closebutton:SetScript("OnClick", Button_OnClick)
            closebutton:SetPoint("BOTTOMRIGHT", -27, 17)
            closebutton:SetHeight(0)
            closebutton:SetWidth(100)
            closebutton:SetText(CLOSE)
            closebutton:Hide()

            local statusbg = CreateFrame("Button", nil, frame, "BackdropTemplate")
            statusbg:SetPoint("BOTTOMLEFT", 15, 15)
            statusbg:SetPoint("BOTTOMRIGHT", -132, 15)
            statusbg:SetHeight(0)
            statusbg:SetBackdrop(PaneBackdrop)
            statusbg:SetBackdropColor(0.1, 0.1, 0.1)
            statusbg:SetBackdropBorderColor(0.4, 0.4, 0.4)
            statusbg:SetScript("OnEnter", StatusBar_OnEnter)
            statusbg:SetScript("OnLeave", StatusBar_OnLeave)
            statusbg:Hide()

            local statustext = statusbg:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            statustext:SetPoint("TOPLEFT", 7, -2)
            statustext:SetPoint("BOTTOMRIGHT", -7, 2)
            statustext:SetHeight(0)
            statustext:SetJustifyH("LEFT")
            statustext:SetText("")
            statustext:Hide()

            local titlebg = frame:CreateTexture(nil, "OVERLAY")
            titlebg:SetTexture("Interface\\ClassTrainerFrame\\UI-ClassTrainer-DetailHeaderLeft")
            titlebg:SetTexCoord(0, 1, 0, 1)
            titlebg:SetPoint("TOP", 0, 12)
            titlebg:SetWidth(100)
            titlebg:SetHeight(40)
            titlebg:Hide()

            local title = CreateFrame("Frame", nil, frame)
            title:EnableMouse(true)
            title:SetScript("OnMouseDown", Title_OnMouseDown)
            title:SetScript("OnMouseUp", MoverSizer_OnMouseUp)
            -- Span the full top of the window so the entire header strip is a drag handle.
            title:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
            title:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
            title:SetHeight(30)

            title:SetMovable(true)
            title:RegisterForDrag("LeftButton")
            title:SetScript("OnDragStart", function(self)
                local parent = self:GetParent()
                local pos = Critmatic.db and Critmatic.db.profile and Critmatic.db.profile.critLogWidgetPos
                if pos and pos.lock and not IsControlKeyDown() then
                    return
                end
                parent._critmaticDragStarted = true
                parent:StartMoving()
            end)
            title:SetScript("OnDragStop", function(self)
                local parent = self:GetParent()
                if not parent._critmaticDragStarted then
                    return
                end
                parent._critmaticDragStarted = nil
                parent:StopMovingOrSizing()
                local pos = Critmatic.db and Critmatic.db.profile and Critmatic.db.profile.critLogWidgetPos
                if pos then
                    local anchor, _, anchor2, xOfs, yOfs = parent:GetPoint()
                    pos.anchor = anchor
                    pos.anchor2 = anchor2
                    pos.pos_x = xOfs
                    pos.pos_y = yOfs
                end
            end)

            local titletext = title:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            titletext:SetFont(main_font, 13, "")
            titletext:SetPoint("LEFT", frame, "TOPLEFT", 32, -10)

            local titlebg_l = frame:CreateTexture(nil, "OVERLAY")
            titlebg_l:SetTexture(131080) -- Interface\\DialogFrame\\UI-DialogBox-Header
            titlebg_l:SetTexCoord(0.21, 0.31, 0, 0.63)
            titlebg_l:SetPoint("RIGHT", titlebg, "LEFT")
            titlebg_l:SetWidth(30)
            titlebg_l:SetHeight(40)
            titlebg_l:Hide()

            local titlebg_r = frame:CreateTexture(nil, "OVERLAY")
            titlebg_r:SetTexture(131080) -- Interface\\DialogFrame\\UI-DialogBox-Header
            titlebg_r:SetTexCoord(0.67, 0.77, 0, 0.63)
            titlebg_r:SetPoint("LEFT", titlebg, "RIGHT")
            titlebg_r:SetWidth(30)
            titlebg_r:SetHeight(40)
            titlebg_r:Hide()

            local sizer_se = CreateFrame("Frame", nil, frame)
            sizer_se:SetPoint("BOTTOMRIGHT")
            sizer_se:SetWidth(25)
            sizer_se:SetHeight(25)
            sizer_se:EnableMouse()
            sizer_se:SetScript("OnMouseDown", SizerSE_OnMouseDown)
            sizer_se:SetScript("OnMouseUp", MoverSizer_OnMouseUp)

            local line1 = sizer_se:CreateTexture(nil, "BACKGROUND")
            line1:SetWidth(14)
            line1:SetHeight(14)
            line1:SetPoint("BOTTOMRIGHT", -8, 8)
            line1:SetTexture(137057) -- Interface\\Tooltips\\UI-Tooltip-Border
            local x = 0.1 * 14 / 17
            line1:SetTexCoord(0.05 - x, 0.5, 0.05, 0.5 + x, 0.05, 0.5 - x, 0.5 + x, 0.5)

            local line2 = sizer_se:CreateTexture(nil, "BACKGROUND")
            line2:SetWidth(8)
            line2:SetHeight(8)
            line2:SetPoint("BOTTOMRIGHT", -8, 8)
            line2:SetTexture(137057) -- Interface\\Tooltips\\UI-Tooltip-Border
            x = 0.1 * 8 / 17
            line2:SetTexCoord(0.05 - x, 0.5, 0.05, 0.5 + x, 0.05, 0.5 - x, 0.5 + x, 0.5)

            local sizer_s = CreateFrame("Frame", nil, frame)
            sizer_s:SetPoint("BOTTOMRIGHT", -25, 0)
            sizer_s:SetPoint("BOTTOMLEFT")
            sizer_s:SetHeight(25)
            sizer_s:EnableMouse(true)
            sizer_s:SetScript("OnMouseDown", SizerS_OnMouseDown)
            sizer_s:SetScript("OnMouseUp", MoverSizer_OnMouseUp)

            local sizer_e = CreateFrame("Frame", nil, frame)
            sizer_e:SetPoint("BOTTOMRIGHT", 0, 25)
            sizer_e:SetPoint("TOPRIGHT")
            sizer_e:SetWidth(25)
            sizer_e:EnableMouse(true)
            sizer_e:SetScript("OnMouseDown", SizerE_OnMouseDown)
            sizer_e:SetScript("OnMouseUp", MoverSizer_OnMouseUp)

            --Container Support
            local content = CreateFrame("Frame", nil, frame)
            content:SetPoint("TOPLEFT", 3, -33)
            content:SetPoint("BOTTOMRIGHT", 15, 6)

            local scrollContainer = CreateFrame("ScrollFrame", nil, frame, "UIPanelScrollFrameTemplate")
            scrollContainer:SetPoint("TOPLEFT", frame, "TOPLEFT", 10, -30)
            scrollContainer:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -28, 14)

            local scrollChild = CreateFrame("Frame", nil, scrollContainer)
            scrollContainer:SetScrollChild(scrollChild)
            scrollChild:SetSize(scrollContainer:GetWidth(), 200)

            scrollContainer:SetScript("OnSizeChanged", function(self, width, height)
                scrollChild:SetWidth(width)
            end)

            local scrollBar = scrollContainer.ScrollBar
            if not scrollBar then
                for _, child in ipairs({ scrollContainer:GetChildren() }) do
                    if child:IsObjectType("Slider") then
                        scrollBar = child
                        break
                    end
                end
            end
            if scrollBar then
                scrollBar:ClearAllPoints()
                scrollBar:SetPoint("TOPLEFT", scrollContainer, "TOPRIGHT", 6, -16)
                scrollBar:SetPoint("BOTTOMLEFT", scrollContainer, "BOTTOMRIGHT", 6, 31)
            end

            -- Table to keep track of created frames
            local createdSpellFrames = {}

            function RedrawCritMaticWidget()
                if Critmatic.UpdateBroker then Critmatic.UpdateBroker() end

                if not Critmatic._uiReady then
                    Critmatic._critLogDirty = true
                    return
                end

                -- Skip the rebuild entirely while the window is hidden; redraw on
                -- show instead. WoW frames are never garbage-collected, so the old
                -- code's habit of re-creating a frame per row on every record even
                -- when the window was closed was a steady, permanent memory leak.
                if not (Critmatic.crit_log_frame and Critmatic.crit_log_frame.frame
                        and Critmatic.crit_log_frame.frame:IsShown()) then
                    Critmatic._critLogDirty = true
                    return
                end
                Critmatic._critLogDirty = false

                local LSM = LibStub("LibSharedMedia-3.0")

                local function hex(c)
                    return string.format("|cff%02x%02x%02x", math.floor((c[1] or 1)*255+0.5), math.floor((c[2] or 1)*255+0.5), math.floor((c[3] or 1)*255+0.5))
                end
                local f = Critmatic.db.profile.critLogFontSettings

                local function lcHex(logicalKey)
                    return hex(Critmatic:ResolveColor("critlog", logicalKey))
                end

                -- Resolve the font and colors once per redraw instead of per row:
                -- LSM:Fetch and the color-escape formatting are identical for
                -- every header/row in a given rebuild.
                local rowFontPath = LSM:Fetch("font", f.font) or (_G.STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF")
                local rowFontSize = f.fontSize or 12
                local rowFontOutline = (f.fontOutline ~= "NONE" and f.fontOutline) or nil
                local nameHex = lcHex("spellName")
                local critHex, hitHex = lcHex("crit"), lcHex("hit")
                local critHealHex, healHex = lcHex("critHeal"), lcHex("heal")
                local petCritHex, petHitHex = lcHex("petCrit"), lcHex("petHit")

                titletext:SetFont(rowFontPath, rowFontSize + 1, rowFontOutline)

                local yOffset = 0
                local LINE_HEIGHT = 13     -- pixels per line of GameFontNormal text
                local ROW_PADDING = 4      -- top/bottom padding inside each row
                local MIN_ROW_HEIGHT = 34  -- at least tall enough to host the 30px spell icon

                local sortableData = Critmatic.GetGroupedSpellRecords({ sortMode = f.sortMode, groupByType = f.groupByType })

                local rowIndex = 0

                local function acquireRow()
                    rowIndex = rowIndex + 1
                    local spellFrame = createdSpellFrames[rowIndex]
                    if not spellFrame then
                        spellFrame = CreateFrame("Frame", nil, scrollChild, BackdropTemplateMixin and "BackdropTemplate" or nil)
                        spellFrame:EnableMouse(true)
                        spellFrame:SetScript("OnEnter", function(self)
                            self:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8x8"})
                            self:SetBackdropColor(1, 1, 1, 0.1)
                        end)
                        spellFrame:SetScript("OnLeave", function(self)
                            self:SetBackdrop(nil)
                        end)
                        spellFrame:SetScript("OnMouseUp", function(self, button)
                            if button == "RightButton" then
                                local sName = self.spellName
                                local ids = self.spellIDs
                                if not sName then return end
                                local shareMenu = CritMaticBuildShareMenu(sName, self.spellData)
                                local menu = {
                                    {text = "|cffffd700" .. sName .. "|r", isTitle = true, notCheckable = true},
                                    {text = "Ignore Spell", notCheckable = true, func = function()
                                        if Critmatic.ignoredSpells then
                                            Critmatic.ignoredSpells[sName:lower()] = true
                                            RedrawCritMaticWidget()
                                        end
                                    end},
                                    {text = "Delete Spell Data", notCheckable = true, func = function()
                                        StaticPopupDialogs["CRITMATIC_DELETE_SPELL"] = {
                                            text = "Delete all data for " .. sName .. "?",
                                            button1 = "Yes",
                                            button2 = "No",
                                            OnAccept = function()
                                                for _, id in ipairs(ids) do
                                                    CritMaticData[id] = nil
                                                end
                                                RedrawCritMaticWidget()
                                            end,
                                            timeout = 0,
                                            whileDead = true,
                                            hideOnEscape = true,
                                        }
                                        StaticPopup_Show("CRITMATIC_DELETE_SPELL")
                                    end},
                                }
                                if shareMenu then
                                    table.insert(menu, 2, {
                                        text = L["crit_log_share"],
                                        notCheckable = true,
                                        hasArrow = true,
                                        menuList = shareMenu,
                                    })
                                end
                                ShowContextMenu(menu, self)
                            end
                        end)

                        spellFrame.icon = spellFrame:CreateTexture(nil, "ARTWORK")
                        spellFrame.icon:SetSize(30, 30)
                        spellFrame.icon:SetPoint("LEFT", spellFrame, "LEFT", 5, 0)

                        spellFrame.text = spellFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
                        spellFrame.text:SetJustifyH("LEFT")
                        spellFrame.text:SetPoint("LEFT", spellFrame.icon, "RIGHT", 5, 0)
                        spellFrame.text:SetWidth(175)

                        createdSpellFrames[rowIndex] = spellFrame
                    end
                    return spellFrame
                end

                local function emitHeader(label)
                    local hf = acquireRow()
                    hf.spellName = nil
                    hf.spellIDs = nil
                    hf:SetHeight(LINE_HEIGHT + ROW_PADDING)
                    hf:ClearAllPoints()
                    hf:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 0, -yOffset)
                    hf:SetPoint("TOPRIGHT", scrollChild, "TOPRIGHT", 0, -yOffset)
                    hf.icon:SetTexture(nil)
                    hf.text:SetPoint("LEFT", hf, "LEFT", 5, 0)
                    hf.text:SetText(nameHex .. label .. "|r")
                    hf.text:SetFont(rowFontPath, rowFontSize, rowFontOutline)
                    hf:Show()
                    yOffset = yOffset + LINE_HEIGHT + ROW_PADDING
                end

                local function emitSpellRow(entry, showDamage, showHealing)
                    local spellName = entry.name
                    local spellData = entry.data
                    if Critmatic.ignoredSpells and Critmatic.ignoredSpells[spellName:lower()] then return end

                    local spellIDs = entry.ids
                    local spellIDToUse = #spellIDs > 1 and spellIDs[2] or spellIDs[1]
                    local storedIcon = spellData.spellIcon or CritMaticData[spellIDToUse] and CritMaticData[spellIDToUse].spellIcon
                    local spellIconPath = storedIcon or select(3, GetSpellInfo(spellIDToUse))
                    if not spellIconPath then return end

                    local lineCount = 1
                    if showDamage then
                        if spellData.highestCrit and spellData.highestCrit > 0 then lineCount = lineCount + 1 end
                        if spellData.highestNormal and spellData.highestNormal > 0 then lineCount = lineCount + 1 end
                    end
                    if showHealing then
                        if spellData.highestHealCrit and spellData.highestHealCrit > 0 then lineCount = lineCount + 1 end
                        if spellData.highestHeal and spellData.highestHeal > 0 then lineCount = lineCount + 1 end
                    end
                    local rowHeight = math.max(MIN_ROW_HEIGHT, lineCount * LINE_HEIGHT + ROW_PADDING)

                    local spellFrame = acquireRow()
                    spellFrame.spellName = spellName
                    spellFrame.spellIDs = spellIDs
                    spellFrame.spellData = spellData
                    spellFrame:SetHeight(rowHeight)
                    spellFrame:ClearAllPoints()
                    spellFrame:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 0, -yOffset)
                    spellFrame:SetPoint("TOPRIGHT", scrollChild, "TOPRIGHT", 0, -yOffset)
                    spellFrame.icon:SetTexture(spellIconPath)
                    spellFrame.icon:SetPoint("LEFT", spellFrame, "LEFT", 5, 0)
                    spellFrame.text:SetPoint("LEFT", spellFrame.icon, "RIGHT", 5, 0)

                    local spellInfoText = string.format(nameHex .. "%s|r\n", spellName)

                    if showDamage then
                        if spellData.highestCrit and spellData.highestCrit > 0 then
                            spellInfoText = spellInfoText .. string.format((spellData.isPet and petCritHex or critHex) .. L["crit_log_crit"] .. ": %s (" .. L["crit_log_old"] .. ": %s)|r\n", spellData.highestCrit, spellData.highestCritOld or "0")
                        end
                        if spellData.highestNormal and spellData.highestNormal > 0 then
                            spellInfoText = spellInfoText .. string.format((spellData.isPet and petHitHex or hitHex) .. L["crit_log_hit"] .. ": %s (" .. L["crit_log_old"] .. ": %s)|r\n", spellData.highestNormal, spellData.highestNormalOld or "0")
                        end
                    end

                    if showHealing then
                        if spellData.highestHealCrit and spellData.highestHealCrit > 0 then
                            spellInfoText = spellInfoText .. string.format((spellData.isPet and petCritHex or critHealHex) .. L["crit_log_crit"] .. " " .. L["crit_log_heal"] .. ": %s (" .. L["crit_log_old"] .. ": %s)|r\n", spellData.highestHealCrit, spellData.highestHealCritOld or "0")
                        end
                        if spellData.highestHeal and spellData.highestHeal > 0 then
                            spellInfoText = spellInfoText .. string.format((spellData.isPet and petHitHex or healHex) .. L["crit_log_heal"] .. ": %s (" .. L["crit_log_old"] .. ": %s)|r", spellData.highestHeal, spellData.highestHealOld or "0")
                        end
                    end

                    spellFrame.text:SetText(spellInfoText)
                    spellFrame.text:SetFont(rowFontPath, rowFontSize, rowFontOutline)
                    spellFrame:Show()
                    yOffset = yOffset + rowHeight
                end

                if f.groupByType then
                    local Grouping = _G.CritMaticRecordGrouping
                    local EntryMatches = Grouping and Grouping.EntryMatchesCategory
                    local function matches(entry, category)
                        return EntryMatches and EntryMatches(entry.data, category)
                    end
                    local hasDamage = false
                    local hasHealing = false
                    local hasPet = false
                    if EntryMatches then
                        for _, entry in ipairs(sortableData) do
                            if not (Critmatic.ignoredSpells and Critmatic.ignoredSpells[entry.name:lower()]) then
                                if matches(entry, "damage") then hasDamage = true end
                                if matches(entry, "healing") then hasHealing = true end
                                if matches(entry, "pet") then hasPet = true end
                            end
                        end
                    end

                    local function emitDamageBlock()
                        if not hasDamage then return end
                        emitHeader(L["appearance_section_damage"])
                        for _, entry in ipairs(sortableData) do
                            if not (Critmatic.ignoredSpells and Critmatic.ignoredSpells[entry.name:lower()]) then
                                if matches(entry, "damage") then
                                    emitSpellRow(entry, true, false)
                                end
                            end
                        end
                    end

                    local function emitHealingBlock()
                        if not hasHealing then return end
                        emitHeader(L["appearance_section_healing"])
                        for _, entry in ipairs(sortableData) do
                            if not (Critmatic.ignoredSpells and Critmatic.ignoredSpells[entry.name:lower()]) then
                                if matches(entry, "healing") then
                                    emitSpellRow(entry, false, true)
                                end
                            end
                        end
                    end

                    local function emitPetBlock()
                        if not hasPet then return end
                        emitHeader(L["appearance_section_pet"])
                        for _, entry in ipairs(sortableData) do
                            if not (Critmatic.ignoredSpells and Critmatic.ignoredSpells[entry.name:lower()]) then
                                if matches(entry, "pet") then
                                    emitSpellRow(entry, true, true)
                                end
                            end
                        end
                    end

                    if f.groupOrder == "healing" then
                        emitHealingBlock()
                        emitDamageBlock()
                    else
                        emitDamageBlock()
                        emitHealingBlock()
                    end
                    emitPetBlock()

                    if not hasDamage and not hasHealing and not hasPet then
                        for _, entry in ipairs(sortableData) do
                            emitSpellRow(entry, true, true)
                        end
                    end
                else
                    for _, entry in ipairs(sortableData) do
                        emitSpellRow(entry, true, true)
                    end
                end

                -- Hide pooled rows left over from a previous, longer redraw.
                for i = rowIndex + 1, #createdSpellFrames do
                    createdSpellFrames[i]:Hide()
                end

                scrollChild:SetHeight(math.max(yOffset, 1))

                if scrollContainer and scrollContainer.SetVerticalScroll then
                    scrollContainer:SetVerticalScroll(0)
                end
            end

            function RecordEvent(spellID)
                -- Check if the spellName is valid
                if not spellID then
                    return
                end

                -- Initialize spell data if not already present
                if not CritMaticData[spellID] then
                    CritMaticData[spellID] = {
                        timestamp = 0  -- Initialize the timestamp
                    }
                end

                -- Update the timestamp for the spell event
                CritMaticData[spellID].timestamp = time()
            end

            RedrawCritMaticWidget()
            -- A function to update CritMaticData and refresh the widget


            local widget = {
                localstatus = {},
                titletext = titletext,
                statustext = statustext,
                titlebg = titlebg,
                sizer_se = sizer_se,
                sizer_s = sizer_s,
                sizer_e = sizer_e,
                content = content,
                frame = frame,
                type = Type,
            }
            for method, func in pairs(methods) do
                widget[method] = func
            end
            closebutton.obj, statusbg.obj = widget, widget

            return AceGUI:RegisterAsContainer(widget)
        end

        AceGUI:RegisterWidgetType(Type, Constructor, Version)

        -- Frame creation and initial setup code
        Critmatic.crit_log_frame = AceGUI:Create("CritMatic_CritLog")

        Critmatic.crit_log_frame.frame:SetMovable(true)
        Critmatic.crit_log_frame.frame:EnableMouse(true)
        Critmatic.crit_log_frame:SetTitle("CritMatic")
        Critmatic.crit_log_frame:SetLayout("Fill")

        local function CritLogDefaultPosFrame()
            local frame = Critmatic.crit_log_frame
            local defaultPos = Critmatic.db.defaults.profile.critLogWidgetPos
            frame:Show()
            frame:ClearAllPoints()
            frame:SetPoint("RIGHT", UIParent, "RIGHT", defaultPos.pos_x, defaultPos.pos_y)
            Critmatic.db.profile.critLogWidgetPos.pos_x = defaultPos.pos_x
            Critmatic.db.profile.critLogWidgetPos.pos_y = defaultPos.pos_y

        end

        AceConsole:RegisterChatCommand("cmcritlogdefaultpos", CritLogDefaultPosFrame)
        Critmatic.ResetCritLogPosition = CritLogDefaultPosFrame

        local critmatic_icon_frame = CreateFrame("Frame", nil, Critmatic.crit_log_frame.frame)
        critmatic_icon_frame:SetSize(40, 40)
        critmatic_icon_frame:SetMovable(true)
        critmatic_icon_frame:EnableMouse(true)
        critmatic_icon_frame:SetPoint("TOPLEFT", Critmatic.crit_log_frame.frame, "TOPLEFT", -4, 10)
        -- Sit above the title-bar drag handle so right-click and tooltip work on the icon.
        critmatic_icon_frame:SetFrameLevel(Critmatic.crit_log_frame.frame:GetFrameLevel() + 10)

        local texture_round_black = critmatic_icon_frame:CreateTexture(nil, "OVERLAY")
        texture_round_black:SetPoint("CENTER", critmatic_icon_frame, "CENTER", -5, 4)
        texture_round_black:SetDrawLayer("OVERLAY", 2)
        texture_round_black:SetHeight(40)
        texture_round_black:SetWidth(40)
        texture_round_black:SetTexture("Interface\\PVPFrame\\PVP-Separation-Circle-Cooldown-overlay")

        local texture_CritMatic_icon = critmatic_icon_frame:CreateTexture(nil, "OVERLAY")
        texture_CritMatic_icon:SetPoint("CENTER", critmatic_icon_frame, "CENTER", -4, 4)
        texture_CritMatic_icon:SetDrawLayer("OVERLAY", 3)
        texture_CritMatic_icon:SetHeight(25)
        texture_CritMatic_icon:SetWidth(25)
        texture_CritMatic_icon:SetTexture("Interface\\AddOns\\CritMatic\\Media\\Textures\\critmatic-medallion.tga")

        local texture_gold_ring = critmatic_icon_frame:CreateTexture(nil, "OVERLAY")
        texture_gold_ring:SetPoint("CENTER", critmatic_icon_frame, "CENTER", 0, 0)
        texture_gold_ring:SetDrawLayer("OVERLAY", 4)
        texture_gold_ring:SetHeight(50)
        texture_gold_ring:SetWidth(50)
        texture_gold_ring:SetTexture("Interface\\COMMON\\BlueMenuRing")

        critmatic_icon_frame:RegisterForDrag("LeftButton")
        critmatic_icon_frame:SetScript("OnDragStart", function(self)
            local mainFrame = Critmatic.crit_log_frame.frame
            if Critmatic.db.profile.critLogWidgetPos.lock and not IsControlKeyDown() then
                return
            end
            mainFrame._critmaticDragStarted = true
            mainFrame:StartMoving()
        end)
        critmatic_icon_frame:SetScript("OnDragStop", function(self)
            local mainFrame = Critmatic.crit_log_frame.frame
            if not mainFrame._critmaticDragStarted then
                return
            end
            mainFrame._critmaticDragStarted = nil
            mainFrame:StopMovingOrSizing()
            local anchor, _, anchor2, xOfs, yOfs = mainFrame:GetPoint()
            local sp = Critmatic.db.profile.critLogWidgetPos
            sp.anchor = anchor
            sp.anchor2 = anchor2
            sp.pos_x = xOfs
            sp.pos_y = yOfs
        end)
        critmatic_icon_frame:SetScript("OnMouseUp", function(self, button)
            if button == "RightButton" then
                local menu = {
                    {text = "|cffffd700CritMatic|r", isTitle = true, notCheckable = true},
                    {text = "Open Settings", notCheckable = true, func = OpenCritMaticSettings},
                    {text = "Open Changelog", notCheckable = true, func = function()
                        Critmatic.showChangeLog()
                    end},
                    {text = Critmatic.db.profile.critLogWidgetPos.lock and "Unlock Position" or "Lock Position", notCheckable = true, func = function()
                        Critmatic.db.profile.critLogWidgetPos.lock = not Critmatic.db.profile.critLogWidgetPos.lock
                    end},
                    {text = "Reset Position", notCheckable = true, func = function()
                        local defaultPos = Critmatic.db.defaults.profile.critLogWidgetPos
                        Critmatic.crit_log_frame.frame:ClearAllPoints()
                        Critmatic.crit_log_frame.frame:SetPoint("RIGHT", UIParent, "RIGHT", defaultPos.pos_x, defaultPos.pos_y)
                        local sp = Critmatic.db.profile.critLogWidgetPos
                        sp.pos_x = defaultPos.pos_x
                        sp.pos_y = defaultPos.pos_y
                    end},
                    {text = "Hide Crit Log", notCheckable = true, func = function()
                        toggleCritMaticCritLog()
                    end},
                }
                ShowContextMenu(menu, self)
            end
        end)

        critmatic_icon_frame:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText("|cffffd700CritMatic|r")
            GameTooltip:AddLine("Right-click for options", 0.8, 0.8, 0.8)
            GameTooltip:Show()
        end)
        critmatic_icon_frame:SetScript("OnLeave", function()
            GameTooltip:Hide()
        end)

        -- Load the saved state and apply it
        if Critmatic.db.profile.isCritLogFrameShown then

            Critmatic.crit_log_frame.frame:ClearAllPoints()
            Critmatic.crit_log_frame.frame:SetPoint(sizePos.anchor, UIParent, sizePos.anchor2, sizePos.pos_x, sizePos
                    .pos_y)
            -- Restore the user's last resized dimensions; fall back to the same defaults ApplyStatus uses.
            Critmatic.crit_log_frame.frame:SetSize(sizePos.size_x or 700, sizePos.size_y or 500)
            Critmatic.crit_log_frame.frame:Show()
            RedrawCritMaticWidget()
            --  critmatic_icon_frame:Show()
        else

            Critmatic.crit_log_frame.frame:Hide()
            texture_round_black:Hide()
            texture_CritMatic_icon:Hide()
            texture_gold_ring:Hide()
        end
        critmatic_icon_frame:HookScript("OnShow", function(self, button)
            texture_round_black:Show()
            texture_CritMatic_icon:Show()
            texture_gold_ring:Show()
        end)

        critmatic_icon_frame:HookScript("OnHide", function(self, button)
            texture_round_black:Hide()
            texture_CritMatic_icon:Hide()
            texture_gold_ring:Hide()
        end)

        -- Make sure the main frame is movable and draggable
        Critmatic.crit_log_frame.frame:SetMovable(true)
        Critmatic.crit_log_frame.frame:RegisterForDrag("LeftButton")

        -- When you start dragging the main frame, this will be called.
        -- If the lock is on, holding Ctrl while dragging bypasses it.
        Critmatic.crit_log_frame.frame:SetScript("OnDragStart", function(self)
            if Critmatic.db.profile.critLogWidgetPos.lock and not IsControlKeyDown() then
                return
            end
            self._critmaticDragStarted = true
            self:StartMoving()
            critmatic_icon_frame:StartMoving()
        end)

        -- When you stop dragging the main frame, this will be called.
        Critmatic.crit_log_frame.frame:SetScript("OnDragStop", function(self)
            if not self._critmaticDragStarted then
                return
            end
            self._critmaticDragStarted = nil
            self:StopMovingOrSizing()
            local anchor, _, anchor2, xOfs, yOfs = self:GetPoint()  -- Get the current anchor point and offsets

            local sp = Critmatic.db.profile.critLogWidgetPos
            sp.anchor = anchor
            sp.anchor2 = anchor2-- Save the anchor point
            sp.pos_x = xOfs
            sp.pos_y = yOfs
        end)

        hooksecurefunc(Critmatic.crit_log_frame.frame, "StopMovingOrSizing", function()
            local f = Critmatic.crit_log_frame.frame
            local sp = Critmatic.db.profile.critLogWidgetPos
            sp.size_x = f:GetWidth()
            sp.size_y = f:GetHeight()
            local anchor, _, anchor2, xOfs, yOfs = f:GetPoint()
            if anchor then
                sp.anchor = anchor
                sp.anchor2 = anchor2
                sp.pos_x = xOfs
                sp.pos_y = yOfs
            end
        end)


    else
        -- Toggle visibility

        if Critmatic.crit_log_frame.frame:IsShown() then
            Critmatic.crit_log_frame.frame:Hide()
            Critmatic.db.profile.isCritLogFrameShown = false


        else

            Critmatic.crit_log_frame.frame:Show()
            Critmatic.db.profile.isCritLogFrameShown = true
            RedrawCritMaticWidget()
        end
        -- Save the visibility state

    end
end
