local addonName, NS = ...
local GR = _G["GRS_MainFrame"]

local iconPath = "Interface\\AddOns\\" .. addonName .. "\\RaidRoster\\Resources\\Icons\\"

-- =========================================================================
-- STATIC REGISTRY
-- =========================================================================
local TOOL_DATA = {
    -- RAID MARKERS (Master Sheet)
    { id = 1, type = "texture", path = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", coords = {0.75, 1, 0.25, 0.5}, name = "Skull", unique = true },
    { id = 2, type = "texture", path = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", coords = {0.5, 0.75, 0.25, 0.5}, name = "Cross", unique = true },
    { id = 3, type = "texture", path = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", coords = {0, 0.25, 0.25, 0.5}, name = "Moon", unique = true },
    { id = 4, type = "texture", path = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", coords = {0.25, 0.5, 0.25, 0.5}, name = "Square", unique = true },
    { id = 5, type = "texture", path = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", coords = {0.5, 0.75, 0, 0.25}, name = "Diamond", unique = true },
    { id = 6, type = "texture", path = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", coords = {0.75, 1, 0, 0.25}, name = "Triangle", unique = true },
    { id = 7, type = "texture", path = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", coords = {0, 0.25, 0, 0.25}, name = "Star", unique = true },
    { id = 8, type = "texture", path = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", coords = {0.25, 0.5, 0, 0.25}, name = "Circle", unique = true },

    -- ROLES
    { id = 50, type = "texture", path = iconPath .. "Tank.tga", name = "Tank" },
    { id = 51, type = "texture", path = iconPath .. "Melee.tga", name = "Melee" },
    { id = 52, type = "texture", path = iconPath .. "Ranged.tga", name = "Ranged" },
    { id = 53, type = "texture", path = iconPath .. "Healer.tga", name = "Healer" },

    -- CLASSES
    { id = 100, type = "atlas", path = "classicon-deathknight", name = "DK" },
    { id = 101, type = "atlas", path = "classicon-demonhunter", name = "DH" },
    { id = 102, type = "atlas", path = "classicon-druid", name = "Druid" },
    { id = 103, type = "atlas", path = "classicon-evoker", name = "Evoker" },
    { id = 104, type = "atlas", path = "classicon-hunter", name = "Hunter" },
    { id = 105, type = "atlas", path = "classicon-mage", name = "Mage" },
    { id = 106, type = "atlas", path = "classicon-monk", name = "Monk" },
    { id = 107, type = "atlas", path = "classicon-paladin", name = "Paladin" },
    { id = 108, type = "atlas", path = "classicon-priest", name = "Priest" },
    { id = 109, type = "atlas", path = "classicon-rogue", name = "Rogue" },
    { id = 110, type = "atlas", path = "classicon-shaman", name = "Shaman" },
    { id = 111, type = "atlas", path = "classicon-warlock", name = "Warlock" },
    { id = 112, type = "atlas", path = "classicon-warrior", name = "Warrior" },

    -- UTILITIES
    { id = 200, type = "text", path = "A", name = "Text", isLabel = true }, 
    { id = 201, type = "texture", path = iconPath .. "Box.tga", name = "Box", canStretch = true },
    { id = 202, type = "texture", path = iconPath .. "Circle.tga", name = "ShapeCircle", canStretch = true },
    { id = 203, type = "texture", path = iconPath .. "Arrow.tga", name = "Arrow", canStretch = true }
}

-- LOOKUP TABLES
GR.ToolRegistry = {}
GR.ToolIDLookup = {}

for _, data in ipairs(TOOL_DATA) do
    GR.ToolRegistry[data.id] = data
    GR.ToolIDLookup[data.name] = data.id
end

GR.CanvasIcons = {}

-- =========================================================================
-- FORMATTING UI
-- =========================================================================
local FormatPopup = CreateFrame("Frame", "GRS_TextFormatPopup", UIParent, "BackdropTemplate")
FormatPopup:SetSize(160, 30); FormatPopup:SetFrameStrata("FULLSCREEN_DIALOG")
FormatPopup:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = false, tileSize = 16, edgeSize = 12, insets = { left = 3, right = 3, top = 3, bottom = 3 } })
FormatPopup:SetBackdropColor(0.1, 0.1, 0.1, 1); FormatPopup:SetBackdropBorderColor(0.6, 0.6, 0.6, 1); FormatPopup:Hide()

local function CreateToolBtn(parent, text, w, func)
    local b = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
    b:SetSize(w, 20); b:SetText(text)
    b:SetScript("OnClick", function() func(); if FormatPopup.target and FormatPopup.target.eb then FormatPopup.target.eb:SetFocus() end end)
    return b
end

FormatPopup.btnBold = CreateToolBtn(FormatPopup, "B", 20, function() if not FormatPopup.target then return end; FormatPopup.target.data.isBold = not FormatPopup.target.data.isBold; FormatPopup.target:UpdateVisuals(); if GR.TriggerLiveUpdate then GR:TriggerLiveUpdate() end end); FormatPopup.btnBold:SetPoint("LEFT", 5, 0)
FormatPopup.btnItalic = CreateToolBtn(FormatPopup, "I", 20, function() if not FormatPopup.target then return end; FormatPopup.target.data.isItalic = not FormatPopup.target.data.isItalic; FormatPopup.target:UpdateVisuals(); if GR.TriggerLiveUpdate then GR:TriggerLiveUpdate() end end); FormatPopup.btnItalic:SetPoint("LEFT", FormatPopup.btnBold, "RIGHT", 2, 0)
FormatPopup.btnUnder = CreateToolBtn(FormatPopup, "U", 20, function() if not FormatPopup.target then return end; FormatPopup.target.data.isUnderline = not FormatPopup.target.data.isUnderline; FormatPopup.target:UpdateVisuals(); if GR.TriggerLiveUpdate then GR:TriggerLiveUpdate() end end); FormatPopup.btnUnder:SetPoint("LEFT", FormatPopup.btnItalic, "RIGHT", 2, 0)
FormatPopup.btnSizeDown = CreateToolBtn(FormatPopup, "-", 20, function() if not FormatPopup.target then return end; FormatPopup.target.data.fontSize = math.max(8, (FormatPopup.target.data.fontSize or 14) - 2); FormatPopup.target:UpdateVisuals(); if GR.TriggerLiveUpdate then GR:TriggerLiveUpdate() end end); FormatPopup.btnSizeDown:SetPoint("LEFT", FormatPopup.btnUnder, "RIGHT", 10, 0)
FormatPopup.btnSizeUp = CreateToolBtn(FormatPopup, "+", 20, function() if not FormatPopup.target then return end; FormatPopup.target.data.fontSize = math.min(64, (FormatPopup.target.data.fontSize or 14) + 2); FormatPopup.target:UpdateVisuals(); if GR.TriggerLiveUpdate then GR:TriggerLiveUpdate() end end); FormatPopup.btnSizeUp:SetPoint("LEFT", FormatPopup.btnSizeDown, "RIGHT", 2, 0)

local colorSwatch = CreateFrame("Button", nil, FormatPopup)
colorSwatch:SetSize(18, 18); colorSwatch:SetPoint("LEFT", FormatPopup.btnSizeUp, "RIGHT", 5, 0)
colorSwatch.bg = colorSwatch:CreateTexture(nil, "ARTWORK"); colorSwatch.bg:SetAllPoints(); colorSwatch.bg:SetColorTexture(1, 1, 1, 1)
colorSwatch:SetScript("OnClick", function()
    if not FormatPopup.target then return end
    local d = FormatPopup.target.data
    local prevR, prevG, prevB = d.r or 1, d.g or 1, d.b or 1
    local info = { 
        r = prevR, g = prevG, b = prevB, opacity = 1.0, hasOpacity = false, 
        swatchFunc = function() 
            local r, g, b = ColorPickerFrame:GetColorRGB(); d.r, d.g, d.b = r, g, b; 
            FormatPopup.target:UpdateVisuals(); colorSwatch.bg:SetColorTexture(r, g, b) 
        end, 
        cancelFunc = function() 
            d.r, d.g, d.b = prevR, prevG, prevB; 
            FormatPopup.target:UpdateVisuals(); colorSwatch.bg:SetColorTexture(prevR, prevG, prevB) 
        end,
        func = function() if GR.TriggerLiveUpdate then GR:TriggerLiveUpdate() end end 
    }
    ColorPickerFrame:SetupColorPickerAndShow(info)
end)
FormatPopup.colorSwatch = colorSwatch

function FormatPopup:AttachTo(targetFrame)
    self.target = targetFrame; self:ClearAllPoints(); self:SetPoint("BOTTOMLEFT", targetFrame, "TOPLEFT", 0, 5)
    local d = targetFrame.data; self.colorSwatch.bg:SetColorTexture(d.r or 1, d.g or 1, d.b or 1); self:Show()
end

-- =========================================================================
-- CANVAS LOGIC
-- =========================================================================

function GR:ClearVisuals()
    if not GR.CanvasIcons then return end
    for _, icon in ipairs(GR.CanvasIcons) do
        icon:Hide()
        icon:SetParent(nil)
        icon:SetScript("OnUpdate", nil)
    end
    table.wipe(GR.CanvasIcons)
    if FormatPopup then FormatPopup:Hide() end
end

function GR:WipeCanvasData(isSilent)
    if not DB_Rmap_Tools then return end
    table.wipe(DB_Rmap_Tools.canvasData)
    GR:ClearVisuals()
    if not isSilent then PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF) end
    if GR.TriggerLiveUpdate then GR:TriggerLiveUpdate() end
end

function GR:EnforceUniqueIcon(targetName)
    if not targetName then return end
    local changed = false
    -- 1. Remove from Visuals
    for i = #GR.CanvasIcons, 1, -1 do
        local icon = GR.CanvasIcons[i]
        if icon.data and icon.data.name == targetName then 
            icon:Hide(); icon:SetParent(nil); table.remove(GR.CanvasIcons, i) 
            changed = true
        end
    end
    -- 2. Remove from Data
    if DB_Rmap_Tools and DB_Rmap_Tools.canvasData then
        for i = #DB_Rmap_Tools.canvasData, 1, -1 do
            local d = DB_Rmap_Tools.canvasData[i]
            if d.name == targetName then 
                table.remove(DB_Rmap_Tools.canvasData, i)
                changed = true 
            end
        end
    end
end

local function RemoveIcon(icon)
    for i, v in ipairs(GR.CanvasIcons) do if v == icon then table.remove(GR.CanvasIcons, i); break end end
    if icon.data.isValid and DB_Rmap_Tools then
        for i, d in ipairs(DB_Rmap_Tools.canvasData) do if d == icon.data then table.remove(DB_Rmap_Tools.canvasData, i); break end end
    end
    icon:Hide(); icon:SetParent(nil); PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF)
    if FormatPopup.target == icon then FormatPopup:Hide() end
    if GR.TriggerLiveUpdate then GR:TriggerLiveUpdate() end
end

local function HandleIconDrop(icon)
    icon:StopMovingOrSizing()
    local mapOverlay = _G["GRS_Map_Overlay"]
    if not mapOverlay or not mapOverlay:IsShown() then RemoveIcon(icon); return end
    local cx, cy = icon:GetCenter(); local ox, oy = mapOverlay:GetCenter(); local ow, oh = mapOverlay:GetSize()
    if not cx or not ox or ow <= 0 then RemoveIcon(icon); return end
    local halfW, halfH = (ow / 2) + 10, (oh / 2) + 10; local dx, dy = math.abs(cx - ox), math.abs(cy - oy)
    if dx <= halfW and dy <= halfH then
        icon.data.normX = 0.5 + ((cx - ox) / ow); icon.data.normY = 0.5 + ((cy - oy) / oh)
        local isNew = not icon.data.isValid
        if isNew then
            icon.data.isValid = true; table.insert(DB_Rmap_Tools.canvasData, icon.data); PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
            if icon.data.isLabel then icon.data.text = "New Label"; icon:UpdateVisuals() end
        end
        if icon.UpdatePosition then icon:UpdatePosition() end
        if isNew and icon.data.isLabel then icon:SetEditMode(true) end
        if GR.TriggerLiveUpdate then GR:TriggerLiveUpdate() end
    else RemoveIcon(icon); GR:ShowMapToast("Removed", true) end
end

function GR:SpawnIndependentIcon(toolData, initialX, initialY, isFromLoad)
    -- Look up registry data if only ID is provided (Save/Load)
    local regData = GR.ToolRegistry[toolData.id] or toolData
    if not regData.path then regData = GR.ToolRegistry[1] end 

    if not isFromLoad then
        if regData.unique then GR:EnforceUniqueIcon(regData.name) end
    end
    
    local mainWin = GR.MapWindow or UIParent
    local icon = CreateFrame("Frame", nil, mainWin)
    icon:SetSize(32, 32); icon:SetFrameStrata("FULLSCREEN_DIALOG"); icon:SetMovable(true); icon:EnableMouse(true); icon:EnableMouseWheel(true)
    
    -- Merge Registry Data with Instance Data
    icon.data = { 
        id = regData.id,
        type = regData.type, 
        path = regData.path, 
        coords = regData.coords, 
        name = regData.name, 
        canStretch = regData.canStretch, 
        isLabel = regData.isLabel, 
        
        normX = toolData.normX or 0.5, 
        normY = toolData.normY or 0.5, 
        scale = toolData.scale or 1.0, 
        scaleX = toolData.scaleX or (toolData.scale or 1.0), 
        scaleY = toolData.scaleY or (toolData.scale or 1.0), 
        rotation = toolData.rotation or 0, 
        
        text = toolData.text or (regData.isLabel and "A") or "Text", 
        fontSize = toolData.fontSize or 14, 
        isBold = toolData.isBold or false, 
        isItalic = toolData.isItalic or false, 
        isUnderline = toolData.isUnderline or false, 
        r = toolData.r or 1, 
        g = toolData.g or 1, 
        b = toolData.b or 1, 
        
        isValid = isFromLoad or false 
    }
    
    table.insert(GR.CanvasIcons, icon)
    
    if icon.data.isLabel then
        icon.fs = icon:CreateFontString(nil, "OVERLAY", "GameFontHighlight"); icon.fs:SetPoint("CENTER")
        icon.underline = icon:CreateTexture(nil, "OVERLAY"); icon.underline:SetColorTexture(1, 1, 1, 1); icon.underline:SetHeight(2); icon.underline:SetPoint("TOPLEFT", icon.fs, "BOTTOMLEFT", 0, -2); icon.underline:SetPoint("TOPRIGHT", icon.fs, "BOTTOMRIGHT", 0, -2); icon.underline:Hide()
        icon.eb = CreateFrame("EditBox", nil, icon, "InputBoxTemplate"); icon.eb:SetSize(100, 24); icon.eb:SetPoint("CENTER"); icon.eb:SetAutoFocus(false); icon.eb:SetMaxLetters(50); icon.eb:Hide()
        icon.eb:SetScript("OnEnterPressed", function(self) self:ClearFocus() end); icon.eb:SetScript("OnEscapePressed", function(self) self:SetText(icon.data.text); self:ClearFocus() end)
        icon.eb:SetScript("OnEditFocusLost", function(self) 
            if FormatPopup:IsMouseOver() or ColorPickerFrame:IsShown() then return end
            local txt = self:GetText(); if txt and txt ~= "" then icon.data.text = txt end; icon:SetEditMode(false); FormatPopup:Hide()
            if not icon.data.isValid then HandleIconDrop(icon) 
            else if GR.TriggerLiveUpdate then GR:TriggerLiveUpdate() end end 
        end)
        icon.SetEditMode = function(self, edit) if edit then self.fs:Hide(); self.underline:Hide(); self.eb:Show(); self.eb:SetText(self.data.text); self.eb:SetFocus(); self.eb:SetWidth(math.max(100, self.fs:GetStringWidth() + 20)); FormatPopup:AttachTo(self) else self.eb:Hide(); self:UpdateVisuals(); self.fs:Show(); if self.data.isUnderline then self.underline:Show() end end end
        icon.UpdateVisuals = function(self) 
            local d = self.data
            local fontFile = "Fonts\\FRIZQT__.TTF"; if d.isItalic then fontFile = "Fonts\\ARIALN.TTF" end
            local flags = d.isBold and "THICKOUTLINE" or "OUTLINE"
            
            -- [SCALING FIX] Scale the font size relative to map
            local mapScale = GR.currMapScale or 1.0
            local scaledFontSize = (d.fontSize or 14) * mapScale
            
            self.fs:SetFont(fontFile, scaledFontSize, flags)
            self.fs:SetText(d.text)
            self.fs:SetTextColor(d.r, d.g, d.b)
            self.underline:SetColorTexture(d.r, d.g, d.b)
            self:SetSize(self.fs:GetStringWidth() + 10, self.fs:GetStringHeight() + 10)
            if d.isUnderline then self.underline:Show() else self.underline:Hide() end 
        end
    else
        local tex = icon:CreateTexture(nil, "OVERLAY"); tex:SetAllPoints(); 
        if regData.type == "atlas" then 
            tex:SetAtlas(regData.path) 
        else 
            tex:SetTexture(regData.path) 
            if regData.coords then tex:SetTexCoord(unpack(regData.coords)) end 
        end; 
        icon.texture = tex
    end
    
    icon.UpdatePosition = function(self)
        if self.data.isValid and GR.mapTex and GR.mapTex:IsShown() then
            local mW, mH = GR.mapTex:GetSize(); local offX = (self.data.normX - 0.5) * mW; local offY = (self.data.normY - 0.5) * mH; self:SetParent(GR.mapPane); self:SetFrameStrata("HIGH"); self:SetFrameLevel(GR.mapPane:GetFrameLevel() + 50); self:ClearAllPoints(); self:SetPoint("CENTER", GR.mapTex, "CENTER", offX, offY)
        end
        
        -- [SCALING FIX] Apply the global map scale to the icon dimensions
        local mapScale = GR.currMapScale or 1.0
        
        if self.data.isLabel then 
            self:UpdateVisuals()
            self:SetScale(self.data.scale) 
        else 
            local baseSize = 32
            local finalW = baseSize * self.data.scaleX * mapScale
            local finalH = baseSize * self.data.scaleY * mapScale
            self:SetSize(finalW, finalH)
            
            if self.texture and self.texture.SetRotation then self.texture:SetRotation(self.data.rotation) end 
        end
    end
    
    local lastClick = 0
    icon:SetScript("OnMouseDown", function(self, button)
        if button == "LeftButton" then if self.data.isLabel then local now = GetTime(); if now - lastClick < 0.4 then self:SetEditMode(true); return end; lastClick = now end; if self.eb and self.eb:HasFocus() then return end; if IsShiftKeyDown() and self.data.isValid and not self.data.isLabel then GR:SpawnIndependentIcon(self.data, nil, nil, true) else self:SetParent(GR.MapWindow); self:SetFrameStrata("FULLSCREEN_DIALOG"); self:StartMoving(); if self.data.isLabel then FormatPopup:Hide() end end
        elseif button == "RightButton" and self.data.isValid then self.scaling = true; self.startX, self.startY = GetCursorPosition(); self.startScaleX, self.startScaleY = self.data.scaleX, self.data.scaleY; self.startScale = self.data.scale
            self:SetScript("OnUpdate", function(f) local currX, currY = GetCursorPosition(); local minPx, maxPx = 32, 64; if f.data.canStretch then maxPx = 512 end; if f.data.isLabel then minPx, maxPx = 0.5, 3.0 end 
                if f.data.isLabel then local diff = (currX - f.startX) / 100; f.data.scale = math.max(0.5, math.min(3.0, f.startScale + diff))
                else local minS, maxS = minPx/32, maxPx/32; if IsShiftKeyDown() and f.data.canStretch then local diffX = (currX - f.startX) / 50; local diffY = (currY - f.startY) / 50; f.data.scaleX = math.max(minS, math.min(maxS, f.startScaleX + diffX)); f.data.scaleY = math.max(minS, math.min(maxS, f.startScaleY + diffY)) else local diff = (currX - f.startX) / 100; local newS = math.max(0.5, math.min(maxS, f.startScaleX + diff)); f.data.scaleX = newS; f.data.scaleY = newS; f.data.scale = newS end end; f:UpdatePosition() end)
        end
    end)
    icon:SetScript("OnMouseUp", function(self, button) 
        if button == "LeftButton" then 
            if not (self.eb and self.eb:HasFocus()) then HandleIconDrop(self) end 
        elseif button == "RightButton" then 
            self.scaling = false; self:SetScript("OnUpdate", nil); 
            if self.data.isValid then 
                self:UpdatePosition() 
                if GR.TriggerLiveUpdate then GR:TriggerLiveUpdate() end
            end 
        end 
    end)
    icon:SetScript("OnMouseWheel", function(self, delta) 
        if not self.data.isValid then return end; 
        local rotationStep = math.rad(10); self.data.rotation = (self.data.rotation or 0) + (delta * rotationStep); 
        if self.texture and self.texture.SetRotation then self.texture:SetRotation(self.data.rotation) end
        if GR.TriggerLiveUpdate then GR:TriggerLiveUpdate() end
    end)
    
    if isFromLoad then icon:UpdatePosition() else local scale = UIParent:GetEffectiveScale(); local x, y = GetCursorPosition(); icon:SetPoint("CENTER", nil, "BOTTOMLEFT", x/scale, y/scale); icon:StartMoving(); if icon.data.isLabel then icon:UpdateVisuals() end; icon:SetScript("OnUpdate", function(f) if not IsMouseButtonDown("LeftButton") then f:SetScript("OnUpdate", nil); HandleIconDrop(f) end end) end; icon:Show()
end

function GR:RefreshCanvasIcons()
    GR:ClearVisuals() 
    if not DB_Rmap_Tools.canvasData then return end
    for _, toolData in ipairs(DB_Rmap_Tools.canvasData) do
        GR:SpawnIndependentIcon(toolData, nil, nil, true)
    end
end

function GR:RT_PopulateToolGrid()
    local parent = self.RaidToolsContent; if not parent then return end; local iconSize, gap, cols = 32, 10, 2; local startX, startY = 40, -60
    local clearBtn = CreateFrame("Button", nil, parent); clearBtn:SetSize(180, 24); clearBtn:SetPoint("TOP", parent, "TOP", 0, -20)
    if NS.CreateBg then NS.CreateBg(clearBtn, 0.4, 0.1, 0.1, 1) end; clearBtn:SetNormalFontObject("GameFontNormalSmall"); clearBtn:SetText("Clear Canvas"); clearBtn:SetScript("OnClick", function() GR:WipeCanvasData() end)
    
    for i, data in ipairs(TOOL_DATA) do 
        local col = (i - 1) % cols; local row = math.floor((i - 1) / cols)
        local btn = CreateFrame("Button", nil, parent); btn:SetSize(iconSize, iconSize); btn:SetPoint("TOPLEFT", startX + (col * (iconSize + gap)), startY - (row * (iconSize + gap)))
        local visual = CreateFrame("Frame", nil, btn); visual:SetSize(iconSize, iconSize); visual:SetPoint("CENTER"); visual:SetAlpha(0.8)
        
        if data.type == "text" then 
            local fs = visual:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge"); fs:SetPoint("CENTER"); fs:SetText(data.path); fs:SetScale(1.8) 
        else 
            local tex = visual:CreateTexture(nil, "ARTWORK"); tex:SetAllPoints(); 
            if data.type == "atlas" then 
                tex:SetAtlas(data.path) 
            else 
                tex:SetTexture(data.path)
                if data.coords then tex:SetTexCoord(unpack(data.coords)) end 
            end 
        end
        
        btn:SetScript("OnEnter", function() visual:SetAlpha(1.0) end); btn:SetScript("OnLeave", function() visual:SetAlpha(0.8) end)
        btn:SetScript("OnMouseDown", function() GR:SpawnIndependentIcon(data) end)
    end
    
    local totalRows = math.ceil(#TOOL_DATA / cols); local contentHeight = math.abs(startY) + (totalRows * (iconSize + gap)) + 60; parent:SetHeight(contentHeight); if self.RaidToolsSlider then self.RaidToolsSlider:SetMinMaxValues(0, math.max(0, contentHeight - self.RaidToolsScroll:GetHeight())) end
end