-------------------------------------------------------------------------------
--  GraphicsProfileSwitcher.lua
--  Save and switch between up to 5 named graphics profiles.
--  Includes 3 built-in presets: Crank it to 11, Looks good performs fine,
--  and Optimized FPS.
--  Originally inspired by EllesmereUI by Ellesmere.
-------------------------------------------------------------------------------

local ADDON = "GraphicsProfileSwitcher"
local MAX_PROFILES = 5

-------------------------------------------------------------------------------
--  All CVars we snapshot and restore
-------------------------------------------------------------------------------
local TRACKED_CVARS = {
    "graphicsShadowQuality",
    "graphicsLiquidDetail",
    "graphicsParticleDensity",
    "graphicsSSAO",
    "graphicsDepthEffects",
    "graphicsComputeEffects",
    "graphicsOutlineMode",
    "graphicsTextureResolution",
    "graphicsSpellDensity",
    "graphicsProjectedTextures",
    "graphicsViewDistance",
    "graphicsEnvironmentDetail",
    "graphicsGroundClutter",
    "RAIDsettingsEnabled",
    "ResampleAlwaysSharpen",
    "Contrast",
}

-------------------------------------------------------------------------------
--  Built-in presets
-------------------------------------------------------------------------------
local PRESETS = {
    {
        id    = "__preset_max__",
        label = "Crank it to 11",
        icon  = "",
        desc  = "Maximum quality. Everything turned up. Looks incredible, bring a good GPU.",
        cvars = {
            graphicsShadowQuality     = "8",
            graphicsLiquidDetail      = "3",
            graphicsParticleDensity   = "100",
            graphicsSSAO              = "2",
            graphicsDepthEffects      = "2",
            graphicsComputeEffects    = "2",
            graphicsOutlineMode       = "1",
            graphicsTextureResolution = "4",
            graphicsSpellDensity      = "100",
            graphicsProjectedTextures = "1",
            graphicsViewDistance      = "10",
            graphicsEnvironmentDetail = "10",
            graphicsGroundClutter     = "10",
            RAIDsettingsEnabled       = "0",
            ResampleAlwaysSharpen     = "0",
        },
    },
    {
        id    = "__preset_balanced__",
        label = "Looks good, performs fine",
        icon  = "",
        desc  = "A balanced middle ground. Good visuals without tanking your framerate.",
        cvars = {
            graphicsShadowQuality     = "3",
            graphicsLiquidDetail      = "2",
            graphicsParticleDensity   = "50",
            graphicsSSAO              = "1",
            graphicsDepthEffects      = "1",
            graphicsComputeEffects    = "1",
            graphicsOutlineMode       = "0",
            graphicsTextureResolution = "3",
            graphicsSpellDensity      = "50",
            graphicsProjectedTextures = "1",
            graphicsViewDistance      = "5",
            graphicsEnvironmentDetail = "5",
            graphicsGroundClutter     = "5",
            RAIDsettingsEnabled       = "0",
            ResampleAlwaysSharpen     = "1",
        },
    },
    {
        id    = "__preset_fps__",
        label = "Optimized FPS",
        icon  = "",
        desc  = "Shadows off · low detail · sharpening on. Best for raids and high-pop areas.",
        cvars = {
            graphicsShadowQuality     = "0",
            graphicsLiquidDetail      = "0",
            graphicsParticleDensity   = "5",
            graphicsSSAO              = "0",
            graphicsDepthEffects      = "0",
            graphicsComputeEffects    = "0",
            graphicsOutlineMode       = "0",
            graphicsTextureResolution = "2",
            graphicsSpellDensity      = "1",
            graphicsProjectedTextures = "1",
            graphicsViewDistance      = "1",
            graphicsEnvironmentDetail = "1",
            graphicsGroundClutter     = "1",
            RAIDsettingsEnabled       = "0",
            ResampleAlwaysSharpen     = "1",
        },
        -- Optimized FPS also gets a contrast nudge (handled in ApplyPreset)
        contrastBoost = true,
    },
}

-------------------------------------------------------------------------------
--  Helpers
-------------------------------------------------------------------------------
local function SetCVarSafe(cvar, value)
    if InCombatLockdown() then return end
    pcall(SetCVar, cvar, value)
end

local function SnapshotCVars()
    local snapshot = {}
    for _, cvar in ipairs(TRACKED_CVARS) do
        snapshot[cvar] = GetCVar(cvar) or ""
    end
    return snapshot
end

local function ProfileCount()
    local count = 0
    for _ in pairs(GraphicsProfileSwitcherDB.profiles) do count = count + 1 end
    return count
end

-------------------------------------------------------------------------------
--  Preset application
-------------------------------------------------------------------------------
local function ApplyPreset(preset)
    for cvar, value in pairs(preset.cvars) do
        SetCVarSafe(cvar, value)
    end
    if preset.contrastBoost then
        local cur = tonumber(GetCVar("Contrast")) or 50
        if cur <= 55 then SetCVarSafe("Contrast", tostring(cur + 10)) end
    end
    GraphicsProfileSwitcherDB.activeProfile = preset.id
end

-------------------------------------------------------------------------------
--  Saved profile operations
-------------------------------------------------------------------------------
local function SaveProfile(name)
    if not name or name:match("^%s*$") then return false, "Name cannot be empty." end
    if #name > 24 then return false, "Name too long (max 24 chars)." end
    if ProfileCount() >= MAX_PROFILES and not GraphicsProfileSwitcherDB.profiles[name] then
        return false, "Maximum of " .. MAX_PROFILES .. " profiles reached."
    end
    GraphicsProfileSwitcherDB.profiles[name] = SnapshotCVars()
    return true
end

local function ApplyProfile(name)
    local profile = GraphicsProfileSwitcherDB.profiles[name]
    if not profile then return false end
    for _, cvar in ipairs(TRACKED_CVARS) do
        if profile[cvar] then SetCVarSafe(cvar, profile[cvar]) end
    end
    GraphicsProfileSwitcherDB.activeProfile = name
    return true
end

local function DeleteProfile(name)
    GraphicsProfileSwitcherDB.profiles[name] = nil
    if GraphicsProfileSwitcherDB.activeProfile == name then
        GraphicsProfileSwitcherDB.activeProfile = nil
    end
end

-------------------------------------------------------------------------------
--  Panel ref
-------------------------------------------------------------------------------
local panelRef

local GREEN = "|cff0cd29f"
local GOLD  = "|cffffd100"
local WHITE = "|cffffffff"
local GRAY  = "|cffaaaaaa"
local RED   = "|cffff4444"
local RESET = "|r"

-------------------------------------------------------------------------------
--  UI
-------------------------------------------------------------------------------
local function BuildPanel(panel)

    local function MakeDivider(anchor, dy)
        local d = panel:CreateTexture(nil, "ARTWORK")
        d:SetColorTexture(1, 1, 1, 0.08)
        d:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, dy or -12)
        d:SetSize(540, 1)
        return d
    end

    --------------------------------------------------------------------
    --  Header
    --------------------------------------------------------------------
    local title = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 16, -16)
    title:SetText(GREEN .. "Graphics Profile Switcher" .. RESET)

    local sub = panel:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    sub:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -6)
    sub:SetJustifyH("LEFT")
    sub:SetText("Apply a built-in preset or save up to " .. MAX_PROFILES .. " custom profiles.")

    local div1 = MakeDivider(sub, -10)

    --------------------------------------------------------------------
    --  Active profile status
    --------------------------------------------------------------------
    local activeLabel = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    activeLabel:SetPoint("TOPLEFT", div1, "BOTTOMLEFT", 0, -14)
    activeLabel:SetJustifyH("LEFT")

    local function RefreshActiveLabel()
        local ap = GraphicsProfileSwitcherDB.activeProfile
        -- Check if it matches a preset
        for _, preset in ipairs(PRESETS) do
            if ap == preset.id then
                activeLabel:SetText("Active: " .. GOLD .. preset.label .. " (preset)" .. RESET)
                return
            end
        end
        -- Check saved profiles
        if ap and GraphicsProfileSwitcherDB.profiles[ap] then
            activeLabel:SetText("Active: " .. GREEN .. ap .. RESET)
        else
            activeLabel:SetText(GRAY .. "No profile active." .. RESET)
        end
    end
    RefreshActiveLabel()

    local div2 = MakeDivider(activeLabel, -10)

    --------------------------------------------------------------------
    --  Built-in presets
    --------------------------------------------------------------------
    local presetsHeader = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    presetsHeader:SetPoint("TOPLEFT", div2, "BOTTOMLEFT", 0, -14)
    presetsHeader:SetText(GOLD .. "Built-in Presets" .. RESET)

    local lastPresetAnchor = presetsHeader
    for _, preset in ipairs(PRESETS) do
        local p = preset  -- capture

        local btn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
        btn:SetSize(240, 30)
        btn:SetPoint("TOPLEFT", lastPresetAnchor, "BOTTOMLEFT", 0, lastPresetAnchor == presetsHeader and -10 or -6)
        btn:SetText(p.label)
        btn:SetScript("OnClick", function()
            if InCombatLockdown() then
                print(GREEN .. "[Graphics Profile Switcher]" .. RESET .. " Cannot change settings in combat.")
                return
            end
            ApplyPreset(p)
            RefreshActiveLabel()
            print(GREEN .. "[Graphics Profile Switcher]" .. RESET .. " Applied preset: " .. GOLD .. p.label .. RESET)
        end)

        local desc = panel:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        desc:SetPoint("LEFT", btn, "RIGHT", 12, 0)
        desc:SetJustifyH("LEFT")
        desc:SetWidth(260)
        desc:SetText(GRAY .. p.desc .. RESET)

        lastPresetAnchor = btn
    end

    local div3 = MakeDivider(lastPresetAnchor, -14)

    --------------------------------------------------------------------
    --  Saved profiles list
    --------------------------------------------------------------------
    local profHeader = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    profHeader:SetPoint("TOPLEFT", div3, "BOTTOMLEFT", 0, -14)
    profHeader:SetText(WHITE .. "Saved Profiles" .. RESET)

    local rowsFrame = CreateFrame("Frame", nil, panel)
    rowsFrame:SetPoint("TOPLEFT", profHeader, "BOTTOMLEFT", 0, -8)
    rowsFrame:SetSize(540, MAX_PROFILES * 36)

    local emptyMsg = rowsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    emptyMsg:SetPoint("TOPLEFT", rowsFrame, "TOPLEFT", 4, -6)
    emptyMsg:SetText(GRAY .. "No profiles saved yet. Use the box below to save your first one." .. RESET)

    local profileRows = {}

    local function RefreshRows()
        for i = 1, MAX_PROFILES do
            if profileRows[i] then profileRows[i].frame:Hide() end
        end

        local sorted = {}
        for name in pairs(GraphicsProfileSwitcherDB.profiles) do
            table.insert(sorted, name)
        end
        table.sort(sorted)

        emptyMsg:SetShown(#sorted == 0)
        rowsFrame:SetHeight(math.max(36, #sorted * 36))

        for i, name in ipairs(sorted) do
            if not profileRows[i] then
                local row = CreateFrame("Frame", nil, rowsFrame)
                row:SetSize(540, 32)

                local bg = row:CreateTexture(nil, "BACKGROUND")
                bg:SetAllPoints(); bg:SetColorTexture(1, 1, 1, 0.04)

                local nameLabel = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                nameLabel:SetPoint("LEFT", row, "LEFT", 10, 0)
                nameLabel:SetJustifyH("LEFT")
                nameLabel:SetWidth(210)

                local applyBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
                applyBtn:SetSize(80, 24)
                applyBtn:SetPoint("LEFT", nameLabel, "RIGHT", 10, 0)
                applyBtn:SetText("Apply")

                local overwriteBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
                overwriteBtn:SetSize(100, 24)
                overwriteBtn:SetPoint("LEFT", applyBtn, "RIGHT", 6, 0)
                overwriteBtn:SetText("Overwrite")

                local deleteBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
                deleteBtn:SetSize(80, 24)
                deleteBtn:SetPoint("LEFT", overwriteBtn, "RIGHT", 6, 0)
                deleteBtn:SetText("Delete")

                profileRows[i] = { frame=row, nameLabel=nameLabel,
                    applyBtn=applyBtn, overwriteBtn=overwriteBtn, deleteBtn=deleteBtn }
            end

            local row = profileRows[i]
            row.frame:SetPoint("TOPLEFT", rowsFrame, "TOPLEFT", 0, -(i-1)*34)
            row.frame:Show()

            local isActive = (GraphicsProfileSwitcherDB.activeProfile == name)
            row.nameLabel:SetText(isActive and (GREEN .. "▶ " .. name .. RESET) or name)

            local n = name
            row.applyBtn:SetScript("OnClick", function()
                if InCombatLockdown() then
                    print(GREEN .. "[Graphics Profile Switcher]" .. RESET .. " Cannot change settings in combat.")
                    return
                end
                ApplyProfile(n)
                RefreshActiveLabel()
                RefreshRows()
                print(GREEN .. "[Graphics Profile Switcher]" .. RESET .. " Applied: " .. GOLD .. n .. RESET)
            end)
            row.overwriteBtn:SetScript("OnClick", function()
                if InCombatLockdown() then
                    print(GREEN .. "[Graphics Profile Switcher]" .. RESET .. " Cannot change settings in combat.")
                    return
                end
                SaveProfile(n)
                print(GREEN .. "[Graphics Profile Switcher]" .. RESET .. " Overwrote: " .. GOLD .. n .. RESET)
            end)
            row.deleteBtn:SetScript("OnClick", function()
                DeleteProfile(n)
                RefreshRows()
                RefreshActiveLabel()
                print(GREEN .. "[Graphics Profile Switcher]" .. RESET .. " Deleted: " .. n)
            end)
        end
    end

    RefreshRows()

    --------------------------------------------------------------------
    --  Save new profile
    --------------------------------------------------------------------
    local saveSection = CreateFrame("Frame", nil, panel)
    saveSection:SetSize(540, 80)

    local function RepositionSave()
        saveSection:ClearAllPoints()
        saveSection:SetPoint("TOPLEFT", rowsFrame, "BOTTOMLEFT", 0, -16)
    end
    RepositionSave()

    local div4 = saveSection:CreateTexture(nil, "ARTWORK")
    div4:SetColorTexture(1, 1, 1, 0.08)
    div4:SetPoint("TOPLEFT", saveSection, "TOPLEFT", 0, 0)
    div4:SetSize(540, 1)

    local saveHeader = saveSection:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    saveHeader:SetPoint("TOPLEFT", div4, "BOTTOMLEFT", 0, -12)
    saveHeader:SetText(WHITE .. "Save Current Settings as New Profile" .. RESET)

    local inputBox = CreateFrame("EditBox", "GraphicsProfileSwitcherNameInput", saveSection, "InputBoxTemplate")
    inputBox:SetSize(220, 26)
    inputBox:SetPoint("TOPLEFT", saveHeader, "BOTTOMLEFT", 4, -10)
    inputBox:SetAutoFocus(false)
    inputBox:SetMaxLetters(24)

    local hint = saveSection:CreateFontString(nil, "OVERLAY", "GameFontDisable")
    hint:SetPoint("LEFT", inputBox, "LEFT", 6, 0)
    hint:SetText("e.g. Raiding, Ultra Quality...")
    hint:SetShown(true)
    inputBox:HookScript("OnTextChanged", function(self) hint:SetShown(self:GetText() == "") end)
    inputBox:HookScript("OnEditFocusGained", function() hint:Hide() end)
    inputBox:HookScript("OnEditFocusLost", function(self) hint:SetShown(self:GetText() == "") end)

    local feedbackLabel = saveSection:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    feedbackLabel:SetPoint("LEFT", inputBox, "RIGHT", 120, 0)
    feedbackLabel:SetJustifyH("LEFT")
    feedbackLabel:SetText("")

    local feedbackTimer
    local function ShowFeedback(msg)
        feedbackLabel:SetText(msg)
        if feedbackTimer then feedbackTimer:Cancel() end
        feedbackTimer = C_Timer.NewTimer(3, function() feedbackLabel:SetText("") end)
    end

    local saveBtn = CreateFrame("Button", nil, saveSection, "UIPanelButtonTemplate")
    saveBtn:SetSize(100, 28)
    saveBtn:SetPoint("LEFT", inputBox, "RIGHT", 8, 0)
    saveBtn:SetText("Save Profile")
    saveBtn:SetScript("OnClick", function()
        if InCombatLockdown() then ShowFeedback(RED .. "Cannot save in combat." .. RESET) return end
        local name = inputBox:GetText():match("^%s*(.-)%s*$")
        local ok, err = SaveProfile(name)
        if ok then
            inputBox:SetText(""); hint:Show()
            RefreshRows(); RepositionSave()
            ShowFeedback(GREEN .. "Saved!" .. RESET)
            print(GREEN .. "[Graphics Profile Switcher]" .. RESET .. " Saved profile: " .. GOLD .. name .. RESET)
        else
            ShowFeedback(RED .. (err or "Error.") .. RESET)
        end
    end)
    inputBox:SetScript("OnEnterPressed", function() saveBtn:Click() end)

    local countNote = saveSection:CreateFontString(nil, "OVERLAY", "GameFontDisable")
    countNote:SetPoint("TOPLEFT", inputBox, "BOTTOMLEFT", 0, -6)
    countNote:SetJustifyH("LEFT")
    local function RefreshCount()
        countNote:SetText(ProfileCount() .. " / " .. MAX_PROFILES .. " profiles used")
    end
    RefreshCount()

    --------------------------------------------------------------------
    --  Reload UI button
    --------------------------------------------------------------------
    local reloadDiv = panel:CreateTexture(nil, "ARTWORK")
    reloadDiv:SetColorTexture(1, 1, 1, 0.08)
    reloadDiv:SetSize(540, 1)

    local reloadBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    reloadBtn:SetSize(120, 30)
    reloadBtn:SetText("Reload UI")
    reloadBtn:SetScript("OnClick", function() ReloadUI() end)

    local function RepositionReload()
        reloadDiv:ClearAllPoints()
        reloadDiv:SetPoint("TOPLEFT", saveSection, "BOTTOMLEFT", 0, -16)
        reloadBtn:ClearAllPoints()
        reloadBtn:SetPoint("TOPLEFT", reloadDiv, "BOTTOMLEFT", 0, -12)
    end
    RepositionReload()

    --------------------------------------------------------------------
    --  Expose refresh hooks
    --------------------------------------------------------------------
    panel._refresh = function()
        RefreshActiveLabel()
        RefreshRows()
        RefreshCount()
        RepositionSave()
        RepositionReload()
    end
end

-------------------------------------------------------------------------------
--  Register panel with Blizzard Settings
-------------------------------------------------------------------------------
local frame = CreateFrame("Frame")
frame:RegisterEvent("ADDON_LOADED")
frame:SetScript("OnEvent", function(self, event, name)
    if name ~= ADDON then return end
    self:UnregisterEvent("ADDON_LOADED")

    if not GraphicsProfileSwitcherDB then GraphicsProfileSwitcherDB = {} end
    if not GraphicsProfileSwitcherDB.profiles then GraphicsProfileSwitcherDB.profiles = {} end

    local panel = CreateFrame("Frame")
    panel.name = "Graphics Profile Switcher"

    if Settings and Settings.RegisterCanvasLayoutCategory then
        BuildPanel(panel)
        panelRef = panel
        local category = Settings.RegisterCanvasLayoutCategory(panel, "Graphics Profile Switcher")
        Settings.RegisterAddOnCategory(category)
        frame._category = category
    else
        panel:Hide()
        BuildPanel(panel)
        panelRef = panel
        InterfaceOptions_AddCategory(panel)
    end
end)

-------------------------------------------------------------------------------
--  Slash commands  /gps
-------------------------------------------------------------------------------
SLASH_GRAPHICSPROFILESWITCHER1 = "/gps"
SlashCmdList["GRAPHICSPROFILESWITCHER"] = function(msg)
    msg = (msg or ""):match("^%s*(.-)%s*$")
    local lower = msg:lower()

    -- Preset shortcuts
    if lower == "max" or lower == "crank" then
        if InCombatLockdown() then print(GREEN.."[Graphics Profile Switcher]"..RESET.." Cannot change in combat.") return end
        ApplyPreset(PRESETS[1])
        if panelRef then panelRef._refresh() end
        print(GREEN.."[Graphics Profile Switcher]"..RESET.." Applied: "..GOLD..PRESETS[1].label..RESET)

    elseif lower == "balanced" then
        if InCombatLockdown() then print(GREEN.."[Graphics Profile Switcher]"..RESET.." Cannot change in combat.") return end
        ApplyPreset(PRESETS[2])
        if panelRef then panelRef._refresh() end
        print(GREEN.."[Graphics Profile Switcher]"..RESET.." Applied: "..GOLD..PRESETS[2].label..RESET)

    elseif lower == "fps" or lower == "optimize" then
        if InCombatLockdown() then print(GREEN.."[Graphics Profile Switcher]"..RESET.." Cannot change in combat.") return end
        ApplyPreset(PRESETS[3])
        if panelRef then panelRef._refresh() end
        print(GREEN.."[Graphics Profile Switcher]"..RESET.." Applied: "..GOLD..PRESETS[3].label..RESET)

    elseif lower:sub(1,5) == "save " then
        local n = msg:sub(6):match("^%s*(.-)%s*$")
        local ok, err = SaveProfile(n)
        if ok then
            if panelRef then panelRef._refresh() end
            print(GREEN.."[Graphics Profile Switcher]"..RESET.." Saved: "..GOLD..n..RESET)
        else
            print(GREEN.."[Graphics Profile Switcher]"..RESET.." "..RED..(err or "Error.")..RESET)
        end

    elseif lower:sub(1,6) == "apply " then
        local n = msg:sub(7):match("^%s*(.-)%s*$")
        if InCombatLockdown() then print(GREEN.."[Graphics Profile Switcher]"..RESET.." Cannot change in combat.") return end
        if ApplyProfile(n) then
            if panelRef then panelRef._refresh() end
            print(GREEN.."[Graphics Profile Switcher]"..RESET.." Applied: "..GOLD..n..RESET)
        else
            print(GREEN.."[Graphics Profile Switcher]"..RESET.." Profile not found: "..n)
        end

    elseif lower:sub(1,7) == "delete " then
        local n = msg:sub(8):match("^%s*(.-)%s*$")
        DeleteProfile(n)
        if panelRef then panelRef._refresh() end
        print(GREEN.."[Graphics Profile Switcher]"..RESET.." Deleted: "..n)

    elseif lower == "list" then
        print(GREEN.."[Graphics Profile Switcher]"..RESET.." Saved profiles:")
        local any = false
        for name in pairs(GraphicsProfileSwitcherDB.profiles) do
            local active = (GraphicsProfileSwitcherDB.activeProfile == name) and " "..GOLD.."(active)"..RESET or ""
            print("  - "..name..active)
            any = true
        end
        if not any then print("  "..GRAY.."(none)"..RESET) end

    else
        if Settings and Settings.OpenToCategory and frame._category then
            Settings.OpenToCategory(frame._category)
        else
            print(GREEN.."[Graphics Profile Switcher]"..RESET.." Commands:")
            print("  /gps                   — open settings panel")
            print("  /gps crank             — apply 🔥 Crank it to 11")
            print("  /gps balanced          — apply ✨ Looks good, performs fine")
            print("  /gps fps               — apply ⚡ Optimized FPS")
            print("  /gps save <name>    — save current settings as a profile")
            print("  /gps apply <name>   — apply a saved profile")
            print("  /gps delete <name>  — delete a profile")
            print("  /gps list              — list all saved profiles")
        end
    end
end
