local addonName, SinsToolkit = ...

local Window = {}
SinsToolkit.Window = Window
Window.wrappedTexts = {}

local function GetToolkitValue(key)
    if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
    return SinsToolkitDB and SinsToolkitDB[key]
end

local function SetToolkitValue(key, value)
    if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
    SinsToolkitDB[key] = value
    if SinsToolkit.TriggerUpdate then SinsToolkit.TriggerUpdate(key) end
end

local function GetMainMinimapVisible()
    return not (SinsToolkitDB and SinsToolkitDB.minimap and SinsToolkitDB.minimap.hide)
end

local function SetMainMinimapVisible(value)
    if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
    SinsToolkitDB.minimap = SinsToolkitDB.minimap or {}
    SinsToolkitDB.minimap.hide = not value
    local DBIcon = LibStub and LibStub("LibDBIcon-1.0", true)
    if DBIcon then
        if value then
            DBIcon:Show(addonName)
        else
            DBIcon:Hide(addonName)
        end
        DBIcon:Refresh(addonName, SinsToolkitDB.minimap)
    end
end

local function GetTheme()
    return SinsToolkit.GetTheme and SinsToolkit.GetTheme() or {
        accentR = 0.72, accentG = 0.41, accentB = 0.88, accentHex = "b868dc",
        bgR = 0.02, bgG = 0.02, bgB = 0.025, bgA = 0.94, borderA = 0.9,
    }
end

local function SetFlatBackdrop(frame, bgR, bgG, bgB, bgA, borderR, borderG, borderB, borderA)
    if not frame or not frame.SetBackdrop then return end
    frame:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
        insets = { left = 1, right = 1, top = 1, bottom = 1 },
    })
    frame:SetBackdropColor(bgR, bgG, bgB, bgA)
    frame:SetBackdropBorderColor(borderR, borderG, borderB, borderA)
end

function Window:SetTooltip(frame, title, text)
    if not frame or not text or text == "" then return frame end
    frame.tooltipTitle = title
    frame.tooltipText = text
    if frame.sinsToolkitStyledTooltip then return frame end
    frame:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(self.tooltipTitle or "Sin's Toolkit: QOL", 1, 1, 1)
        GameTooltip:AddLine(self.tooltipText, 0.85, 0.76, 0.95, true)
        GameTooltip:Show()
    end)
    frame:SetScript("OnLeave", GameTooltip_Hide)
    return frame
end

function Window:CreateSectionHeader(parent, text, yOffset)
    local theme = GetTheme()
    local header = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    header:SetPoint("TOPLEFT", 20, yOffset or -18)
    header:SetText(text)
    header:SetTextColor(theme.accentR, theme.accentG, theme.accentB)
    return header
end

function Window:CreateButton(parent, text, width, height, onClick, tooltip)
    local theme = GetTheme()
    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    btn:SetSize(width or 160, height or 30)
    SetFlatBackdrop(btn, 0.035, 0.035, 0.04, 0.98, 0.14, 0.14, 0.15, 0.9)
    local fs = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    fs:SetPoint("CENTER")
    fs:SetJustifyH("CENTER")
    fs:SetTextColor(0.95, 0.80, 0.22)
    btn:SetFontString(fs)
    btn:SetText(text)
    btn:SetScript("OnClick", onClick)
    btn:SetScript("OnEnter", function(self)
        SetFlatBackdrop(self, theme.accentR * 0.14, theme.accentG * 0.14, theme.accentB * 0.14, 0.98, theme.accentR, theme.accentG, theme.accentB, 0.85)
        if self.isNavButton and SinsToolkit.RegisterOpacityFrame then
            SinsToolkit.RegisterOpacityFrame(self, theme.accentR * 0.14, theme.accentG * 0.14, theme.accentB * 0.14, theme.accentR, theme.accentG, theme.accentB, 0.85)
        end
        if self.tooltipText then
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText(self.tooltipTitle or "Sin's Toolkit: QOL", 1, 1, 1)
            GameTooltip:AddLine(self.tooltipText, 0.85, 0.76, 0.95, true)
            GameTooltip:Show()
        end
    end)
    btn:SetScript("OnLeave", function(self)
        if self.isNavButton then
            Window:StyleNavButton(self, self.navSelected)
        elseif self.navSelected then
            SetFlatBackdrop(self, theme.accentR * 0.22, theme.accentG * 0.22, theme.accentB * 0.22, 0.98, theme.accentR, theme.accentG, theme.accentB, 0.9)
        else
            SetFlatBackdrop(self, 0.035, 0.035, 0.04, 0.98, 0.14, 0.14, 0.15, 0.9)
        end
        GameTooltip_Hide()
    end)
    btn:SetScript("OnMouseDown", function(self)
        SetFlatBackdrop(self, theme.accentR * 0.22, theme.accentG * 0.22, theme.accentB * 0.22, 1, theme.accentR, theme.accentG, theme.accentB, 0.95)
        if self.isNavButton and SinsToolkit.RegisterOpacityFrame then
            SinsToolkit.RegisterOpacityFrame(self, theme.accentR * 0.22, theme.accentG * 0.22, theme.accentB * 0.22, theme.accentR, theme.accentG, theme.accentB, 0.95)
        end
    end)
    btn:SetScript("OnMouseUp", function(self)
        if self.isNavButton then
            Window:StyleNavButton(self, self.navSelected)
        elseif self.navSelected then
            SetFlatBackdrop(self, theme.accentR * 0.22, theme.accentG * 0.22, theme.accentB * 0.22, 0.98, theme.accentR, theme.accentG, theme.accentB, 0.9)
        else
            SetFlatBackdrop(self, theme.accentR * 0.14, theme.accentG * 0.14, theme.accentB * 0.14, 0.98, theme.accentR, theme.accentG, theme.accentB, 0.85)
        end
    end)
    btn.sinsToolkitStyledTooltip = true
    Window:SetTooltip(btn, text, tooltip)
    return btn
end

function Window:StyleNavButton(button, selected)
    if not button then return end
    local theme = GetTheme()
    button.isNavButton = true
    button.navSelected = selected and true or nil
    if selected then
        SetFlatBackdrop(button, theme.accentR * 0.18, theme.accentG * 0.18, theme.accentB * 0.18, 0.98, theme.accentR, theme.accentG, theme.accentB, 0.95)
        if SinsToolkit.RegisterOpacityFrame then
            SinsToolkit.RegisterOpacityFrame(button, theme.accentR * 0.18, theme.accentG * 0.18, theme.accentB * 0.18, theme.accentR, theme.accentG, theme.accentB, 0.95)
        end
    else
        SetFlatBackdrop(button, 0.025, 0.025, 0.03, 0.98, 0.12, 0.12, 0.13, 0.86)
        if SinsToolkit.RegisterOpacityFrame then
            SinsToolkit.RegisterOpacityFrame(button, 0.025, 0.025, 0.03, 0.12, 0.12, 0.13, 0.86)
        end
    end
end

local elementCounter = 0

function Window:CreateCheckbox(parent, label, getFunc, setFunc, tooltip)
    elementCounter = elementCounter + 1
    local name = "SinsToolkitWindowCB" .. elementCounter
    local cb = CreateFrame("CheckButton", name, parent, "ChatConfigCheckButtonTemplate")
    local txt = _G[name .. "Text"]
    if txt then txt:SetText(label) end
    cb:SetScript("OnShow", function(self) self:SetChecked(getFunc() == true) end)
    cb:SetScript("OnClick", function(self) setFunc(self:GetChecked() == true) end)
    Window:SetTooltip(cb, label, tooltip)
    return cb
end

function Window:CreateEditBox(parent, label, width, height, getFunc, setFunc, tooltip)
    elementCounter = elementCounter + 1
    local name = "SinsToolkitWindowEB" .. elementCounter
    local eb = CreateFrame("EditBox", name, parent, "InputBoxTemplate")
    eb:SetSize(width or 160, height or 24)
    eb:SetAutoFocus(false)
    local txt = eb:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    txt:SetPoint("BOTTOMLEFT", eb, "TOPLEFT", 0, 2)
    txt:SetText(label)
    eb:SetScript("OnShow", function(self) self:SetText(getFunc() or "") end)
    eb:SetScript("OnEnterPressed", function(self) self:ClearFocus() if setFunc then setFunc(self:GetText()) end end)
    eb:SetScript("OnEscapePressed", function(self) self:ClearFocus() self:SetText(getFunc() or "") end)
    Window:SetTooltip(eb, label, tooltip)
    return eb
end

function Window:CreateCycleButton(parent, label, values, getFunc, setFunc, tooltip)
    local btn = Window:CreateButton(parent, "", 160, 24)
    local txt = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    txt:SetPoint("BOTTOMLEFT", btn, "TOPLEFT", 0, 2)
    txt:SetText(label)
    local keys = {}
    for k in pairs(values) do table.insert(keys, k) end
    table.sort(keys)
    local function UpdateText()
        local val = getFunc()
        btn:SetText(values[val] or val or "None")
    end
    btn:SetScript("OnShow", UpdateText)
    btn:SetScript("OnClick", function()
        local current = getFunc()
        local idx = 1
        for i, k in ipairs(keys) do
            if k == current then idx = i + 1 break end
        end
        if idx > #keys then idx = 1 end
        setFunc(keys[idx])
        UpdateText()
    end)
    Window:SetTooltip(btn, label, tooltip)
    return btn
end

function Window:CreateDropdown(parent, label, items, getFunc, setFunc, width, tooltip)
    local values = {}
    for _, item in ipairs(items) do
        values[item.value] = item.text
    end
    return Window:CreateCycleButton(parent, label, values, getFunc, setFunc, tooltip)
end

function Window:CreateDropdownMenu(parent, label, items, getFunc, setFunc, width, tooltip)
    if not UIDropDownMenu_CreateInfo then
        return Window:CreateDropdown(parent, label, items, getFunc, setFunc, width, tooltip)
    end

    elementCounter = elementCounter + 1
    local name = "SinsToolkitWindowDropdown" .. elementCounter
    local dropdown = CreateFrame("Frame", name, parent, "UIDropDownMenuTemplate")
    local text = dropdown:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    text:SetPoint("BOTTOMLEFT", dropdown, "TOPLEFT", 18, 2)
    text:SetText(label)

    local function UpdateText()
        local current = getFunc()
        local display = current
        for _, item in ipairs(items) do
            if item.value == current then
                display = item.text
                break
            end
        end
        UIDropDownMenu_SetText(dropdown, display or "None")
    end

    UIDropDownMenu_SetWidth(dropdown, width or 160)
    UIDropDownMenu_Initialize(dropdown, function(self, level)
        for _, item in ipairs(items) do
            local info = UIDropDownMenu_CreateInfo()
            info.text = item.text
            info.checked = getFunc() == item.value
            info.func = function()
                setFunc(item.value)
                UpdateText()
            end
            UIDropDownMenu_AddButton(info, level)
        end
    end)
    dropdown:SetScript("OnShow", UpdateText)
    UpdateText()
    Window:SetTooltip(dropdown, label, tooltip)
    return dropdown
end

function Window:CreateKeybindButton(parent, label, getFunc, setFunc, tooltip)
    local btn = Window:CreateButton(parent, "", 180, 24)
    local txt = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    txt:SetPoint("BOTTOMLEFT", btn, "TOPLEFT", 0, 2)
    txt:SetText(label)
    btn:EnableKeyboard(false)

    local listening = false
    local ignored = {
        LSHIFT = true, RSHIFT = true, LCTRL = true, RCTRL = true,
        LALT = true, RALT = true, UNKNOWN = true,
    }

    local function UpdateText()
        if listening then
            btn:SetText("Press a key...")
        else
            btn:SetText((getFunc and getFunc()) or "None")
        end
    end

    local function BuildKey(key)
        if not key or key == "" or ignored[key] then return nil end
        if key == "ESCAPE" then return "" end
        local parts = {}
        if IsControlKeyDown and IsControlKeyDown() then parts[#parts + 1] = "CTRL" end
        if IsAltKeyDown and IsAltKeyDown() then parts[#parts + 1] = "ALT" end
        if IsShiftKeyDown and IsShiftKeyDown() then parts[#parts + 1] = "SHIFT" end
        parts[#parts + 1] = key
        return table.concat(parts, "-")
    end

    btn:SetScript("OnShow", UpdateText)
    btn:SetScript("OnClick", function()
        listening = true
        btn:EnableKeyboard(true)
        if btn.SetPropagateKeyboardInput then btn:SetPropagateKeyboardInput(false) end
        UpdateText()
    end)
    btn:SetScript("OnKeyDown", function(self, key)
        if not listening then return end
        local binding = BuildKey(key)
        if not binding then return end
        listening = false
        self:EnableKeyboard(false)
        if self.SetPropagateKeyboardInput then self:SetPropagateKeyboardInput(true) end
        if setFunc then setFunc(binding) end
        UpdateText()
    end)
    btn:SetScript("OnHide", function(self)
        listening = false
        self:EnableKeyboard(false)
        if self.SetPropagateKeyboardInput then self:SetPropagateKeyboardInput(true) end
    end)
    Window:SetTooltip(btn, label, tooltip)
    return btn
end

function Window:CreateSlider(parent, label, getFunc, setFunc, minV, maxV, step, tooltip, formatFunc)
    elementCounter = elementCounter + 1
    local name = "SinsToolkitWindowSlider" .. elementCounter
    local slider = CreateFrame("Slider", name, parent, "OptionsSliderTemplate")
    slider:SetMinMaxValues(minV, maxV)
    slider:SetValueStep(step)
    slider:SetObeyStepOnDrag(true)
    local txt = _G[name .. "Text"]
    if txt then txt:SetText(label) end
    local lLow = _G[name .. "Low"]
    if lLow then lLow:SetText(formatFunc and formatFunc(minV) or minV) end
    local lHigh = _G[name .. "High"]
    if lHigh then lHigh:SetText(formatFunc and formatFunc(maxV) or maxV) end
    local valText = slider:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    valText:SetPoint("TOP", slider, "BOTTOM", 0, 0)
    slider:SetScript("OnShow", function(self)
        local val = getFunc() or minV
        self:SetValue(val)
        valText:SetText(formatFunc and formatFunc(val) or val)
    end)
    slider:SetScript("OnValueChanged", function(self, value)
        setFunc(value)
        valText:SetText(formatFunc and formatFunc(value) or value)
    end)
    Window:SetTooltip(slider, label, tooltip)
    return slider
end

local function CreateWrappedText(parent, point, width, text, font)
    local fs = parent:CreateFontString(nil, "OVERLAY", font or "GameFontHighlight")
    fs:SetPoint(unpack(point))
    fs:SetWidth(width)
    fs:SetJustifyH("LEFT")
    fs:SetJustifyV("TOP")
    fs:SetText(text)
    Window.wrappedTexts[#Window.wrappedTexts + 1] = { text = fs, maxWidth = width or 620 }
    return fs
end

local AUTO_UNSHIFT_TOOLTIP = "Auto Unshift for Druids\n\nWhen enabled, this feature only activates while playing a Feral or Guardian Druid.\n\nIf you are not Feral or Guardian, this feature remains inactive and does not modify any settings.\n\nHelpful for players using GSE (Gnome Sequencer Enhanced), though GSE is not required.\n\nWhile active:\n- Prevents unwanted auto-unshifting behavior\n- Suppresses the SPELL_FAILED_NOT_SHAPESHIFT UI error\n\nChanges are applied safely outside combat and automatically restored when no longer needed."

local MODIFIER_OPTIONS = {
    { value = "SHIFT", text = "Shift" },
    { value = "CTRL", text = "Ctrl" },
    { value = "ALT", text = "Alt" },
    { value = "NONE", text = "None" },
}

local function GetInfoText()
    return "|cffd8a84aEnvironment|r\n"
        .. "Sin's Toolkit is running as a standalone addon.\n"
        .. "All options in this window are managed directly by Sin's Toolkit.\n\n"
        .. "|cffd8a84aFrame Tools|r\n"
        .. "Move and scale supported Blizzard and addon windows.\n"
        .. "- Drag windows to reposition them\n"
        .. "- Scale supported windows from 50%-200%\n"
        .. "- Save automatically per frame\n"
        .. "- Reset scale at any time\n\n"
        .. "|cffd8a84aCore Commands|r\n/stk status shows feature and automation status.\n/stk scan forces a Gear Scanner scan.\n/stktp opens Teleport Hub.\n/stkbug opens Bug Catcher."
end

local function GetMainWindowOpacity()
    return SinsToolkit.GetWindowOpacity and SinsToolkit.GetWindowOpacity() or 0.98
end

local function SetMainWindowOpacity(value)
    if SinsToolkit.SetWindowOpacity then
        SinsToolkit.SetWindowOpacity(value)
    else
        SetToolkitValue("mainWindowOpacity", math.min(1, math.max(0.45, value or 0.98)))
    end
end

local function FormatOpacity(value)
    return ("%d%%"):format(math.floor(((value or 0) * 100) + 0.5))
end

local function CreateOptionGroup(parent, label, options, getFunc, setFunc, tooltip)
    local holder = CreateFrame("Frame", nil, parent)
    holder:SetSize(360, 48)

    local title = holder:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetPoint("TOPLEFT", holder, "TOPLEFT", 0, 0)
    title:SetText(label)

    local buttons = {}
    local function Refresh()
        local current = getFunc()
        for _, button in ipairs(buttons) do
            button:SetChecked(button.value == current)
        end
    end

    for index, option in ipairs(options) do
        local button = CreateFrame("CheckButton", nil, holder, "UICheckButtonTemplate")
        button:SetSize(24, 24)
        button.value = option.value
        local text = holder:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        text:SetPoint("LEFT", button, "RIGHT", -2, 0)
        text:SetText(option.text)
        text:SetTextColor(1, 0.82, 0.28)
        if index == 1 then
            button:SetPoint("TOPLEFT", title, "BOTTOMLEFT", -4, -4)
        else
            button:SetPoint("LEFT", buttons[index - 1], "RIGHT", 72, 0)
        end
        button:SetScript("OnClick", function(self)
            setFunc(self.value)
            Refresh()
        end)
        button:SetScript("OnShow", Refresh)
        Window:SetTooltip(button, label, tooltip)
        buttons[index] = button
    end

    Refresh()
    return holder
end

local function BuildFrameInspectorPage(CreateScrollablePage)
    local frameInspectorPage, frameInspectorChild = CreateScrollablePage("Frame Inspector")
    local fiHeader = Window:CreateSectionHeader(frameInspectorChild, "Frame Inspector")
    CreateWrappedText(frameInspectorChild, {"TOPLEFT", fiHeader, "BOTTOMLEFT", 0, -8}, 680, "Use the Toolkit Hover Inspector for quick live identification, or open Blizzard's built-in /fstack for deeper frame stack debugging.", "GameFontHighlightSmall")

    local function WithFrameInspector(action)
        local inspector = SinsToolkit.DevTools and SinsToolkit.DevTools.FrameInspector
        if inspector and action then action(inspector) end
        if frameInspectorPage.OnShowCallback then frameInspectorPage.OnShowCallback() end
    end

    local hoverSection = CreateFrame("Frame", nil, frameInspectorChild, "BackdropTemplate")
    hoverSection:SetPoint("TOPLEFT", fiHeader, "BOTTOMLEFT", 0, -52)
    hoverSection:SetPoint("RIGHT", frameInspectorChild, "RIGHT", -16, 0)
    hoverSection:SetHeight(168)
    SetFlatBackdrop(hoverSection, 0.018, 0.016, 0.024, 0.88, 0.22, 0.14, 0.30, 0.62)

    local hoverTitle = hoverSection:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    hoverTitle:SetPoint("TOPLEFT", hoverSection, "TOPLEFT", 14, -12)
    hoverTitle:SetText("Toolkit Hover Inspector")

    local hoverText = hoverSection:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    hoverText:SetPoint("TOPLEFT", hoverTitle, "BOTTOMLEFT", 0, -8)
    hoverText:SetWidth(650)
    hoverText:SetJustifyH("LEFT")
    hoverText:SetText("Start inspecting, hover any UI element, then copy the current frame report. The Toolkit window hides while inspecting so it does not capture itself.")

    local startHover = Window:CreateButton(hoverSection, "Start Hover Inspector", 170, 26, function()
        WithFrameInspector(function(inspector) inspector:StartHoverInspector() end)
    end, "Hide the Toolkit window and inspect frames as you move your cursor.")
    startHover:SetPoint("TOPLEFT", hoverText, "BOTTOMLEFT", 0, -18)

    local stopHover = Window:CreateButton(hoverSection, "Stop Hover Inspector", 160, 26, function()
        WithFrameInspector(function(inspector) inspector:StopHoverInspector(false) end)
    end, "Stop the hover overlay.")
    stopHover:SetPoint("LEFT", startHover, "RIGHT", 10, 0)

    local copyCurrent = Window:CreateButton(hoverSection, "Copy Current", 130, 26, function()
        WithFrameInspector(function(inspector) inspector:CopyCurrent() end)
    end, "Open a copyable popup for the current hovered frame report.")
    copyCurrent:SetPoint("LEFT", stopHover, "RIGHT", 10, 0)

    local copyKeyLabel = hoverSection:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    copyKeyLabel:SetPoint("TOPLEFT", startHover, "BOTTOMLEFT", 0, -10)
    copyKeyLabel:SetWidth(220)
    copyKeyLabel:SetJustifyH("LEFT")
    copyKeyLabel:SetText("Copy Key:\nNot Assigned")

    local setCopyKey = Window:CreateButton(hoverSection, "Set Copy Key", 120, 24, function()
        WithFrameInspector(function(inspector)
            inspector:BeginCopyKeyCapture(function()
                if frameInspectorPage.OnShowCallback then frameInspectorPage.OnShowCallback() end
            end)
        end)
    end, "Capture the next key press and bind it to Copy Current.")
    setCopyKey:SetPoint("LEFT", copyKeyLabel, "RIGHT", 12, 0)

    local clearCopyKey = Window:CreateButton(hoverSection, "Clear Copy Key", 120, 24, function()
        WithFrameInspector(function(inspector) inspector:ClearCopyKey() end)
    end, "Remove the Frame Inspector copy key binding.")
    clearCopyKey:SetPoint("LEFT", setCopyKey, "RIGHT", 10, 0)

    local function SetButtonEnabled(button, enabled)
        if enabled then
            button:Enable()
        else
            button:Disable()
        end
    end

    local stackSection = CreateFrame("Frame", nil, frameInspectorChild, "BackdropTemplate")
    stackSection:SetPoint("TOPLEFT", hoverSection, "BOTTOMLEFT", 0, -14)
    stackSection:SetPoint("RIGHT", frameInspectorChild, "RIGHT", -16, 0)
    stackSection:SetHeight(102)
    SetFlatBackdrop(stackSection, 0.018, 0.016, 0.024, 0.84, 0.18, 0.12, 0.25, 0.56)

    local stackTitle = stackSection:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    stackTitle:SetPoint("TOPLEFT", stackSection, "TOPLEFT", 14, -12)
    stackTitle:SetText("Blizzard Frame Stack")

    local stackText = stackSection:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    stackText:SetPoint("TOPLEFT", stackTitle, "BOTTOMLEFT", 0, -8)
    stackText:SetWidth(650)
    stackText:SetJustifyH("LEFT")
    stackText:SetText("Blizzard's built-in /fstack remains available for advanced frame stack inspection. You can also type /fstack directly.")

    local openFStack = Window:CreateButton(stackSection, "Open Blizzard /fstack", 170, 26, function()
        WithFrameInspector(function(inspector) inspector:OpenFrameStack() end)
    end, "Open Blizzard's separate Frame Stack overlay.")
    openFStack:SetPoint("BOTTOMLEFT", stackSection, "BOTTOMLEFT", 14, 14)

    local selectionSection = CreateFrame("Frame", nil, frameInspectorChild, "BackdropTemplate")
    selectionSection:SetPoint("TOPLEFT", stackSection, "BOTTOMLEFT", 0, -14)
    selectionSection:SetPoint("RIGHT", frameInspectorChild, "RIGHT", -16, 0)
    selectionSection:SetHeight(310)
    SetFlatBackdrop(selectionSection, 0.014, 0.013, 0.020, 0.84, 0.18, 0.12, 0.25, 0.56)

    local selectionTitle = selectionSection:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    selectionTitle:SetPoint("TOPLEFT", selectionSection, "TOPLEFT", 14, -12)
    selectionTitle:SetText("Current Selection")

    local fiSummary = selectionSection:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    fiSummary:SetPoint("TOPLEFT", selectionTitle, "BOTTOMLEFT", 0, -10)
    fiSummary:SetPoint("RIGHT", selectionSection, "RIGHT", -14, 0)
    fiSummary:SetJustifyH("LEFT")
    fiSummary:SetJustifyV("TOP")
    fiSummary:SetText("No frame selected. Click Start Hover Inspector, hover a UI element, then reopen /stk to review or copy the current report.")

    local function RefreshFrameInspectorPage()
        local inspector = SinsToolkit.DevTools and SinsToolkit.DevTools.FrameInspector
        if not inspector then
            fiSummary:SetText("Frame Inspector is not loaded.")
            return
        end

        local rows, report, name = inspector:Refresh()
        local selected = inspector:GetSelection()
        SetButtonEnabled(startHover, not inspector:IsHoverInspecting())
        SetButtonEnabled(stopHover, inspector:IsHoverInspecting())
        copyKeyLabel:SetText("Copy Key:\n" .. (inspector:GetCopyKeyText() or "Not Assigned"))
        if not selected or not rows or #rows == 0 then
            fiSummary:SetText("No frame selected. Click Start Hover Inspector, hover a UI element, then reopen /stk to review or copy the current report.")
            return report
        end

        local lines = {
            "Selected: |cffd8a84a" .. (name or "(anonymous)") .. "|r",
            inspector:IsHoverInspecting() and "Mode: Hover Inspector active" or "Mode: Last inspected frame",
            "",
        }
        for i, row in ipairs(rows) do
            if i > 10 then
                lines[#lines + 1] = "...additional details are included in Copy Current."
                break
            end
            lines[#lines + 1] = row.label .. ": " .. row.value
        end
        fiSummary:SetText(table.concat(lines, "\n"))
        return report
    end

    frameInspectorPage.OnShowCallback = function()
        RefreshFrameInspectorPage()
    end
    frameInspectorPage:SetScript("OnHide", function()
        local inspector = SinsToolkit.DevTools and SinsToolkit.DevTools.FrameInspector
        if inspector then inspector:ClearHighlight() end
    end)
    frameInspectorChild:SetHeight(650)
end

function Window:SelectPage(pageName)
    Window.activePage = pageName
    if Window.welcomeText then Window.welcomeText:Hide() end
    for name, page in pairs(Window.pages) do
        if name == pageName then
            page:Show()
            if page.OnShowCallback then page.OnShowCallback() end
        else
            page:Hide()
        end
    end
    if Window.navButtons then
        for _, btn in pairs(Window.navButtons) do
            if btn.pageName == pageName then
                Window:StyleNavButton(btn, true)
                btn:LockHighlight()
            else
                Window:StyleNavButton(btn, false)
                btn:UnlockHighlight()
            end
        end
    end
end

local frame = nil
local isInitialized = false

local function Init()
    if isInitialized then return end
    isInitialized = true

    frame = CreateFrame("Frame", "SinsToolkitWindowFrame", UIParent, "BackdropTemplate")
    frame:SetSize(900, 600)
    frame:SetFrameStrata("DIALOG")
    frame:SetClampedToScreen(true)
    frame:SetMovable(true)
    frame:SetResizable(true)
    if frame.SetResizeBounds then
        frame:SetResizeBounds(760, 480, 1100, 780)
    else
        frame:SetMinResize(760, 480)
    end
    local providerTheme = SinsToolkit.GetTheme and SinsToolkit.GetTheme() or { blizzard = true, panelScale = 1, accentR = 0.72, accentG = 0.41, accentB = 0.88 }
    Window.mainFrame = frame
    Window.mainFrameTheme = providerTheme
    SetFlatBackdrop(frame, 0.01, 0.01, 0.012, GetMainWindowOpacity(), providerTheme.accentR, providerTheme.accentG, providerTheme.accentB, 0.82)
    if SinsToolkit.RegisterOpacityFrame then
        SinsToolkit.RegisterOpacityFrame(frame, 0.01, 0.01, 0.012, providerTheme.accentR, providerTheme.accentG, providerTheme.accentB, 0.82)
    end

    -- Restore persistent position or default to CENTER
    local pos = SinsToolkitDB and SinsToolkitDB.windowPosition
    if pos then
        frame:SetPoint(pos.point, UIParent, pos.relativePoint, pos.x, pos.y)
        if pos.width and pos.height then
            frame:SetSize(math.min(1100, math.max(760, pos.width)), math.min(780, math.max(480, pos.height)))
        end
    else
        frame:SetPoint("CENTER")
    end

    -- Title Area (Used for dragging)
    local dragArea = CreateFrame("Frame", nil, frame)
    dragArea:SetPoint("TOPLEFT", frame, "TOPLEFT", 1, -1)
    dragArea:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -1, -1)
    dragArea:SetHeight(48)
    dragArea:EnableMouse(true)
    dragArea:RegisterForDrag("LeftButton")
    dragArea:SetScript("OnDragStart", function() frame:StartMoving() end)
    dragArea:SetScript("OnDragStop", function()
        frame:StopMovingOrSizing()
        if SinsToolkitDB then
            local point, _, relativePoint, x, y = frame:GetPoint()
            SinsToolkitDB.windowPosition = {
                point = point,
                relativePoint = relativePoint,
                x = x,
            y = y,
            width = frame:GetWidth(),
            height = frame:GetHeight()
            }
        end
    end)

    local resizeGrip = CreateFrame("Button", nil, frame)
    resizeGrip:SetPoint("BOTTOMRIGHT", -4, 4)
    resizeGrip:SetSize(16, 16)
    resizeGrip:SetNormalTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Up")
    resizeGrip:SetHighlightTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Highlight")
    resizeGrip:SetPushedTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Down")
    resizeGrip:SetScript("OnMouseDown", function() frame:StartSizing("BOTTOMRIGHT") end)
    resizeGrip:SetScript("OnMouseUp", function()
        frame:StopMovingOrSizing()
        if SinsToolkitDB then
            local p, _, rp, dx, dy = frame:GetPoint()
            SinsToolkitDB.windowPosition = { point = p, relativePoint = rp, x = dx, y = dy, width = frame:GetWidth(), height = frame:GetHeight() }
        end
    end)

    local headerBg = frame:CreateTexture(nil, "BACKGROUND")
    headerBg:SetColorTexture(0.025, 0.025, 0.03, 0.98)
    headerBg:SetPoint("TOPLEFT", frame, "TOPLEFT", 1, -1)
    headerBg:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -1, -1)
    headerBg:SetHeight(54)
    if SinsToolkit.RegisterOpacityTexture then
        SinsToolkit.RegisterOpacityTexture(headerBg, 0.025, 0.025, 0.03)
    end

    local headerLine = frame:CreateTexture(nil, "ARTWORK")
    headerLine:SetColorTexture(providerTheme.accentR, providerTheme.accentG, providerTheme.accentB, 0.75)
    headerLine:SetPoint("TOPLEFT", headerBg, "BOTTOMLEFT", 0, 0)
    headerLine:SetPoint("TOPRIGHT", headerBg, "BOTTOMRIGHT", 0, 0)
    headerLine:SetHeight(1)

    -- Close Button
    local closeButton = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
    closeButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -6, -6)

    -- Compact in-window branding. Keep text out of the native metal title trim.
    local headerLogo = frame:CreateTexture(nil, "ARTWORK")
    headerLogo:SetTexture("Interface\\AddOns\\SinsToolkit\\Media\\Logo.tga")
    headerLogo:SetSize(30, 30)
    headerLogo:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -12)

    local title = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
    title:SetPoint("LEFT", headerLogo, "RIGHT", 8, 4)
    title:SetText("|cff" .. providerTheme.accentHex .. "Sin's Toolkit: QOL|r")

    local subtitle = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    subtitle:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -2)
    subtitle:SetText("Quality-of-life enhancements")
    subtitle:SetTextColor(0.85, 0.78, 0.95)

    -- Footer Status Text
    local version = (C_AddOns and C_AddOns.GetAddOnMetadata) and C_AddOns.GetAddOnMetadata(addonName, "Version") or GetAddOnMetadata(addonName, "Version") or "Unknown"
    local footer = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    footer:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 16, 14)
    footer:SetText("Sin's Toolkit: QOL v" .. version)
    footer:SetTextColor(0.5, 0.5, 0.5)

    -- Left Navigation Panel
    local navPanel = CreateFrame("Frame", nil, frame)
    navPanel:SetWidth(190)
    navPanel:SetPoint("TOPLEFT", frame, "TOPLEFT", 12, -68)
    navPanel:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 12, 32)
    SetFlatBackdrop(navPanel, 0.012, 0.012, 0.016, 0.94, 0.10, 0.10, 0.11, 0.9)
    if SinsToolkit.RegisterOpacityFrame then
        SinsToolkit.RegisterOpacityFrame(navPanel, 0.012, 0.012, 0.016, 0.10, 0.10, 0.11, 0.9)
    end

    -- Vertical Separator
    local separator = navPanel:CreateTexture(nil, "ARTWORK")
    separator:SetColorTexture(1, 1, 1, 0.1)
    separator:SetWidth(1)
    separator:SetPoint("TOPRIGHT")
    separator:SetPoint("BOTTOMRIGHT")

    Window.pages = {}

    -- Main Content Panel
    local contentPanel = CreateFrame("Frame", nil, frame)
    contentPanel:SetPoint("TOPLEFT", navPanel, "TOPRIGHT", 12, 0)
    contentPanel:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -12, 34)
    SetFlatBackdrop(contentPanel, 0.012, 0.012, 0.016, 0.92, 0.08, 0.08, 0.09, 0.85)
    if SinsToolkit.RegisterOpacityFrame then
        SinsToolkit.RegisterOpacityFrame(contentPanel, 0.012, 0.012, 0.016, 0.08, 0.08, 0.09, 0.85)
    end
    if contentPanel.SetClipsChildren then contentPanel:SetClipsChildren(true) end

    local function RefreshWrappedTextWidths()
        local available = math.max(360, (contentPanel:GetWidth() or 620) - 40)
        for _, wrapped in ipairs(Window.wrappedTexts or {}) do
            if wrapped.text then
                wrapped.text:SetWidth(math.min(wrapped.maxWidth or available, available))
            end
        end
    end
    contentPanel:SetScript("OnSizeChanged", RefreshWrappedTextWidths)

    local welcomeText = contentPanel:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    welcomeText:SetPoint("CENTER")
    welcomeText:SetJustifyH("CENTER")
    welcomeText:SetText("Welcome to Sin's Toolkit: QOL\nSelect a category from the left.")
    welcomeText:SetTextColor(0.8, 0.8, 0.8)
    Window.welcomeText = welcomeText

    local function CreateScrollablePage(name)
        local page = CreateFrame("Frame", nil, contentPanel)
        page:SetAllPoints()
        page:Hide()
        Window.pages[name] = page

        local scroll = CreateFrame("ScrollFrame", nil, page, "UIPanelScrollFrameTemplate")
        scroll:SetPoint("TOPLEFT", 0, 0)
        scroll:SetPoint("BOTTOMRIGHT", -24, 0)
        local child = CreateFrame("Frame", nil, scroll)
        child:SetSize(660, 1)
        scroll:SetScrollChild(child)
        scroll:SetScript("OnSizeChanged", function(self)
            child:SetWidth(math.max(1, self:GetWidth() - 8))
            if page.OnLayoutChanged then page:OnLayoutChanged() end
        end)
        page.scrollChild = child
        page.scrollFrame = scroll
        return page, child
    end

    local function getGV(key) return function() return GetToolkitValue(key) end end
    local function setGV(key) return function(v) SetToolkitValue(key, v) end end
    local function EnabledText(key)
        return GetToolkitValue(key) and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"
    end
    local function BuildFeatureStatusRows()
        local enabledRows = {}
        local disabledRows = {}

        local function AddStatus(label, enabled, detail, disabledDetail)
            local row = {
                label = label,
                detail = enabled and detail or disabledDetail,
            }
            if enabled then
                enabledRows[#enabledRows + 1] = row
            else
                disabledRows[#disabledRows + 1] = row
            end
        end

        local bugBlocked = SinsToolkit.BugCatcher and SinsToolkit.BugCatcher.IsBlockedByExternalAddon and SinsToolkit.BugCatcher:IsBlockedByExternalAddon()
        AddStatus("Gear Scanner", GetToolkitValue("gearScannerEnabled"))
        AddStatus("Bug Catcher", GetToolkitValue("bugCatcherEnabled") and not bugBlocked, nil, bugBlocked and "External BugSack active" or nil)
        AddStatus("Teleport Hub", GetToolkitValue("teleportHubEnabled"))
        AddStatus("Auto Gossip", GetToolkitValue("automateGossip"), GetToolkitValue("automateGossipModifier") or "SHIFT")
        AddStatus("UI Error Filtering", GetToolkitValue("hideOutOfRangeErrors"))
        AddStatus("Quick Links", GetToolkitValue("wowheadSearchEnabled"))
        AddStatus("No Auto Close Panels", GetToolkitValue("keepPanelsOpen"))
        AddStatus("Auto Unshift for Druids", GetToolkitValue("gseAutoUnshift"))
        AddStatus("Profession Hotkey", GetToolkitValue("professionItemHotkeyEnabled"), GetToolkitValue("professionItemHotkeyModifier") or "ALT")
        AddStatus("Frame Tools", GetToolkitValue("frameMoverEnabled"))
        AddStatus("Auto Flight Form", GetToolkitValue("druidAutoFlightForm"))
        AddStatus("Hover Identifier", GetToolkitValue("identifierEnabled"))

        return enabledRows, disabledRows
    end

    local function GetGeneralStatusText()
        return GetProviderStatusText()
            .. "\n\n|cffd8a84aFeature Status|r\n"
            .. "Gear Mode: " .. (SinsToolkit.GetGearProviderModeText and SinsToolkit.GetGearProviderModeText() or "Unavailable.") .. "\n\n"
            .. GetFeatureStatusText()
    end

    local function GetProviderStatusText()
        return "|cffbca7d6Provider:|r " .. ((SinsToolkit.GetProviderDisplayName and SinsToolkit.GetProviderDisplayName()) or "Standalone")
            .. "\n|cffbca7d6Status:|r |cff00ff00Ready|r"
    end

    local function GetFeatureStatusText()
        local enabledRows, disabledRows = BuildFeatureStatusRows()
        local enabledText = {}
        for _, row in ipairs(enabledRows) do
            enabledText[#enabledText + 1] = "|cff00ff00+ |r" .. row.label .. (row.detail and (" |cffbca7d6(" .. row.detail .. ")|r") or "")
        end
        local disabledText = {}
        for _, row in ipairs(disabledRows) do
            disabledText[#disabledText + 1] = "|cffff4040- |r" .. row.label .. (row.detail and (" |cffbca7d6(" .. row.detail .. ")|r") or "")
        end

        return table.concat({
            "|cff00ff00Enabled|r",
            #enabledText > 0 and table.concat(enabledText, "\n") or "None",
            "",
            "|cffff4040Disabled|r",
            #disabledText > 0 and table.concat(disabledText, "\n") or "None",
        }, "\n")
    end

    local function GetGeneralStatusText()
        return GetProviderStatusText()
            .. "\n\n|cffd8a84aFeature Status|r\n"
            .. "Gear Mode: " .. (SinsToolkit.GetGearProviderModeText and SinsToolkit.GetGearProviderModeText() or "Unavailable.") .. "\n\n"
            .. GetFeatureStatusText()
    end

    -- Information Page Frame
    local infoPage, infoChild = CreateScrollablePage("Information")
    local infoHeader = Window:CreateSectionHeader(infoChild, "Information")
    local UI = SinsToolkit.UIComponents

    local banner = infoChild:CreateTexture(nil, "ARTWORK")
    banner:SetTexture("Interface\\AddOns\\SinsToolkit\\Media\\Banner.tga")
    banner:SetTexCoord(0, 1, 0, 1)
    banner:SetSize(560, 186)

    local statusCard = UI and UI:CreateCard(infoChild, "Toolkit Status", "Interface\\AddOns\\SinsToolkit\\Media\\Logo.tga", 360, 132) or CreateFrame("Frame", nil, infoChild, "BackdropTemplate")
    local providerLine = statusCard:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    providerLine:SetPoint("TOPLEFT", 18, -48)
    providerLine:SetJustifyH("LEFT")
    local statusBadge = UI and UI:CreateStatusBadge(statusCard, "Ready", "enabled")
    local infoMainMinimapToggle = Window:CreateCheckbox(statusCard, "Show Minimap Button", GetMainMinimapVisible, SetMainMinimapVisible, "Shows or hides the main Sin's Toolkit minimap launcher. Left-click opens Sin's Toolkit; right-click opens Teleport Hub.")

    local featureCard = UI and UI:CreateCard(infoChild, "Feature Status", nil, 360, 260) or CreateFrame("Frame", nil, infoChild, "BackdropTemplate")
    local enabledTitle = featureCard:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    enabledTitle:SetText("|cff00ff00Enabled|r")
    local disabledTitle = featureCard:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    disabledTitle:SetText("|cffff4040Disabled|r")
    local featureLabels = {}
    for i = 1, 24 do
        local fs = featureCard:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        fs:SetJustifyH("LEFT")
        featureLabels[i] = fs
    end

    local environmentCard = UI and UI:CreateCard(infoChild, "Environment", nil, 260, 120) or CreateFrame("Frame", nil, infoChild, "BackdropTemplate")
    local environmentText = environmentCard:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    environmentText:SetPoint("TOPLEFT", 18, -40)
    environmentText:SetPoint("RIGHT", -18, 0)
    environmentText:SetJustifyH("LEFT")
    environmentText:SetText("Sin's Toolkit adapts to the active UI framework.\n\nCurrent Mode: |cffb868dc" .. ((SinsToolkit.GetProviderDisplayName and SinsToolkit.GetProviderDisplayName()) or "Standalone") .. "|r")

    local actionsCard = UI and UI:CreateCard(infoChild, "Quick Actions", nil, 260, 150) or CreateFrame("Frame", nil, infoChild, "BackdropTemplate")
    local btnRunScan = UI and UI:CreateActionButton(actionsCard, "Run Gear Scan", function()
        if SinsToolkit.ForceGearScan then SinsToolkit.ForceGearScan() end
    end, "Runs /stk scan with your current Gear Scanner settings.", 150) or CreateFrame("Button", nil, actionsCard, "UIPanelButtonTemplate")
    local btnOpenTeleports = UI and UI:CreateActionButton(actionsCard, "Open Teleport Hub", function()
        if SinsToolkit.TeleportHub and SinsToolkit.TeleportHub.Open then SinsToolkit.TeleportHub:Open() end
    end, "Opens the Teleport Hub. Slash command: /stktp.", 150) or CreateFrame("Button", nil, actionsCard, "UIPanelButtonTemplate")
    local btnOpenBug = UI and UI:CreateActionButton(actionsCard, "Open Bug Catcher", function()
        if SinsToolkit.BugCatcher and SinsToolkit.BugCatcher.OpenSack then SinsToolkit.BugCatcher:OpenSack() end
    end, "Opens the Bug Catcher. Slash command: /stkbug.", 150) or CreateFrame("Button", nil, actionsCard, "UIPanelButtonTemplate")
    local btnOpenProfiles = UI and UI:CreateActionButton(actionsCard, "Open Profile Vault", function()
        Window:SelectPage("Profiles")
    end, "Opens Profile Vault for backups and restores.", 150) or CreateFrame("Button", nil, actionsCard, "UIPanelButtonTemplate")

    infoChild:SetHeight(640)
    local function LayoutInformationPage()
        local availableWidth = math.max(420, (infoPage:GetWidth() or 700) - 44)
        local bannerAspect = 2
        local bannerWidth = math.min(availableWidth - 16, 900)
        local bannerHeight = math.floor(bannerWidth / bannerAspect)
        bannerHeight = math.max(120, math.min(180, bannerHeight))
        bannerWidth = math.floor(bannerHeight * bannerAspect)

        infoChild:SetWidth(math.max(availableWidth, bannerWidth + 40))
        banner:ClearAllPoints()
        banner:SetSize(bannerWidth, bannerHeight)
        banner:SetPoint("TOP", infoChild, "TOP", 0, -46)

        local gap = 12
        local leftWidth = math.max(300, math.floor((availableWidth - gap - 40) * 0.42))
        local rightWidth = math.max(300, availableWidth - leftWidth - gap - 40)
        local topY = -(bannerHeight + 64)

        statusCard:ClearAllPoints()
        statusCard:SetPoint("TOPLEFT", infoChild, "TOPLEFT", 20, topY)
        statusCard:SetSize(leftWidth, 122)
        providerLine:SetText("|cffbca7d6Provider:|r " .. ((SinsToolkit.GetProviderDisplayName and SinsToolkit.GetProviderDisplayName()) or "Standalone"))
        if statusBadge then
            statusBadge:ClearAllPoints()
            statusBadge:SetPoint("TOPRIGHT", statusCard, "TOPRIGHT", -18, -44)
        end
        infoMainMinimapToggle:ClearAllPoints()
        infoMainMinimapToggle:SetPoint("TOPLEFT", statusCard, "TOPLEFT", 14, -80)

        environmentCard:ClearAllPoints()
        environmentCard:SetPoint("TOPLEFT", statusCard, "BOTTOMLEFT", 0, -gap)
        environmentCard:SetSize(leftWidth, 96)
        environmentText:SetText("Sin's Toolkit is running as a standalone addon.\nCurrent Mode: |cffb868dc" .. ((SinsToolkit.GetProviderDisplayName and SinsToolkit.GetProviderDisplayName()) or "Standalone") .. "|r")

        actionsCard:ClearAllPoints()
        actionsCard:SetPoint("TOPLEFT", environmentCard, "BOTTOMLEFT", 0, -gap)
        actionsCard:SetSize(leftWidth, leftWidth >= 340 and 116 or 184)
        btnRunScan:ClearAllPoints()
        btnRunScan:SetPoint("TOPLEFT", actionsCard, "TOPLEFT", 18, -44)
        btnOpenTeleports:ClearAllPoints()
        if leftWidth >= 340 then
            btnOpenTeleports:SetPoint("LEFT", btnRunScan, "RIGHT", 12, 0)
        else
            btnOpenTeleports:SetPoint("TOPLEFT", btnRunScan, "BOTTOMLEFT", 0, -10)
        end
        btnOpenBug:ClearAllPoints()
        btnOpenBug:SetPoint("TOPLEFT", btnOpenTeleports, "BOTTOMLEFT", 0, -10)
        btnOpenProfiles:ClearAllPoints()
        if leftWidth >= 340 then
            btnOpenProfiles:SetPoint("LEFT", btnOpenBug, "RIGHT", 12, 0)
        else
            btnOpenProfiles:SetPoint("TOPLEFT", btnOpenBug, "BOTTOMLEFT", 0, -10)
        end

        featureCard:ClearAllPoints()
        featureCard:SetPoint("TOPLEFT", statusCard, "TOPRIGHT", gap, 0)
        featureCard:SetSize(rightWidth, 390)
        enabledTitle:ClearAllPoints()
        enabledTitle:SetPoint("TOPLEFT", featureCard, "TOPLEFT", 18, -42)
        disabledTitle:ClearAllPoints()
        disabledTitle:SetPoint("TOPLEFT", featureCard, "TOPLEFT", math.max(180, math.floor(rightWidth * 0.52)), -42)

        for _, fs in ipairs(featureLabels) do
            fs:Hide()
        end

        local enabledRows, disabledRows = BuildFeatureStatusRows()
        local function DrawRows(rows, startIndex, anchor, enabled)
            for i, row in ipairs(rows) do
                local fs = featureLabels[startIndex + i - 1]
                if fs then
                    fs:ClearAllPoints()
                    fs:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -7 - ((i - 1) * 18))
                    fs:SetWidth(math.max(130, math.floor(rightWidth * 0.42)))
                    fs:SetText((enabled and "|cff00ff00+ |r" or "|cffff4040- |r") .. row.label .. (row.detail and (" |cffbca7d6(" .. row.detail .. ")|r") or ""))
                    fs:Show()
                end
            end
        end
        DrawRows(enabledRows, 1, enabledTitle, true)
        DrawRows(disabledRows, 13, disabledTitle, false)

        infoChild:SetHeight(math.max(610, bannerHeight + 500))
    end
    infoPage.OnLayoutChanged = LayoutInformationPage
    infoPage.OnShowCallback = LayoutInformationPage

    -- Changelog Page Frame
    local changelogPage, changelogChild = CreateScrollablePage("Changelog")
    local changelogHeader = Window:CreateSectionHeader(changelogChild, "Changelog")
    CreateWrappedText(changelogChild, {"TOPLEFT", changelogHeader, "BOTTOMLEFT", 0, -14}, 620, SinsToolkit:GetChangelogText(), "GameFontHighlight")
    changelogChild:SetHeight(420)

    -- General Page Frame
    local generalPage = CreateFrame("Frame", nil, contentPanel)
    generalPage:SetAllPoints()
    generalPage:Hide()
    Window.pages["General"] = generalPage

    local header = Window:CreateSectionHeader(generalPage, "Sin's Toolkit: QOL")

    local subHeader = generalPage:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    subHeader:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, -8)
    subHeader:SetText("Quality-of-life enhancements")

    local statusHeader = generalPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    statusHeader:SetPoint("TOPLEFT", subHeader, "BOTTOMLEFT", 0, -20)
    statusHeader:SetText("Status Section")

    local versionText = generalPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    versionText:SetPoint("TOPLEFT", statusHeader, "BOTTOMLEFT", 0, -8)
    local version = (C_AddOns and C_AddOns.GetAddOnMetadata) and C_AddOns.GetAddOnMetadata(addonName, "Version") or GetAddOnMetadata(addonName, "Version") or "Unknown"
    versionText:SetText("Version: " .. version)

    local providerText = generalPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    providerText:SetPoint("TOPLEFT", versionText, "BOTTOMLEFT", 0, -4)
    providerText:SetText("Provider: " .. ((SinsToolkit.GetProviderDisplayName and SinsToolkit.GetProviderDisplayName()) or "Standalone"))

    local statusText = generalPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    statusText:SetPoint("TOPLEFT", providerText, "BOTTOMLEFT", 0, -4)
    statusText:SetWidth(760)
    statusText:SetJustifyH("LEFT")
    statusText:SetText(GetGeneralStatusText())
    
    generalPage.OnShowCallback = function()
        statusText:SetText(GetGeneralStatusText())
    end

    local mainMinimapToggle = Window:CreateCheckbox(generalPage, "Show Main Minimap Icon", GetMainMinimapVisible, SetMainMinimapVisible, "Shows or hides the main Sin's Toolkit minimap launcher. The Teleport Hub and Bug Catcher have their own minimap controls on their own tabs.")
    mainMinimapToggle:SetPoint("TOPLEFT", statusText, "BOTTOMLEFT", 0, -14)

    local opacitySlider = Window:CreateSlider(generalPage, "Window Opacity", GetMainWindowOpacity, SetMainWindowOpacity, 0.45, 1, 0.01, "Adjusts Toolkit-owned dark window backgrounds. Text and controls remain fully readable.", FormatOpacity)
    opacitySlider:SetPoint("TOPLEFT", mainMinimapToggle, "BOTTOMLEFT", 4, -28)

    local quickActionsHeader = generalPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    quickActionsHeader:SetPoint("TOPLEFT", opacitySlider, "BOTTOMLEFT", -4, -24)
    quickActionsHeader:SetText("Quick Actions")

    local btnScan = CreateFrame("Button", nil, generalPage, "UIPanelButtonTemplate")
    btnScan:SetSize(160, 30)
    btnScan:SetPoint("TOPLEFT", quickActionsHeader, "BOTTOMLEFT", 0, -10)
    btnScan:SetText("Run Gear Scan")
    btnScan:SetScript("OnClick", function()
        if SinsToolkit.ForceGearScan then
            SinsToolkit.ForceGearScan()
        else
            print("|cff9b59b6[Sin's Toolkit: QOL]|r Gear Scanner unavailable.")
        end
    end)
    Window:SetTooltip(btnScan, "Run Gear Scan", "Runs an immediate bag scan using the current Gear Scanner settings. This is the same action as /stk scan.")

    local btnHub = CreateFrame("Button", nil, generalPage, "UIPanelButtonTemplate")
    btnHub:SetSize(160, 30)
    btnHub:SetPoint("LEFT", btnScan, "RIGHT", 10, 0)
    btnHub:SetText("Open Teleports")
    btnHub:SetScript("OnClick", function()
        Window:SelectPage("Teleports")
    end)
    Window:SetTooltip(btnHub, "Open Teleports", "Switches to the Teleports tab where you can configure and open the floating Teleport Hub.")

    local aboutHeader = generalPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    aboutHeader:SetPoint("TOPLEFT", btnScan, "BOTTOMLEFT", 0, -20)
    aboutHeader:SetText("About")

    local aboutText = generalPage:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    aboutText:SetPoint("TOPLEFT", aboutHeader, "BOTTOMLEFT", 0, -8)
    aboutText:SetJustifyH("LEFT")
    aboutText:SetWidth(740)
    aboutText:SetText("Sin's Toolkit: QOL is a standalone World of Warcraft addon for practical account tools, frame utilities, error capture, lookups, and developer-facing inspection helpers. It no longer depends on a UI provider or plugin host.")

    -- Teleports Page Frame
    local teleportsPage = CreateFrame("Frame", nil, contentPanel)
    teleportsPage:SetAllPoints()
    teleportsPage:Hide()
    Window.pages["Teleports"] = teleportsPage

    local teleportHeader = teleportsPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    teleportHeader:SetPoint("TOPLEFT", 12, -12)
    teleportHeader:SetText("Teleport Hub")

    CreateWrappedText(teleportsPage, {"TOPLEFT", teleportHeader, "BOTTOMLEFT", 0, -6}, 720, "Search and use known teleports, hearthstones, portals, and teleport toys. The floating hub supports a Scotty-style menu selector, Favorites, Recently Used, collapsible sections, grid/list browsing, and a separate minimap button.", "GameFontHighlightSmall")

    local teleportEnabled = Window:CreateCheckbox(teleportsPage, "Enable Teleport Hub", getGV("teleportHubEnabled"), setGV("teleportHubEnabled"), "Enables Sin's Toolkit QOL Teleport Hub and the /stktp commands.")
    teleportEnabled:SetPoint("TOPLEFT", teleportHeader, "BOTTOMLEFT", -4, -46)

    local teleportMinimap = Window:CreateCheckbox(teleportsPage, "Show Teleport Minimap Icon", getGV("teleportHubMinimap"), setGV("teleportHubMinimap"), "Shows a small minimap button for opening Teleport Hub. You can also use /stktp if you prefer no minimap button.")
    teleportMinimap:SetPoint("LEFT", teleportEnabled, "RIGHT", 170, 0)

    local teleportOpen = Window:CreateButton(teleportsPage, "Open Floating Hub", 150, 24, function()
        if SinsToolkit.TeleportHub and SinsToolkit.TeleportHub.Open then
            SinsToolkit.TeleportHub:Open()
        end
    end, "Opens the floating Teleport Hub panel. Same as /stktp.")
    teleportOpen:SetPoint("LEFT", teleportMinimap, "RIGHT", 200, 0)

    local teleportView = Window:CreateDropdown(teleportsPage, "Default Hub View", {
        { value = "menu", text = "Menu" },
        { value = "grid", text = "Grid" },
        { value = "list", text = "List" },
    }, getGV("teleportHubView"), setGV("teleportHubView"), 160, "Choose the default Teleport Hub layout. Menu is a compact dropdown-style selector; Grid is visual browsing; List is compact rows.")
    teleportView:SetPoint("TOPLEFT", teleportEnabled, "BOTTOMLEFT", 4, -24)

    local teleportLocked = Window:CreateCheckbox(teleportsPage, "Show Locked Teleports", getGV("teleportHubShowLocked"), setGV("teleportHubShowLocked"), "Shows teleports, toys, or class options that this character has not learned or does not own yet.")
    teleportLocked:SetPoint("LEFT", teleportView, "RIGHT", 38, 0)

    local teleportNames = Window:CreateCheckbox(teleportsPage, "Show Destination Names", getGV("teleportHubShowDungeonNames"), setGV("teleportHubShowDungeonNames"), "Uses the Toolkit destination names where available instead of relying only on raw spell names.")
    teleportNames:SetPoint("LEFT", teleportLocked, "RIGHT", 190, 0)

    local teleportScan = Window:CreateButton(teleportsPage, "Rescan Teleports", 150, 24, function()
        if SinsToolkit.TeleportHub and SinsToolkit.TeleportHub.Scan then
            SinsToolkit.TeleportHub:Scan(true)
        end
    end, "Refreshes known teleport, hearthstone, toy, and class teleport availability for the current character.")
    teleportScan:SetPoint("TOPLEFT", teleportView, "BOTTOMLEFT", -4, -24)

    CreateWrappedText(teleportsPage, {"TOPLEFT", teleportScan, "BOTTOMLEFT", 4, -16}, 720, "Use /stktp, the Teleport Hub minimap button, or your WoW keybinding to open the hub. /stktp menu, /stktp grid, and /stktp list switch views directly.", "GameFontHighlightSmall")

    local teleportsContainer = CreateFrame("Frame", nil, teleportsPage)
    teleportsContainer:SetPoint("TOPLEFT", teleportsPage, "TOPLEFT", 0, -170)
    teleportsContainer:SetPoint("BOTTOMRIGHT", teleportsPage, "BOTTOMRIGHT", 0, 0)

    teleportsPage.OnShowCallback = function()
        if SinsToolkit.TeleportHub and SinsToolkit.TeleportHub.RenderEmbedded then
            SinsToolkit.TeleportHub:RenderEmbedded(teleportsContainer)
        end
    end

    -- Gear Scanner Page Frame
    local gearPage = CreateFrame("Frame", nil, contentPanel)
    gearPage:SetAllPoints()
    gearPage:Hide()
    Window.pages["Gear"] = gearPage

    local gHeader = Window:CreateSectionHeader(gearPage, "Gear Scanner")
    CreateWrappedText(gearPage, {"TOPLEFT", gHeader, "BOTTOMLEFT", 0, -6}, 720, "Scans bag gear for upgrades. Pawn is preferred when installed; otherwise Gear Scanner falls back to ClassCodex stat priorities. Armor type, locked slots, unbound items, max-level behavior, loot rolls, and soulbind confirmation options are controlled here.", "GameFontHighlightSmall")
    
    local gActiveStatus = gearPage:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    gActiveStatus:SetPoint("TOPLEFT", gHeader, "BOTTOMLEFT", 0, -46)

    local gStatus = gearPage:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    gStatus:SetPoint("TOPLEFT", gActiveStatus, "BOTTOMLEFT", 0, -4)
    gStatus:SetJustifyH("LEFT")
    
    local gLastScan = gearPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    gLastScan:SetPoint("TOPLEFT", gStatus, "BOTTOMLEFT", 0, -4)
    gLastScan:SetText("Last Scan: Never")

    gearPage.OnShowCallback = function()
        local isEnabled = GetToolkitValue("gearScannerEnabled")
        gActiveStatus:SetText("Feature: " .. (isEnabled and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r"))

        local pawnActive = SinsToolkit.IsPawnAvailable and SinsToolkit.IsPawnAvailable()
        local classCodexActive = SinsToolkit.IsClassCodexAvailable and SinsToolkit.IsClassCodexAvailable()
        gStatus:SetText(("Providers: Pawn %s  |  ClassCodex %s\nMode: %s"):format(
            pawnActive and "|cff00ff00Active|r" or "|cffff4040Inactive|r",
            classCodexActive and "|cff00ff00Active|r" or "|cffff4040Inactive|r",
            SinsToolkit.GetGearProviderModeText and SinsToolkit.GetGearProviderModeText() or "Unavailable."
        ))
        gLastScan:SetText("Last Scan: " .. (Window.lastGearScanTime or "Never"))
    end

    local gEnable = Window:CreateCheckbox(gearPage, "Enable Gear Scanner", getGV("gearScannerEnabled"), setGV("gearScannerEnabled"), "Master switch for Gear Scanner. When enabled, Sin's Toolkit can scan bag items, compare them against equipped gear, and equip upgrades.")
    gEnable:SetPoint("TOPLEFT", gLastScan, "BOTTOMLEFT", 0, -16)

    local gBtnScan2 = Window:CreateButton(gearPage, "Force Gear Scan", 160, 30, function()
        if SinsToolkit.ForceGearScan then 
            SinsToolkit.ForceGearScan() 
            Window.lastGearScanTime = date("%Y-%m-%d %H:%M:%S")
            gLastScan:SetText("Last Scan: " .. Window.lastGearScanTime)
        end
    end, "Runs an immediate bag scan using the current Gear Scanner settings. This is the same action as /stk scan.")
    gBtnScan2:SetPoint("LEFT", gEnable, "RIGHT", 160, 0)

    local gMaxLevel = Window:CreateCheckbox(gearPage, "Disable at Max Level", getGV("gearScannerMaxLevelDisable"), setGV("gearScannerMaxLevelDisable"), "Turns Gear Scanner off automatically when your character reaches the current expansion level cap.")
    gMaxLevel:SetPoint("TOPLEFT", gEnable, "BOTTOMLEFT", 0, -8)

    local gSilent = Window:CreateCheckbox(gearPage, "Silent Mode", getGV("gearScannerSilent"), setGV("gearScannerSilent"), "Suppresses Gear Scanner chat messages when items are equipped, scans are run, or loot-roll actions are taken.")
    gSilent:SetPoint("TOPLEFT", gMaxLevel, "BOTTOMLEFT", 0, -8)

    local gScanOnLoot = Window:CreateCheckbox(gearPage, "Scan When Bags Change", getGV("gearScannerScanOnLoot"), setGV("gearScannerScanOnLoot"), "Automatically scans your bags after loot or bag changes. Turn this off if you only want manual scans.")
    gScanOnLoot:SetPoint("TOPLEFT", gSilent, "BOTTOMLEFT", 0, -8)

    local gAllowUnbound = Window:CreateCheckbox(gearPage, "Allow Unbound Upgrades", getGV("gearScannerAllowUnbound"), setGV("gearScannerAllowUnbound"), "Allows Gear Scanner to equip unbound upgrades from your bags. Leave this off if you want BoE items left untouched.")
    gAllowUnbound:SetPoint("TOPLEFT", gScanOnLoot, "BOTTOMLEFT", 0, -8)

    local gUsePawn = Window:CreateCheckbox(gearPage, "Use Pawn When Installed", getGV("gearScannerUsePawn"), setGV("gearScannerUsePawn"), "When Pawn is installed, Sin's Toolkit asks Pawn's upgrade advisor first. If Pawn cannot evaluate an item, Gear Scanner falls back to ClassCodex priorities.")
    gUsePawn:SetPoint("TOPLEFT", gAllowUnbound, "BOTTOMLEFT", 0, -8)

    local gScoreTooltips = Window:CreateCheckbox(gearPage, "Show Item Score", getGV("gearScannerScoreTooltips"), setGV("gearScannerScoreTooltips"), "Adds a Sin's Toolkit gear score line to item tooltips.")
    gScoreTooltips:SetPoint("TOPLEFT", gUsePawn, "BOTTOMLEFT", 0, -8)

    local gReasonsTooltips = Window:CreateCheckbox(gearPage, "Show Won't-Equip Reasons", getGV("gearScannerReasonsTooltips"), setGV("gearScannerReasonsTooltips"), "Adds a tooltip note explaining why an item will or will not equip, such as locked slots, no upgrade, unbound protection, or provider fallback behavior.")
    gReasonsTooltips:SetPoint("TOPLEFT", gScoreTooltips, "BOTTOMLEFT", 0, -8)

    local gMinUpgrade = Window:CreateSlider(gearPage, "Minimum Upgrade Score", getGV("gearScannerMinimumUpgrade"), setGV("gearScannerMinimumUpgrade"), 0, 500, 5, "Requires a new item to beat your equipped item by at least this many score points before it is equipped.")
    gMinUpgrade:SetPoint("TOPLEFT", gReasonsTooltips, "BOTTOMLEFT", 4, -20)

    local lootHeader = gearPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    lootHeader:SetPoint("TOPLEFT", gLastScan, "BOTTOMLEFT", 330, -58)
    lootHeader:SetText("Loot Roll Behavior")

    local gRollGreens = Window:CreateCheckbox(gearPage, "Roll on greens and BoP blues", getGV("gearScannerAutoRollGreensBopBlues"), setGV("gearScannerAutoRollGreensBopBlues"), "Automatically handles green gear and blue bind-on-pickup gear loot rolls using your Gear Scanner rules.")
    gRollGreens:SetPoint("TOPLEFT", lootHeader, "BOTTOMLEFT", 0, -10)

    local gRollBoeBlues = Window:CreateCheckbox(gearPage, "Roll on BoE blues", getGV("gearScannerAutoRollBoeBlues"), setGV("gearScannerAutoRollBoeBlues"), "Automatically handles blue bind-on-equip gear loot rolls. Use carefully if you prefer to sell or trade BoE blues.")
    gRollBoeBlues:SetPoint("TOPLEFT", gRollGreens, "BOTTOMLEFT", 0, -8)

    local gRollEpics = Window:CreateCheckbox(gearPage, "Roll on epics", getGV("gearScannerAutoRollEpics"), setGV("gearScannerAutoRollEpics"), "Automatically handles epic gear loot rolls. This is off by default because many players want manual control over epic loot.")
    gRollEpics:SetPoint("TOPLEFT", gRollBoeBlues, "BOTTOMLEFT", 0, -8)

    local gRollNonGear = Window:CreateCheckbox(gearPage, "Roll on non-gear loot", getGV("gearScannerRollNonGearLoot"), setGV("gearScannerRollNonGearLoot"), "Automatically rolls Greed on handled non-gear loot. This does not evaluate upgrades because the item is not equippable gear.")
    gRollNonGear:SetPoint("TOPLEFT", gRollEpics, "BOTTOMLEFT", 0, -8)

    local gNeverNeed = Window:CreateCheckbox(gearPage, "Never auto-need; only greed", getGV("gearScannerNeverAutoNeed"), setGV("gearScannerNeverAutoNeed"), "Prevents Sin's Toolkit from rolling Need automatically. Upgrade candidates will roll Greed instead when Greed is available.")
    gNeverNeed:SetPoint("TOPLEFT", gRollNonGear, "BOTTOMLEFT", 0, -8)

    local bindHeader = gearPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    bindHeader:SetPoint("TOPLEFT", gNeverNeed, "BOTTOMLEFT", 0, -18)
    bindHeader:SetText("Soulbind Confirmations")

    local gBindOther = Window:CreateCheckbox(gearPage, "Confirm non-blue/non-epic binds", getGV("gearScannerAutoConfirmBindOther"), setGV("gearScannerAutoConfirmBindOther"), "Automatically confirms the soulbind popup when Gear Scanner equips an upgrade below blue quality.")
    gBindOther:SetPoint("TOPLEFT", bindHeader, "BOTTOMLEFT", 0, -10)

    local gBindBlues = Window:CreateCheckbox(gearPage, "Confirm blue binds", getGV("gearScannerAutoConfirmBindBlues"), setGV("gearScannerAutoConfirmBindBlues"), "Automatically confirms the soulbind popup when Gear Scanner equips a blue upgrade.")
    gBindBlues:SetPoint("TOPLEFT", gBindOther, "BOTTOMLEFT", 0, -8)

    local gBindEpics = Window:CreateCheckbox(gearPage, "Confirm epic binds", getGV("gearScannerAutoConfirmBindEpics"), setGV("gearScannerAutoConfirmBindEpics"), "Automatically confirms the soulbind popup when Gear Scanner equips an epic upgrade. This is off by default because epic items are usually worth reviewing manually.")
    gBindEpics:SetPoint("TOPLEFT", gBindBlues, "BOTTOMLEFT", 0, -8)

    local lockHeader = gearPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    lockHeader:SetPoint("TOPLEFT", gMinUpgrade, "BOTTOMLEFT", -4, -30)
    lockHeader:SetText("Locked Slots")

    local gLockSlots = Window:CreateCheckbox(gearPage, "Enable locked gear slots", getGV("gearScannerLockSlots"), setGV("gearScannerLockSlots"), "Enables the locked slot list below. Locked slots are still scored, but Gear Scanner will not replace items in those slots.")
    gLockSlots:SetPoint("TOPLEFT", lockHeader, "BOTTOMLEFT", -4, -8)

    local slotValues = {
        { INVSLOT_HEAD, HEADSLOT or "Head" },
        { INVSLOT_NECK, NECKSLOT or "Neck" },
        { INVSLOT_SHOULDER, SHOULDERSLOT or "Shoulders" },
        { INVSLOT_BACK, BACKSLOT or "Back" },
        { INVSLOT_CHEST, CHESTSLOT or "Chest" },
        { INVSLOT_WRIST, WRISTSLOT or "Wrist" },
        { INVSLOT_HAND, HANDSSLOT or "Hands" },
        { INVSLOT_WAIST, WAISTSLOT or "Waist" },
        { INVSLOT_LEGS, LEGSSLOT or "Legs" },
        { INVSLOT_FEET, FEETSLOT or "Feet" },
        { INVSLOT_FINGER1, FINGER0SLOT or "Finger 1" },
        { INVSLOT_FINGER2, FINGER1SLOT or "Finger 2" },
        { INVSLOT_TRINKET1, TRINKET0SLOT or "Trinket 1" },
        { INVSLOT_TRINKET2, TRINKET1SLOT or "Trinket 2" },
        { INVSLOT_MAINHAND, MAINHANDSLOT or "Main Hand" },
        { INVSLOT_OFFHAND, SECONDARYHANDSLOT or "Off Hand" },
    }

    local slotAnchor = gLockSlots
    for i, slot in ipairs(slotValues) do
        local slotID, slotName = slot[1], slot[2]
        local slotCheck = Window:CreateCheckbox(gearPage, slotName, function()
            return SinsToolkitDB and SinsToolkitDB.gearScannerLockedSlots and SinsToolkitDB.gearScannerLockedSlots[slotID] == true
        end, function(value)
            if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
            SinsToolkitDB.gearScannerLockedSlots = SinsToolkitDB.gearScannerLockedSlots or {}
            SinsToolkitDB.gearScannerLockedSlots[slotID] = value or nil
            if SinsToolkit.TriggerUpdate then SinsToolkit.TriggerUpdate("gearScannerLockedSlots") end
        end)
        if i == 1 then
            slotCheck:SetPoint("TOPLEFT", slotAnchor, "BOTTOMLEFT", 0, -8)
        elseif (i - 1) % 4 == 0 then
            slotCheck:SetPoint("TOPLEFT", slotValues[i - 4].checkbox, "BOTTOMLEFT", 0, -8)
        else
            slotCheck:SetPoint("LEFT", slotValues[i - 1].checkbox, "RIGHT", 110, 0)
        end
        slot.checkbox = slotCheck
    end

    -- Profiles Page Frame
    local profilesPage = CreateFrame("Frame", nil, contentPanel)
    profilesPage:SetAllPoints()
    profilesPage:Hide()
    Window.pages["Profiles"] = profilesPage

    local pHeader = Window:CreateSectionHeader(profilesPage, "Profile Vault")

    local pIntro = profilesPage:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    pIntro:SetPoint("TOPLEFT", pHeader, "BOTTOMLEFT", 0, -8)
    pIntro:SetWidth(720)
    pIntro:SetJustifyH("LEFT")
    pIntro:SetText("Save and restore character setup backups. Backups include action bars, account macros, character macros, keybinds, character name, realm, class, backup date, and your custom backup name. Restore and delete actions require confirmation.")

    local pBackupNameLabel = profilesPage:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    pBackupNameLabel:SetPoint("TOPLEFT", pIntro, "BOTTOMLEFT", 0, -16)
    pBackupNameLabel:SetText("Backup Name:")

    local pBackupNameInput = CreateFrame("EditBox", nil, profilesPage, "InputBoxTemplate")
    pBackupNameInput:SetSize(200, 24)
    pBackupNameInput:SetPoint("LEFT", pBackupNameLabel, "RIGHT", 10, 0)
    pBackupNameInput:SetAutoFocus(false)
    Window:SetTooltip(pBackupNameInput, "Backup Name", "Name this backup before saving your current action bars, macros, and keybinds.")

    local pBtnCreate = Window:CreateButton(profilesPage, "Create Backup", 160, 30, function()
        if not SinsToolkit.ProfileVault then return end
        local name = pBackupNameInput:GetText()
        local backup = SinsToolkit.ProfileVault:CreateBackup(name)
        Window.selectedProfileVaultBackup = backup.id
        print("|cff9b59b6[Sin's Toolkit: QOL]|r Profile backup saved: " .. backup.name)
        if Window.RefreshProfilesPage then Window:RefreshProfilesPage() end
    end, "Saves this character's action bars, account macros, character macros, and keybinds into Profile Vault.")
    pBtnCreate:SetPoint("LEFT", pBackupNameInput, "RIGHT", 10, 0)

    local pSummaryHeader = Window:CreateSectionHeader(profilesPage, "Saved Backups", -120)
    local pBackupList = CreateFrame("Frame", nil, profilesPage, "BackdropTemplate")
    pBackupList:SetSize(260, 200)
    pBackupList:SetPoint("TOPLEFT", pSummaryHeader, "BOTTOMLEFT", 0, -8)
    pBackupList:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1,
        insets = {left=1, right=1, top=1, bottom=1}
    })
    pBackupList:SetBackdropColor(0.1, 0.1, 0.1, 0.8)
    pBackupList:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)

    local pScrollFrame = CreateFrame("ScrollFrame", nil, pBackupList, "UIPanelScrollFrameTemplate")
    pScrollFrame:SetPoint("TOPLEFT", 4, -4)
    pScrollFrame:SetPoint("BOTTOMRIGHT", -26, 4)
    local pScrollChild = CreateFrame("Frame", nil, pScrollFrame)
    pScrollChild:SetSize(230, 1)
    pScrollFrame:SetScrollChild(pScrollChild)

    local pSummaryDetails = profilesPage:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    pSummaryDetails:SetPoint("TOPLEFT", pBackupList, "TOPRIGHT", 20, 0)
    pSummaryDetails:SetWidth(380)
    pSummaryDetails:SetJustifyH("LEFT")
    pSummaryDetails:SetJustifyV("TOP")

    local pBtnRestore = Window:CreateButton(profilesPage, "Restore Profile", 140, 30, function()
        if not SinsToolkit.ProfileVault or not Window.selectedProfileVaultBackup then return end
        SinsToolkit.ProfileVault:ConfirmRestoreBackup(Window.selectedProfileVaultBackup, function(ok, message)
            print("|cff9b59b6[Sin's Toolkit: QOL]|r " .. (message or (ok and "Profile restored." or "Profile restore failed.")))
        end)
    end, "Applies the selected backup to the current character after confirmation. This can replace action bars, macros, and keybindings.")
    pBtnRestore:SetPoint("BOTTOMLEFT", pBackupList, "BOTTOMRIGHT", 20, 0)

    local pBtnDelete = Window:CreateButton(profilesPage, "Delete Profile", 140, 30, function()
        if not SinsToolkit.ProfileVault or not Window.selectedProfileVaultBackup then return end
        SinsToolkit.ProfileVault:ConfirmDeleteBackup(Window.selectedProfileVaultBackup, function(ok, message)
            if ok then
                Window.selectedProfileVaultBackup = nil
                if Window.RefreshProfilesPage then Window:RefreshProfilesPage() end
            end
            print("|cff9b59b6[Sin's Toolkit: QOL]|r " .. (message or (ok and "Profile backup deleted." or "Profile backup delete failed.")))
        end)
    end, "Deletes the selected backup from Sin's Toolkit saved variables after confirmation.")
    pBtnDelete:SetPoint("LEFT", pBtnRestore, "RIGHT", 10, 0)

    local profileButtons = {}
    function Window:RefreshProfilesPage()
        for _, b in ipairs(profileButtons) do b:Hide() end
        if not SinsToolkit.ProfileVault then return end
        local backups = SinsToolkit.ProfileVault:GetBackups()
        pSummaryHeader:SetText("Saved Backups (" .. #backups .. ")")
        local y = 0
        local hasSelection = false
        for i, backup in ipairs(backups) do
            local b = profileButtons[i]
            if not b then
                b = CreateFrame("Button", nil, pScrollChild, "BackdropTemplate")
                b:SetSize(230, 24)
                b.text = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
                b.text:SetPoint("LEFT", 4, 0)
                b.text:SetPoint("RIGHT", -4, 0)
                b.text:SetJustifyH("LEFT")
                b:SetScript("OnClick", function(self)
                    Window.selectedProfileVaultBackup = self.backupID
                    Window:RefreshProfilesPage()
                end)
                profileButtons[i] = b
            end
            b.backupID = backup.id
            b:SetPoint("TOPLEFT", 0, y)
            local label = ("%s - %s"):format(backup.name or "Backup", backup.className or "Unknown")
            b.text:SetText(label)
            if Window.selectedProfileVaultBackup == backup.id then
                b:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8"})
                b:SetBackdropColor(0.3, 0.3, 0.6, 0.5)
                hasSelection = true
            else
                b:SetBackdrop(nil)
            end
            b:Show()
            y = y - 24
        end
        if not hasSelection then
            Window.selectedProfileVaultBackup = nil
        end
        pScrollChild:SetHeight(math.max(1, -y))
        
        if Window.selectedProfileVaultBackup then
            pSummaryDetails:SetText(SinsToolkit.ProfileVault:GetRestoreSummary(Window.selectedProfileVaultBackup) or "No details available.")
            pBtnRestore:Enable()
            pBtnDelete:Enable()
        else
            pSummaryDetails:SetText("Select a saved backup to view its details.")
            pBtnRestore:Disable()
            pBtnDelete:Disable()
        end
    end
    profilesPage.OnShowCallback = function() Window:RefreshProfilesPage() end

    -- CVars Page Frame
    local cvarsPage = CreateFrame("Frame", nil, contentPanel)
    cvarsPage:SetAllPoints()
    cvarsPage:Hide()
    Window.pages["CVars"] = cvarsPage
    local cvHeader = Window:CreateSectionHeader(cvarsPage, "CVar Browser")

    CreateWrappedText(cvarsPage, {"TOPLEFT", cvHeader, "BOTTOMLEFT", 0, -8}, 720, "Browse live client CVars, compare current values against Blizzard defaults, filter modified values, edit supported CVars, and reset values when the client allows it. Modified CVars appear green in the list.", "GameFontHighlightSmall")

    local cvSearch = Window:CreateEditBox(cvarsPage, "Search CVars", 200, 24, function() return Window.cvarSearchString or "" end, function(v) Window.cvarSearchString = v; Window.RefreshCVarsList() end, "Search by CVar name, category, or client-provided description.")
    cvSearch:SetPoint("TOPLEFT", cvHeader, "BOTTOMLEFT", 0, -66)

    local cvModOnly = Window:CreateCheckbox(cvarsPage, "Modified Only", function() return Window.cvarModifiedOnly end, function(v) Window.cvarModifiedOnly = v; Window.RefreshCVarsList() end, "Only show CVars where the current value differs from the client default.")
    cvModOnly:SetPoint("LEFT", cvSearch, "RIGHT", 10, 0)

    local cvRefresh = Window:CreateButton(cvarsPage, "Refresh List", 100, 24, function() if SinsToolkit.CVars then SinsToolkit.CVars:Refresh(); Window.RefreshCVarsList() end end, "Refreshes the CVar list and current/default values from the WoW client.")
    cvRefresh:SetPoint("LEFT", cvModOnly, "RIGHT", 120, 0)

    local cvListBg = CreateFrame("Frame", nil, cvarsPage, "BackdropTemplate")
    cvListBg:SetPoint("TOPLEFT", cvSearch, "BOTTOMLEFT", -5, -15)
    cvListBg:SetPoint("BOTTOMLEFT", cvarsPage, "BOTTOMLEFT", 0, 0)
    cvListBg:SetWidth(250)
    cvListBg:SetBackdrop({ bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8", edgeSize=1, insets={left=1, right=1, top=1, bottom=1} })
    cvListBg:SetBackdropColor(0.1, 0.1, 0.1, 0.8)
    cvListBg:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)

    local cvScroll = CreateFrame("ScrollFrame", nil, cvListBg, "UIPanelScrollFrameTemplate")
    cvScroll:SetPoint("TOPLEFT", 4, -4)
    cvScroll:SetPoint("BOTTOMRIGHT", -26, 4)
    local cvScrollChild = CreateFrame("Frame", nil, cvScroll)
    cvScrollChild:SetSize(220, 1)
    cvScroll:SetScrollChild(cvScrollChild)

    local cvDetails = cvarsPage:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    cvDetails:SetPoint("TOPLEFT", cvListBg, "TOPRIGHT", 20, 0)
    cvDetails:SetPoint("BOTTOMRIGHT", cvarsPage, "BOTTOMRIGHT", 0, 155)
    cvDetails:SetJustifyH("LEFT")
    cvDetails:SetJustifyV("TOP")

    local cvValBox = Window:CreateEditBox(cvarsPage, "New Value", 200, 24, function() return Window.cvarPendingValue or "" end, function(v) Window.cvarPendingValue = v end, "Enter the value to apply to the selected CVar.")
    cvValBox:SetPoint("BOTTOMLEFT", cvListBg, "BOTTOMRIGHT", 20, 10)

    local cvApply = Window:CreateButton(cvarsPage, "Apply", 80, 24, function()
        if SinsToolkit.CVars and Window.cvarSelected and Window.cvarPendingValue then
            local ok, msg = SinsToolkit.CVars:SetValue(Window.cvarSelected, Window.cvarPendingValue)
            print("|cff9b59b6[Sin's Toolkit: QOL]|r " .. (msg or (ok and "CVar updated." or "CVar update failed.")))
            Window.RefreshCVarsList()
        end
    end, "Applies the new value to the selected CVar.")
    cvApply:SetPoint("LEFT", cvValBox, "RIGHT", 10, 0)

    local cvReset = Window:CreateButton(cvarsPage, "Reset to Default", 120, 24, function()
        if SinsToolkit.CVars and Window.cvarSelected then
            local ok, msg = SinsToolkit.CVars:ResetToDefault(Window.cvarSelected)
            print("|cff9b59b6[Sin's Toolkit: QOL]|r " .. (msg or (ok and "CVar reset." or "CVar reset failed.")))
            Window.RefreshCVarsList()
        end
    end, "Sets the selected CVar back to the default value reported by the WoW client.")
    cvReset:SetPoint("LEFT", cvApply, "RIGHT", 10, 0)

    local cvModifiedSummary = cvarsPage:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    cvModifiedSummary:SetPoint("TOPLEFT", cvValBox, "BOTTOMLEFT", 0, -18)
    cvModifiedSummary:SetPoint("BOTTOMRIGHT", cvarsPage, "BOTTOMRIGHT", 0, 0)
    cvModifiedSummary:SetJustifyH("LEFT")
    cvModifiedSummary:SetJustifyV("TOP")

    local cvarButtons = {}
    function Window.RefreshCVarsList()
        for _, b in ipairs(cvarButtons) do b:Hide() end
        if not SinsToolkit.CVars then return end
        local vals = SinsToolkit.CVars:GetValues(Window.cvarSearchString, Window.cvarModifiedOnly)
        local res = {}
        for k in pairs(vals) do if k ~= "" and k ~= "Select a CVar" then table.insert(res, k) end end
        table.sort(res)

        local y = 0
        local hasSelection = false
        for i, cvarName in ipairs(res) do
            local b = cvarButtons[i]
            if not b then
                b = CreateFrame("Button", nil, cvScrollChild, "BackdropTemplate")
                b:SetSize(220, 24)
                b.text = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
                b.text:SetPoint("LEFT", 4, 0)
                b.text:SetPoint("RIGHT", -4, 0)
                b.text:SetJustifyH("LEFT")
                b:SetScript("OnClick", function(self)
                    Window.cvarSelected = self.cvarName
                    local info = SinsToolkit.CVars:GetInfo(self.cvarName)
                    Window.cvarPendingValue = info and tostring(info.value or "") or ""
                    cvValBox:SetText(Window.cvarPendingValue)
                    Window.RefreshCVarsList()
                end)
                cvarButtons[i] = b
            end
            b.cvarName = cvarName
            b:SetPoint("TOPLEFT", 0, y)
            b.text:SetText(vals[cvarName])
            if Window.cvarSelected == cvarName then
                b:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8"})
                b:SetBackdropColor(0.3, 0.3, 0.6, 0.5)
                hasSelection = true
            else
                b:SetBackdrop(nil)
            end
            b:Show()
            y = y - 24
        end

        if not hasSelection then Window.cvarSelected = nil end
        cvScrollChild:SetHeight(math.max(1, -y))

        if Window.cvarSelected then
            cvDetails:SetText(SinsToolkit.CVars:GetSummary(Window.cvarSelected))
            cvValBox:Show()
            cvApply:Show()
            cvReset:Show()
        else
            cvDetails:SetText("Select a CVar from the list.")
            cvValBox:Hide()
            cvApply:Hide()
            cvReset:Hide()
        end
        cvModifiedSummary:SetText(SinsToolkit.CVars:GetModifiedSummary(10))
    end
    cvarsPage.OnShowCallback = Window.RefreshCVarsList

    -- Developer Tools Page Frame
    local devToolsPage, devToolsChild = CreateScrollablePage("Developer Tools")
    local devHeader = Window:CreateSectionHeader(devToolsChild, "Developer Tools")
    CreateWrappedText(devToolsChild, {"TOPLEFT", devHeader, "BOTTOMLEFT", 0, -8}, 720, "A shared workspace for inspection, diagnostics, and runtime debugging tools. This MVP links existing developer-adjacent tools while preserving their current pages and slash commands.", "GameFontHighlightSmall")

    local devCards = {}
    local function CreateDeveloperToolCard(index, tool)
        local UI = SinsToolkit.UIComponents
        local col = (index - 1) % 2
        local row = math.floor((index - 1) / 2)
        local x = 0 + (col * 335)
        local y = -92 - (row * 142)
        local card

        if UI and UI.CreateCard then
            card = UI:CreateCard(devToolsChild, tool.name or tool.id or "Developer Tool", nil, 310, 124)
        else
            card = CreateFrame("Frame", nil, devToolsChild, "BackdropTemplate")
            card:SetSize(310, 124)
            SetFlatBackdrop(card, 0.025, 0.022, 0.032, 0.88, 0.32, 0.20, 0.42, 0.72)
            card.title = card:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            card.title:SetPoint("TOPLEFT", 14, -12)
            card.title:SetText(tool.name or tool.id or "Developer Tool")
            card.body = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            card.body:SetPoint("TOPLEFT", 14, -38)
            card.body:SetPoint("RIGHT", -14, 0)
            card.body:SetJustifyH("LEFT")
            card.body:SetJustifyV("TOP")
        end

        card:SetPoint("TOPLEFT", devToolsChild, "TOPLEFT", x, y)
        card.body:SetText(tool.description or "")

        local status = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        status:SetPoint("BOTTOMLEFT", 14, 14)
        status:SetText(("Status: |cff00ff00%s|r"):format(tool.status or "available"))

        local command = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        command:SetPoint("LEFT", status, "RIGHT", 12, 0)
        command:SetText(tool.slashCommand and ("Command: |cffd8a84a" .. tool.slashCommand .. "|r") or "")

        local button = Window:CreateButton(card, tool.page and "Open Page" or "Open", 98, 24, function()
            if tool.open then
                tool.open()
            elseif tool.page and Window.SelectPage then
                Window:SelectPage(tool.page)
            elseif tool.slashCommand then
                print("|cff9b59b6[Sin's Toolkit: QOL]|r Use " .. tool.slashCommand .. " to open " .. (tool.name or "this tool") .. ".")
            end
        end, "Open " .. (tool.name or "developer tool") .. ".")
        button:SetPoint("BOTTOMRIGHT", -14, 10)

        devCards[#devCards + 1] = card
        return card
    end

    devToolsPage.OnShowCallback = function()
        for _, card in ipairs(devCards) do
            card:Show()
        end

        if #devCards == 0 then
            local tools = SinsToolkit.DevTools and SinsToolkit.DevTools.GetTools and SinsToolkit.DevTools:GetTools() or {}
            for index, tool in ipairs(tools) do
                CreateDeveloperToolCard(index, tool)
            end
        end

        if #devCards == 0 then
            if not devToolsPage.emptyText then
                devToolsPage.emptyText = devToolsChild:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                devToolsPage.emptyText:SetPoint("TOPLEFT", devHeader, "BOTTOMLEFT", 0, -80)
                devToolsPage.emptyText:SetWidth(620)
                devToolsPage.emptyText:SetJustifyH("LEFT")
            end
            devToolsPage.emptyText:SetText("No Developer Tools are registered yet.")
            devToolsPage.emptyText:Show()
        elseif devToolsPage.emptyText then
            devToolsPage.emptyText:Hide()
        end

        local rows = math.ceil(math.max(1, #devCards) / 2)
        devToolsChild:SetHeight(math.max(360, 120 + (rows * 142)))
    end

    BuildFrameInspectorPage(CreateScrollablePage)

    -- Bug Catcher Page Frame
    local bugPage = CreateFrame("Frame", nil, contentPanel)
    bugPage:SetAllPoints()
    bugPage:Hide()
    Window.pages["Bug Catcher"] = bugPage
    local bgHeader = Window:CreateSectionHeader(bugPage, "Bug Catcher")

    local bgIntro = bugPage:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    bgIntro:SetPoint("TOPLEFT", bgHeader, "BOTTOMLEFT", 0, -8)
    bgIntro:SetJustifyH("LEFT")
    bgIntro:SetWidth(720)
    bgIntro:SetJustifyH("LEFT")
    bgIntro:SetText("Capture Lua errors, protected-action errors, and Lua warnings into a compact Toolkit window. The minimap bag can stay visible or only appear when errors exist.")

    local cbBugEn = Window:CreateCheckbox(bugPage, "Enable Bug Catcher", getGV("bugCatcherEnabled"), setGV("bugCatcherEnabled"), "Captures Lua errors, protected-action errors, and Lua warnings inside Sin's Toolkit.")
    cbBugEn:SetPoint("TOPLEFT", bgIntro, "BOTTOMLEFT", 0, -16)

    local cbBugMM = Window:CreateCheckbox(bugPage, "Minimap Icon", getGV("bugCatcherMinimap"), setGV("bugCatcherMinimap"), "Shows the Bug Catcher minimap button. Click to show the last error, Shift-click to reload UI, Alt-click to clear errors, Right-click to hide the button.")
    cbBugMM:SetPoint("TOPLEFT", cbBugEn, "BOTTOMLEFT", 0, -8)

    local cbBugMMO = Window:CreateCheckbox(bugPage, "Only Show With Errors", getGV("bugCatcherMinimapOnlyErrors"), setGV("bugCatcherMinimapOnlyErrors"), "Keeps the Bug Catcher minimap button hidden while there are no stored errors. It appears automatically after an error is captured.")
    cbBugMMO:SetPoint("TOPLEFT", cbBugMM, "BOTTOMLEFT", 0, -8)

    local btnBugSound = Window:CreateDropdownMenu(bugPage, "Error Sound", {
        { value = "NONE", text = "None" },
        { value = "RAID_WARNING", text = "Raid Warning" },
        { value = "ALARM", text = "Alarm" },
        { value = "WHISPER", text = "Whisper" },
        { value = "READY_CHECK", text = "Ready Check" },
        { value = "BUGSACK_FATALITY", text = "BugSack: Fatality" },
    }, getGV("bugCatcherSound"), setGV("bugCatcherSound"), 190, "Choose the sound Sin's Toolkit plays when Bug Catcher captures a Lua error.")
    btnBugSound:SetPoint("TOPLEFT", cbBugMMO, "BOTTOMLEFT", 4, -20)

    local bBugOpen = Window:CreateButton(bugPage, "Open Catcher Window", 180, 30, function() if SinsToolkit.BugCatcher then SinsToolkit.BugCatcher:ShowLast() end end, "Opens the Bug Catcher window and displays the most recent captured error.")
    bBugOpen:SetPoint("TOPLEFT", btnBugSound, "BOTTOMLEFT", -4, -20)

    local bBugTest = Window:CreateButton(bugPage, "Test Error", 180, 30, function() if SinsToolkit.BugCatcher and SinsToolkit.BugCatcher.RunSelfTest then SinsToolkit.BugCatcher:RunSelfTest() end end, "Captures a safe test entry, opens Bug Catcher, and plays the configured alert sound without generating a real Lua error.")
    bBugTest:SetPoint("LEFT", bBugOpen, "RIGHT", 10, 0)

    -- UI Tweaks Page Frame
    local tweaksPage = CreateFrame("Frame", nil, contentPanel)
    tweaksPage:SetAllPoints()
    tweaksPage:Hide()
    Window.pages["UI Tweaks"] = tweaksPage
    local twHeader = Window:CreateSectionHeader(tweaksPage, "UI Tweaks & Automation")
    CreateWrappedText(tweaksPage, {"TOPLEFT", twHeader, "BOTTOMLEFT", 0, -8}, 720, "Small interface behavior changes and optional automation helpers. This page keeps supported Blizzard panels open, cleans up repeated interactions, and keeps the Druid form helpers in one place.", "GameFontHighlightSmall")
    local cbPanels = Window:CreateCheckbox(tweaksPage, "No Auto Close Panels", getGV("keepPanelsOpen"), setGV("keepPanelsOpen"), "Uses Sin's Toolkit No Auto Close behavior so standard Blizzard UI panels can stay open together. Reload after disabling if a Blizzard panel keeps its old position.")
    cbPanels:SetPoint("TOPLEFT", twHeader, "BOTTOMLEFT", 0, -48)
    CreateWrappedText(tweaksPage, {"TOPLEFT", cbPanels, "BOTTOMLEFT", 0, -10}, 600, "Keeps supported Blizzard panels open together using Sin's Toolkit standalone panel handling.", "GameFontHighlightSmall")
    local cbAutoGossipTweaks = Window:CreateCheckbox(tweaksPage, "Automate Gossip", getGV("automateGossip"), setGV("automateGossip"), "Automatically selects safe single-option gossip while avoiding city direction, trainer, bank, auction house, and storyteller-style gossip.")
    cbAutoGossipTweaks:SetPoint("TOPLEFT", cbPanels, "BOTTOMLEFT", 0, -48)
    local btnGossipModTweaks = CreateOptionGroup(tweaksPage, "Gossip Bypass Modifier", MODIFIER_OPTIONS, getGV("automateGossipModifier"), setGV("automateGossipModifier"), "Hold this modifier key to temporarily bypass auto-gossip. None means auto-gossip has no modifier bypass.")
    btnGossipModTweaks:SetPoint("TOPLEFT", cbAutoGossipTweaks, "BOTTOMLEFT", 4, -16)
    local cbAutoUnshiftTweaks = Window:CreateCheckbox(tweaksPage, "Auto Unshift for Druids", getGV("gseAutoUnshift"), setGV("gseAutoUnshift"), AUTO_UNSHIFT_TOOLTIP)
    cbAutoUnshiftTweaks:SetPoint("TOPLEFT", btnGossipModTweaks, "BOTTOMLEFT", -4, -16)
    local cbFlightTweaks = Window:CreateCheckbox(tweaksPage, "Druid Auto Flight Form", getGV("druidAutoFlightForm"), setGV("druidAutoFlightForm"), "When you are a Druid in ground Travel Form and move into an outdoor flyable area, Sin's Toolkit helps move you toward Flight Form on your next safe travel-form action. It never runs in combat, instances, challenge modes, while flying, or while submerged.")
    cbFlightTweaks:SetPoint("TOPLEFT", cbAutoUnshiftTweaks, "BOTTOMLEFT", 0, -8)

    -- UI Errors Page Frame
    local errorsPage = CreateFrame("Frame", nil, contentPanel)
    errorsPage:SetAllPoints()
    errorsPage:Hide()
    Window.pages["UI Errors"] = errorsPage
    local errHeader = Window:CreateSectionHeader(errorsPage, "UI Errors")
    CreateWrappedText(errorsPage, {"TOPLEFT", errHeader, "BOTTOMLEFT", 0, -8}, 720, "Choose which red UI error messages Sin's Toolkit should block. Checked messages are blocked and sorted to the top; unchecked messages are allowed through normally.", "GameFontHighlightSmall")
    local cbErrEnable = Window:CreateCheckbox(errorsPage, "Enable UI Error Filtering", getGV("hideOutOfRangeErrors"), setGV("hideOutOfRangeErrors"), "Enable or disable the filtering of UI error messages entirely.")
    cbErrEnable:SetPoint("TOPLEFT", errHeader, "BOTTOMLEFT", 0, -50)
    local errSearch = Window:CreateEditBox(errorsPage, "Search Errors", 260, 24, function() return Window.errorSearchString or "" end, function(v) Window.errorSearchString = v; if Window.RefreshUIErrorsList then Window:RefreshUIErrorsList() end end, "Filter the error list below. Text updates dynamically.")
    errSearch:SetPoint("TOPLEFT", cbErrEnable, "BOTTOMLEFT", 4, -24)
    local errListBg = CreateFrame("Frame", nil, errorsPage, "BackdropTemplate")
    errListBg:SetPoint("TOPLEFT", errSearch, "BOTTOMLEFT", -4, -14)
    errListBg:SetPoint("BOTTOMRIGHT", errorsPage, "BOTTOMRIGHT", -12, 8)
    errListBg:SetBackdrop({ bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8", edgeSize=1, insets={left=1, right=1, top=1, bottom=1} })
    errListBg:SetBackdropColor(0.05, 0.05, 0.05, 0.85)
    errListBg:SetBackdropBorderColor(0.35, 0.18, 0.55, 1)
    local errScroll = CreateFrame("ScrollFrame", nil, errListBg, "UIPanelScrollFrameTemplate")
    errScroll:SetPoint("TOPLEFT", 4, -4)
    errScroll:SetPoint("BOTTOMRIGHT", -28, 4)
    local errScrollChild = CreateFrame("Frame", nil, errScroll)
    errScrollChild:SetSize(580, 1)
    errScroll:SetScrollChild(errScrollChild)
    local errButtons = {}
    local uiErrorCache
    local function GetStandaloneUIErrors()
        if uiErrorCache then return uiErrorCache end
        uiErrorCache = {}
        local grouped = {}
        if GetGameMessageInfo then
            for i = 1, 1600 do
                local key = GetGameMessageInfo(i)
                local text = key and _G[key]
                if key and key:match("^ERR_") and text and text ~= "" then
                    local entry = grouped[text]
                    if not entry then
                        entry = { key = key, keys = {}, text = text, search = (key .. " " .. text):lower() }
                        grouped[text] = entry
                        uiErrorCache[#uiErrorCache + 1] = entry
                    end
                    entry.keys[#entry.keys + 1] = key
                end
            end
        end
        table.sort(uiErrorCache, function(a, b) return a.text < b.text end)
        return uiErrorCache
    end
    local function IsErrorBlocked(entry)
        local blocked = SinsToolkitDB and SinsToolkitDB.blockedUIErrors
        if not blocked then return false end
        for _, key in ipairs(entry.keys) do
            if blocked[key] == true then return true end
        end
        return false
    end
    function Window:RefreshUIErrorsList()
        for _, button in ipairs(errButtons) do button:Hide() end
        local search = (Window.errorSearchString or ""):lower()
        local filtered = {}
        for _, entry in ipairs(GetStandaloneUIErrors()) do
            if search == "" or entry.search:find(search, 1, true) then
                filtered[#filtered + 1] = entry
            end
        end
        table.sort(filtered, function(a, b)
            local aBlocked, bBlocked = IsErrorBlocked(a), IsErrorBlocked(b)
            if aBlocked ~= bBlocked then return aBlocked end
            return a.text < b.text
        end)
        local y = 0
        for i, entry in ipairs(filtered) do
            if i > 120 then break end
            local cb = errButtons[i]
            if not cb then
                cb = nil
                cb = Window:CreateCheckbox(errScrollChild, "", function() return cb and cb.entry and IsErrorBlocked(cb.entry) end, function(value)
                    if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
                    SinsToolkitDB.blockedUIErrors = SinsToolkitDB.blockedUIErrors or {}
                    if cb and cb.entry then
                        for _, key in ipairs(cb.entry.keys) do SinsToolkitDB.blockedUIErrors[key] = value or nil end
                    end
                    if SinsToolkit.TriggerUpdate then SinsToolkit.TriggerUpdate("blockedUIErrors") end
                    Window:RefreshUIErrorsList()
                end)
                errButtons[i] = cb
            end
            cb.entry = entry
            cb:SetPoint("TOPLEFT", 0, y)
            local label = _G[cb:GetName() .. "Text"]
            if label then label:SetText(entry.text) end
            cb:SetChecked(IsErrorBlocked(entry))
            cb:Show()
            y = y - 28
        end
        errScrollChild:SetHeight(math.max(1, -y))
    end
    errorsPage.OnShowCallback = function() Window:RefreshUIErrorsList() end

    -- Automation Page Frame
    local automationPage = CreateFrame("Frame", nil, contentPanel)
    automationPage:SetAllPoints()
    automationPage:Hide()
    Window.pages["Automation"] = automationPage
    local autoHeader = Window:CreateSectionHeader(automationPage, "Automation")
    CreateWrappedText(automationPage, {"TOPLEFT", autoHeader, "BOTTOMLEFT", 0, -8}, 720, "Optional helpers for repeated interaction and Druid form handling. Auto Unshift and Auto Flight Form are separate Druid options, so you can use one without enabling the other.", "GameFontHighlightSmall")
    local cbAutoGossip = Window:CreateCheckbox(automationPage, "Automate Gossip", getGV("automateGossip"), setGV("automateGossip"), "Automatically selects safe single-option gossip while avoiding city direction, trainer, bank, auction house, and storyteller-style gossip.")
    cbAutoGossip:SetPoint("TOPLEFT", autoHeader, "BOTTOMLEFT", 0, -48)
    local btnGossipMod = CreateOptionGroup(automationPage, "Gossip Bypass Modifier", MODIFIER_OPTIONS, getGV("automateGossipModifier"), setGV("automateGossipModifier"), "Hold this modifier key to temporarily bypass auto-gossip. None means auto-gossip has no modifier bypass.")
    btnGossipMod:SetPoint("TOPLEFT", cbAutoGossip, "BOTTOMLEFT", 4, -16)
    local cbAutoUnshift = Window:CreateCheckbox(automationPage, "Auto Unshift for Druids", getGV("gseAutoUnshift"), setGV("gseAutoUnshift"), AUTO_UNSHIFT_TOOLTIP)
    cbAutoUnshift:SetPoint("TOPLEFT", btnGossipMod, "BOTTOMLEFT", -4, -16)
    local cbFlight = Window:CreateCheckbox(automationPage, "Druid Auto Flight Form", getGV("druidAutoFlightForm"), setGV("druidAutoFlightForm"), "When you are a Druid in ground Travel Form and move into an outdoor flyable area, Sin's Toolkit helps move you toward Flight Form on your next safe travel-form action. It never runs in combat, instances, challenge modes, while flying, or while submerged.")
    cbFlight:SetPoint("TOPLEFT", cbAutoUnshift, "BOTTOMLEFT", 0, -8)

    local function BuildFrameToolsPage()
        local page, child = CreateScrollablePage("Frame Tools")
        local content = child
        local pageHeader = Window:CreateSectionHeader(content, "Frame Tools", -18)
        CreateWrappedText(content, {"TOPLEFT", pageHeader, "BOTTOMLEFT", 0, -8}, 660, "Move, scale, save, and reset supported Blizzard windows. Panel behavior options remain grouped here for frame-related workflows.", "GameFontHighlightSmall")

        local leftX = 10
        local rightX = 350
        local colWidth = 320
        local controlRowHeight = 38
        local fieldRowHeight = 58
        local sliderRowHeight = 66
        local leftY = -96
        local rightY = -96

        local POSITION_SAVE_OPTIONS = {
            { value = "permanent", text = "Permanent" },
            { value = "session", text = "Session" },
        }
        local SCALE_SAVE_OPTIONS = {
            { value = "permanent", text = "Permanent" },
            { value = "session", text = "Session" },
            { value = "off", text = "None" },
        }

        local function CreateSectionAt(x, y, title, description)
            local header = Window:CreateSectionHeader(content, title, y)
            header:ClearAllPoints()
            header:SetPoint("TOPLEFT", content, "TOPLEFT", x, y)
            y = y - 26
            if description then
                CreateWrappedText(content, {"TOPLEFT", header, "BOTTOMLEFT", 0, -7}, colWidth - 12, description, "GameFontHighlightSmall")
                y = y - 34
            else
                y = y - 10
            end
            return y
        end

        rightY = CreateSectionAt(rightX, rightY, "Status", "Live status of the Frame Mover.")
        local status = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        status:SetPoint("TOPLEFT", content, "TOPLEFT", rightX + 10, rightY)
        status:SetWidth(colWidth - 10)
        status:SetJustifyH("LEFT")
        rightY = rightY - 132

        -- Frame Mover
        leftY = CreateSectionAt(leftX, leftY, "Frame Mover", "Move, scale, and save window positions.")
        local cbMover = Window:CreateCheckbox(content, "Enable Frame Mover", getGV("frameMoverEnabled"), setGV("frameMoverEnabled"), "Globally enables or disables the Frame Mover feature.")
        cbMover:SetPoint("TOPLEFT", content, "TOPLEFT", leftX, leftY)
        leftY = leftY - controlRowHeight

        local requireShift = Window:CreateCheckbox(content, "Require Shift to Move", getGV("frameMoverRequireModifier"), setGV("frameMoverRequireModifier"), "If checked, you must hold Shift to move frames.")
        requireShift:SetPoint("TOPLEFT", content, "TOPLEFT", leftX, leftY)
        leftY = leftY - controlRowHeight

        local ctrlScale = Window:CreateCheckbox(content, "Ctrl + Mouse Wheel Scaling", getGV("frameMoverCtrlScrollScale"), setGV("frameMoverCtrlScrollScale"), "Allows Ctrl + mouse wheel over supported frames to adjust scale.")
        ctrlScale:SetPoint("TOPLEFT", content, "TOPLEFT", leftX, leftY)
        leftY = leftY - controlRowHeight - 8

        -- Saving
        leftY = CreateSectionAt(leftX, leftY, "Saving", "Determines how frame positions and scales are saved.")
        local posMode = CreateOptionGroup(content, "Position Saving", POSITION_SAVE_OPTIONS, function()
            local value = GetToolkitValue("frameMoverPositionMode")
            return value == "permanent" and "permanent" or "session"
        end, setGV("frameMoverPositionMode"), "Session saves until logout/reload. Permanent saves across sessions.")
        posMode:SetPoint("TOPLEFT", content, "TOPLEFT", leftX + 4, leftY)
        leftY = leftY - fieldRowHeight

        local scaleMode = CreateOptionGroup(content, "Scale Saving", SCALE_SAVE_OPTIONS, function()
            local value = GetToolkitValue("frameMoverScaleMode")
            if value == "off" or value == "permanent" then return value end
            return "session"
        end, setGV("frameMoverScaleMode"), "Session saves until logout/reload. Permanent saves across sessions. None disables scale saving.")
        scaleMode:SetPoint("TOPLEFT", content, "TOPLEFT", leftX + 4, leftY)
        leftY = leftY - fieldRowHeight - 8

        -- Utilities
        leftY = CreateSectionAt(leftX, leftY, "Utilities", "Reset saved frame data.")
        local resetPos = Window:CreateButton(content, "Reset All Positions & Scales", 220, 26, function() if SinsToolkit.FrameMover and SinsToolkit.FrameMover.ResetAll then SinsToolkit.FrameMover:ResetAll() end end, "Clears all saved Frame Mover positions and scales.")
        resetPos:SetPoint("TOPLEFT", content, "TOPLEFT", leftX, leftY)
        leftY = leftY - 48

        -- Behavior
        rightY = CreateSectionAt(rightX, rightY, "Panel Behavior", "Additional options affecting panel behavior.")
        local cbNoAutoClose = Window:CreateCheckbox(content, "No Auto Close Panels", getGV("keepPanelsOpen"), setGV("keepPanelsOpen"), "Keeps supported Blizzard panels open together. This is the same setting found on the UI Tweaks page. Reload after disabling.")
        cbNoAutoClose:SetPoint("TOPLEFT", content, "TOPLEFT", rightX, rightY)
        rightY = rightY - controlRowHeight

        local exclude = Window:CreateCheckbox(content, "Respect Excluded Frames", function()
            if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
            return SinsToolkitDB and SinsToolkitDB.noAutoClose and SinsToolkitDB.noAutoClose.respectExcludedFrames ~= false
        end, function(value)
            if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
            if SinsToolkitDB and SinsToolkitDB.noAutoClose then
                SinsToolkitDB.noAutoClose.respectExcludedFrames = value
            end
            if SinsToolkit.ApplyNoAutoClosePanels then SinsToolkit.ApplyNoAutoClosePanels() end
        end, "Leaves sensitive frames such as cinematics, barbershop, and trading post under Blizzard control.")
        exclude:SetPoint("TOPLEFT", content, "TOPLEFT", rightX, rightY)
        rightY = rightY - controlRowHeight - 8

        -- No Auto Close Defaults
        rightY = CreateSectionAt(rightX, rightY, "No Auto Close Defaults", "Default position for panels when no position is saved.")
        local anchor = Window:CreateCycleButton(content, "Default Anchor", { TOPLEFT = "Top Left", TOP = "Top", TOPRIGHT = "Top Right", CENTER = "Center" }, function()
            if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
            return SinsToolkitDB and SinsToolkitDB.noAutoClose and SinsToolkitDB.noAutoClose.defaultPosition and SinsToolkitDB.noAutoClose.defaultPosition.anchor or "TOPLEFT"
        end, function(value)
            if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
            if SinsToolkitDB and SinsToolkitDB.noAutoClose and SinsToolkitDB.noAutoClose.defaultPosition then
                SinsToolkitDB.noAutoClose.defaultPosition.anchor = value
            end
            if SinsToolkit.ApplyNoAutoClosePanels then SinsToolkit.ApplyNoAutoClosePanels() end
        end, "Default anchor used when a panel loses its Blizzard layout point.")
        anchor:SetPoint("TOPLEFT", content, "TOPLEFT", rightX + 4, rightY)
        rightY = rightY - fieldRowHeight

        local x = Window:CreateSlider(content, "Default X Offset", function() if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end return SinsToolkitDB and SinsToolkitDB.noAutoClose and SinsToolkitDB.noAutoClose.defaultPosition and SinsToolkitDB.noAutoClose.defaultPosition.x or 50 end, function(value) if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end if SinsToolkitDB and SinsToolkitDB.noAutoClose and SinsToolkitDB.noAutoClose.defaultPosition then SinsToolkitDB.noAutoClose.defaultPosition.x = value end; if SinsToolkit.ApplyNoAutoClosePanels then SinsToolkit.ApplyNoAutoClosePanels() end end, -900, 900, 1, "Default X offset for No Auto Close panel placement.")
        x:SetPoint("TOPLEFT", content, "TOPLEFT", rightX, rightY)
        rightY = rightY - sliderRowHeight

        local y = Window:CreateSlider(content, "Default Y Offset", function() if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end return SinsToolkitDB and SinsToolkitDB.noAutoClose and SinsToolkitDB.noAutoClose.defaultPosition and SinsToolkitDB.noAutoClose.defaultPosition.y or -50 end, function(value) if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end if SinsToolkitDB and SinsToolkitDB.noAutoClose and SinsToolkitDB.noAutoClose.defaultPosition then SinsToolkitDB.noAutoClose.defaultPosition.y = value end; if SinsToolkit.ApplyNoAutoClosePanels then SinsToolkit.ApplyNoAutoClosePanels() end end, -900, 900, 1, "Default Y offset for No Auto Close panel placement.")
        y:SetPoint("TOPLEFT", content, "TOPLEFT", rightX, rightY)
        rightY = rightY - sliderRowHeight

        page.OnShowCallback = function()
            if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
            local statusText = "FrameMover is not loaded."
            if SinsToolkit.FrameMover and SinsToolkit.FrameMover.GetStatusText then
                local numPositions = 0
                local numScales = 0
                if SinsToolkitDB and SinsToolkitDB.frameMoverPositions then
                    for _ in pairs(SinsToolkitDB.frameMoverPositions) do numPositions = numPositions + 1 end
                end
                if SinsToolkitDB and SinsToolkitDB.frameScales then
                    for _ in pairs(SinsToolkitDB.frameScales) do numScales = numScales + 1 end
                end

                local enabled = SinsToolkit.IsFeatureActive and SinsToolkit.IsFeatureActive("frameMoverEnabled")
                statusText = ("Mover Status: %s\nPosition Saving: |cffb868dc%s|r\nScale Saving: |cffb868dc%s|r\nMove Modifier: |cffb868dc%s|r\nRegistered Movable Frames: |cffb868dc%d|r\nSaved Positions: |cffb868dc%d|r\nSaved Scales: |cffb868dc%d|r"):format(
                    enabled and "|cff00ff00Enabled|r" or "|cffff4040Disabled|r",
                    SinsToolkitDB and SinsToolkitDB.frameMoverPositionMode or "session",
                    SinsToolkitDB and SinsToolkitDB.frameMoverScaleMode or "session",
                    SinsToolkitDB and SinsToolkitDB.frameMoverRequireModifier and "Shift" or "None",
                    SinsToolkit.FrameMover.GetRegisteredFrameCount and SinsToolkit.FrameMover:GetRegisteredFrameCount() or 0,
                    numPositions,
                    numScales
                )
            end
            status:SetText(statusText)
            if page.GetParent and page:GetParent().SetScrollVertical then
                page:GetParent():SetScrollVertical(0)
            end
        end

        child:SetHeight(math.max(620, -math.min(leftY, rightY) + 100))
    end
    BuildFrameToolsPage()

    -- Professions & Lookups Page Frame
    local profPage = CreateFrame("Frame", nil, contentPanel)
    profPage:SetAllPoints()
    profPage:Hide()
    Window.pages["Professions"] = profPage
    local profHeader = Window:CreateSectionHeader(profPage, "Professions & Lookups")
    CreateWrappedText(profPage, {"TOPLEFT", profHeader, "BOTTOMLEFT", 0, -8}, 720, "Hold your selected modifier over an eligible bag item to show the secure profession overlay, then click to mill, prospect, disenchant, or open supported items. Quick Link creates copyable Wowhead, Armory, and Raider.IO URLs from supported tooltips.", "GameFontHighlightSmall")
    local cbProfHotkey = Window:CreateCheckbox(profPage, "Profession Item Hotkey", getGV("professionItemHotkeyEnabled"), setGV("professionItemHotkeyEnabled"), "When enabled, hold the selected modifier over an eligible bag item. Sin's Toolkit highlights supported items and routes your click through a secure profession action for milling, prospecting, disenchanting, or opening.")
    cbProfHotkey:SetPoint("TOPLEFT", profHeader, "BOTTOMLEFT", 0, -48)
    local btnProfModifier = CreateOptionGroup(profPage, "Profession Modifier", MODIFIER_OPTIONS, function()
        if GetToolkitValue("professionItemHotkeyEnabled") == false then return "NONE" end
        return GetToolkitValue("professionItemHotkeyModifier") or "ALT"
    end, function(value)
        if value == "NONE" then
            SetToolkitValue("professionItemHotkeyEnabled", false)
        else
            SetToolkitValue("professionItemHotkeyEnabled", true)
            SetToolkitValue("professionItemHotkeyModifier", value)
        end
    end, "Select the modifier used by the secure profession overlay. None disables the profession hotkey.")
    btnProfModifier:SetPoint("TOPLEFT", cbProfHotkey, "BOTTOMLEFT", 4, -16)
    local cbWowhead = Window:CreateCheckbox(profPage, "Wowhead Quick Link", getGV("wowheadSearchEnabled"), setGV("wowheadSearchEnabled"), "Mouse over supported UI elements and press Ctrl+C to open a copyable Wowhead, Armory, or Raider.IO link.")
    cbWowhead:SetPoint("TOPLEFT", btnProfModifier, "BOTTOMLEFT", -4, -16)

    -- Identifier Page Frame
    local identifierPage = CreateFrame("Frame", nil, contentPanel)
    identifierPage:SetAllPoints()
    identifierPage:Hide()
    Window.pages["Identifier"] = identifierPage
    local idHeader = Window:CreateSectionHeader(identifierPage, "Identifier")
    CreateWrappedText(identifierPage, {"TOPLEFT", idHeader, "BOTTOMLEFT", 0, -8}, 720, "Inspect frames under your cursor, copy frame details, and look up global frame names. You can also use /stkid for a one-time identification without leaving hover mode enabled.", "GameFontHighlightSmall")
    local cbIdentifier = Window:CreateCheckbox(identifierPage, "Enable Hover Identifier", getGV("identifierEnabled"), setGV("identifierEnabled"), "Shows a small identifier tooltip while you move your cursor over UI frames.")
    cbIdentifier:SetPoint("TOPLEFT", idHeader, "BOTTOMLEFT", 0, -48)
    local cbIdentifierTooltip = Window:CreateCheckbox(identifierPage, "Show Identifier Tooltip", getGV("identifierTooltip"), setGV("identifierTooltip"), "Displays the current frame details near your cursor while hover identifier mode is enabled.")
    cbIdentifierTooltip:SetPoint("TOPLEFT", cbIdentifier, "BOTTOMLEFT", 0, -8)
    local cbIdentifierDetail = Window:CreateCheckbox(identifierPage, "Detailed Output", getGV("identifierDetailed"), setGV("identifierDetailed"), "Includes frame size, strata, level, alpha, point, and source-location details when the client exposes them.")
    cbIdentifierDetail:SetPoint("TOPLEFT", cbIdentifierTooltip, "BOTTOMLEFT", 0, -8)
    local bIdentify = Window:CreateButton(identifierPage, "Identify Cursor Frame", 180, 30, function() if SinsToolkit.Identifier then SinsToolkit.Identifier:IdentifyNow() end end, "Prints information about the frame currently under your cursor. Same as /stkid.")
    bIdentify:SetPoint("TOPLEFT", cbIdentifierDetail, "BOTTOMLEFT", 0, -18)
    local bIdentifierCopy = Window:CreateButton(identifierPage, "Copy Last Result", 160, 30, function() if SinsToolkit.Identifier then SinsToolkit.Identifier:CopyLast() end end, "Opens a copyable popup for the last identified frame.")
    bIdentifierCopy:SetPoint("LEFT", bIdentify, "RIGHT", 10, 0)

    -- Navigation Buttons
    local categories = {
        "Information",
        "General",
        "Gear",
        "UI Tweaks",
        "Frame Tools",
        "Developer Tools",
        "Frame Inspector",
        "UI Errors",
        "CVars",
        "Professions",
        "Teleports",
        "Profiles",
        "Bug Catcher",
        "Changelog",
    }
    local navButtons = {}
    for i, category in ipairs(categories) do
        local btn = Window:CreateButton(navPanel, category, 174, 24, function()
            Window:SelectPage(category)
        end)
        btn.pageName = category
        btn.isNavButton = true
        btn:SetSize(174, 24)
        local fs = btn:GetFontString()
        if fs then
            fs:ClearAllPoints()
            fs:SetPoint("LEFT", btn, "LEFT", 10, 0)
            fs:SetPoint("RIGHT", btn, "RIGHT", -6, 0)
            fs:SetJustifyH("LEFT")
            fs:SetTextColor(0.95, 0.80, 0.22)
        end
        Window:StyleNavButton(btn, false)
        btn:SetScript("OnShow", function(self)
            Window:StyleNavButton(self, self.pageName == Window.activePage)
        end)
        if i == 1 then
            btn:SetPoint("TOP", navPanel, "TOP", 0, -10)
        else
            btn:SetPoint("TOP", navButtons[i-1], "BOTTOM", 0, -2)
        end
        navButtons[i] = btn
    end

    Window.navPanel = navPanel
    Window.contentPanel = contentPanel
    Window.navButtons = navButtons
    Window.generalPage = generalPage
    RefreshWrappedTextWidths()

    -- Information should load automatically when the window opens.
    Window:SelectPage("Information")

    -- Close on ESC
    frame:Hide()
    tinsert(UISpecialFrames, "SinsToolkitWindowFrame")
end

function Window:Show()
    if not isInitialized then Init() end
    if not frame then return end
    local inspector = SinsToolkit.DevTools and SinsToolkit.DevTools.FrameInspector
    if inspector and inspector.IsHoverInspecting and inspector:IsHoverInspecting() then
        inspector:StopHoverInspector(false)
    end
    frame:Show()
    Window:SelectPage(Window.activePage or "Information")
    if Window.navButtons then
        for _, btn in ipairs(Window.navButtons) do
            Window:StyleNavButton(btn, btn.pageName == Window.activePage)
        end
    end
end

function Window:Hide()
    if frame then frame:Hide() end
end

function Window:Toggle()
    if not isInitialized then Init() end
    if not frame then return end
        if frame:IsShown() then frame:Hide() else self:Show() end
end
