-- OldManCursor_Options.lua

local ADDON_NAME = "OldManCursor"
local floor = math.floor
local format = string.format
local tremove = table.remove
local tinsert = table.insert
local tconcat = table.concat

local panel = CreateFrame("Frame", ADDON_NAME .. "OptionsPanel")
panel.name = ADDON_NAME

local State = {
    built = false,
    previewActive = false,
    currentTabKey = "GENERAL",
}

local UI = {
    previewButton = nil,
    UpdatePreviewButtonState = nil,

    layerContent = nil,
    layerScrollFrame = nil,

    linesEnabledCheck = nil,
    lineLengthModeWidgets = nil,
    lineThicknessWidgets = nil,
    lineGapWidgets = nil,
    lineOpacityWidgets = nil,
    lineBlendDropdown = nil,
    lineColorWidgets = nil,

    exportEditBox = nil,
    importEditBox = nil,
    exportStatusText = nil,
    importStatusText = nil,

    profilesStatusText = nil,
    newProfileEditBox = nil,
    existingProfilesDropdown = nil,
    generalVisibilityWidgets = nil,

    lineHeadingText = nil,
    layersHeadingText = nil,
    lineLockOverlay = nil,
    layersLockOverlay = nil,
}

local Lists = {
    layerRows = {},
    layerRowWidgets = {},
    tabs = {},
}

local PROFILE_PREFIX = "OMC:"
local PROFILE_VERSION = "2"

local imexportBoxWidth = 370

local availableTextures = OldManCursor_AvailableTextures

local availableBlendModes = {
    { text = "Blend (Default)", value = "BLEND" },
    { text = "Add (Glow)",      value = "ADD" },
    { text = "Disable (Crisp)", value = "DISABLE" },
    { text = "Mod (Darken)",    value = "MOD" },
    { text = "Mod2x (Strong)",  value = "MOD2X" },
}

-- Class color definitions used by the Colors tab and the color picker side panel.
local CLASS_COLORS_ROWS = {
    {
        { key = "MAGE",        label = "Mage",         r = 0.251, g = 0.78, b = 0.922 },
        { key = "PRIEST",      label = "Priest",        r = 1, g = 1, b = 1 },
        { key = "WARLOCK",     label = "Warlock",       r = 0.529, g = 0.529, b = 0.929 },
    },
    {
        { key = "DEMONHUNTER", label = "Demon Hunter",  r = 0.639, g = 0.188, b = 0.788 },
        { key = "DRUID",       label = "Druid",         r = 1, g = 0.49, b = 0.039 },
        { key = "MONK",        label = "Monk",          r = 0, g = 1, b = 0.6 },
        { key = "ROGUE",       label = "Rogue",         r = 1, g = 0.961, b = 0.412 },
    },
    {
        { key = "EVOKER",      label = "Evoker",        r = 0.2, g = 0.58, b = 0.502 },
        { key = "HUNTER",      label = "Hunter",        r = 0.671, g = 0.831, b = 0.451 },
        { key = "SHAMAN",      label = "Shaman",        r = 0, g = 0.439, b = 0.871 },
    },
    {
        { key = "DEATHKNIGHT", label = "Death Knight",  r = 0.769, g = 0.122, b = 0.231 },
        { key = "PALADIN",     label = "Paladin",       r = 0.961, g = 0.549, b = 0.729 },
        { key = "WARRIOR",     label = "Warrior",       r = 0.78, g = 0.612, b = 0.431 },
    },
}

local CLASS_COLORS_FLAT = {}
for _, _ccRow in ipairs(CLASS_COLORS_ROWS) do
    for _, _ccEntry in ipairs(_ccRow) do
        tinsert(CLASS_COLORS_FLAT, _ccEntry)
    end
end

-- Immutable snapshot of the hardcoded values, captured before any DB overwrite.
local CLASS_COLORS_HARDCODED = {}
for _, _ccEntry in ipairs(CLASS_COLORS_FLAT) do
    CLASS_COLORS_HARDCODED[_ccEntry.key] = { r = _ccEntry.r, g = _ccEntry.g, b = _ccEntry.b }
end

-- Populated by the OMCColorPicker do block below; declared here so CreateColorButton
-- closures (defined earlier) can reference it by the time they execute.
local OMCColorPicker = {}

-- Persists a user-edited class color to SavedVariables and updates the in-memory
-- CLASS_COLORS_FLAT entry so the color picker side panel stays in sync.
local function GetClassColorDB()
    OldManCursorDB = OldManCursorDB or {}
    OldManCursorDB.CLASS_COLORS = OldManCursorDB.CLASS_COLORS or {}
    return OldManCursorDB.CLASS_COLORS
end

local function GetColorVarsDB()
    OldManCursorDB = OldManCursorDB or {}
    OldManCursorDB.COLOR_VARS = OldManCursorDB.COLOR_VARS or {}
    return OldManCursorDB.COLOR_VARS
end

-- Returns ordered list of {id, name, r, g, b} for all color variables.
local function GetColorVarList()
    local db = GetColorVarsDB()
    local out = {}
    for id, v in pairs(db) do
        tinsert(out, { id = id, name = v.name, r = v.r, g = v.g, b = v.b })
    end
    table.sort(out, function(a, b) return a.name < b.name end)
    return out
end

local function GetColorVarByID(id)
    return GetColorVarsDB()[id]
end

local function CreateColorVar(name, r, g, b)
    local db = GetColorVarsDB()
    local id = tostring(math.floor(GetTime() * 1000))
    -- Ensure uniqueness
    while db[id] do id = id .. "x" end
    db[id] = { name = name, r = r, g = g, b = b }
    return id
end

local function UpdateColorVar(id, name, r, g, b)
    local db = GetColorVarsDB()
    if not db[id] then return end
    db[id].name = name
    db[id].r    = r
    db[id].g    = g
    db[id].b    = b
    -- Propagate to all profiles referencing this variable.
    OldManCursorDB = OldManCursorDB or {}
    for _, profile in pairs((OldManCursorDB.PROFILES) or {}) do
        if profile.LINE_COLOR_VAR_REF == id then
            profile.LINE_COLOR = { r, g, b }
        end
        for _, layer in ipairs(profile.LAYERS or {}) do
            if layer.COLOR_VAR_REF == id then
                layer.COLOR = { r, g, b }
            end
        end
    end
end

local function DeleteColorVar(id)
    -- Detach all references first so affected layers keep their current
    -- resolved color but lose the link (bake-in).
    OldManCursorDB = OldManCursorDB or {}
    for _, profile in pairs((OldManCursorDB.PROFILES) or {}) do
        if profile.LINE_COLOR_VAR_REF == id then
            profile.LINE_COLOR_VAR_REF = nil
        end
        for _, layer in ipairs(profile.LAYERS or {}) do
            if layer.COLOR_VAR_REF == id then
                layer.COLOR_VAR_REF = nil
            end
        end
    end
    GetColorVarsDB()[id] = nil
end

local function SaveClassColorToDB(key, r, g, b)
    GetClassColorDB()[key] = { r, g, b }
    for _, ccEntry in ipairs(CLASS_COLORS_FLAT) do
        if ccEntry.key == key then
            ccEntry.r = r
            ccEntry.g = g
            ccEntry.b = b
            break
        end
    end
    -- Propagate to all profiles that hold a class color reference to this key.
    OldManCursorDB = OldManCursorDB or {}
    for _, profile in pairs((OldManCursorDB.PROFILES) or {}) do
        if profile.LINE_COLOR_REF == key then
            profile.LINE_COLOR = { r, g, b }
        end
        for _, layer in ipairs(profile.LAYERS or {}) do
            if layer.COLOR_REF == key then
                layer.COLOR = { r, g, b }
            end
        end
    end
end

local function LoadClassColorsFromDB()
    local colorDB = GetClassColorDB()
    for _, ccEntry in ipairs(CLASS_COLORS_FLAT) do
        local saved = colorDB[ccEntry.key]
        if saved then
            ccEntry.r = saved[1] or ccEntry.r
            ccEntry.g = saved[2] or ccEntry.g
            ccEntry.b = saved[3] or ccEntry.b
        end
    end
end

-- Returns the current character's per-character settings table.
local function GetCharDB()
    if _G.OldManCursor_GetCharacterDB then
        return _G.OldManCursor_GetCharacterDB()
    end
    -- Fallback: should not be reached if load order is correct.
    return OldManCursorDB
end

-- Returns the root SavedVariables table.  Only global (non-profile) keys
-- should be read or written via this function.
local function GetDB()
    OldManCursorDB = OldManCursorDB or {}
    OldManCursorDB.PROFILES = OldManCursorDB.PROFILES or {}

    if OldManCursorDB.NEW_PROFILE_NAME        == nil then OldManCursorDB.NEW_PROFILE_NAME        = ""        end
    if OldManCursorDB.COPY_FROM_PROFILE       == nil then OldManCursorDB.COPY_FROM_PROFILE       = ""        end
    if OldManCursorDB.DELETE_PROFILE          == nil then OldManCursorDB.DELETE_PROFILE          = ""        end
    if OldManCursorDB.EXISTING_PROFILE_SELECT == nil or OldManCursorDB.EXISTING_PROFILE_SELECT == "" or
       not (OldManCursorDB.PROFILES and OldManCursorDB.PROFILES[OldManCursorDB.EXISTING_PROFILE_SELECT]) then
        OldManCursorDB.EXISTING_PROFILE_SELECT = GetCharDB().CURRENT_PROFILE or "DEFAULT"
    end

    return OldManCursorDB
end

-- Returns the per-profile data table currently being edited/previewed.
-- All line settings and layer data should be read and written via this.
local function GetProfileDB()
    if _G.OldManCursor_GetActiveProfileData then
        return _G.OldManCursor_GetActiveProfileData()
    end
    -- Fallback: should not be reached if load order is correct.
    return OldManCursorDB
end

local Trim = _G.OldManCursor_Trim

-- Expands a radio button's hit rect rightward to cover its label so the text
-- is clickable as part of the radio selection.
local function ExtendRadioHitToLabel(radio, label)
    radio:SetHitRectInsets(0, -(label:GetStringWidth() + 6), -2, -2)
end

local function GetEditingProfileName()
    local db = GetDB()
    return db.EXISTING_PROFILE_SELECT or GetCharDB().CURRENT_PROFILE or "DEFAULT"
end

local function GetPreviewTargetProfileName()
    local db = GetDB()
    return db.EXISTING_PROFILE_SELECT or GetCharDB().CURRENT_PROFILE or "DEFAULT"
end

local function IsPreviewActive()
    if _G.OldManCursor_GetPreviewState then
        return _G.OldManCursor_GetPreviewState() and true or false
    end
    return State.previewActive and true or false
end

local function CanApplyLiveEdit()
    return IsPreviewActive()
end

local function UpdateEditingProfileTexts()
    if UI.lineHeadingText then
        UI.lineHeadingText:SetText("Line Settings")
    end
    if UI.layersHeadingText then
        local layerCount = #((GetProfileDB and GetProfileDB() or {}).LAYERS or {})
        UI.layersHeadingText:SetText("Layers (" .. layerCount .. ")")
    end
end

local function RefreshReticle()
    if _G.OldManCursor_ApplySettings then
        _G.OldManCursor_ApplySettings()
    end
end

local function SaveActiveProfile()
    if _G.OldManCursor_SaveActiveProfile then
        _G.OldManCursor_SaveActiveProfile()
    end
end

local function SetPreview(enabled, profileName)
    if _G.OldManCursor_SetPreview then
        _G.OldManCursor_SetPreview(enabled, profileName)
    end
    State.previewActive = IsPreviewActive()
end

local function UpdatePreviewButton()
    if not UI.previewButton then
        return
    end

    State.previewActive = IsPreviewActive()

    if State.previewActive then
        UI.previewButton:SetText("Stop Edit & Preview:")
    else
        UI.previewButton:SetText("Edit & Preview:")
    end
end

UI.UpdatePreviewButtonState = function()
    if not UI.previewButton then
        return
    end

    UI.previewButton:Enable()

    if UI.previewButton.tooltipOverlay then
        UI.previewButton.tooltipOverlay:EnableMouse(false)
        GameTooltip:Hide()
    end

    UpdatePreviewButton()
end

local function UpdateEditModeUI()
    local unlocked = IsPreviewActive()
    -- UpdateEditingProfileTexts() owns the text content; we only set colour here.
    UpdateEditingProfileTexts()

    if UI.lineHeadingText then
        if unlocked then
            UI.lineHeadingText:SetTextColor(1, 0.82, 0, 1)
        else
            UI.lineHeadingText:SetTextColor(1, 0.2, 0.2, 1)
        end
    end

    if UI.layersHeadingText then
        if unlocked then
            UI.layersHeadingText:SetTextColor(1, 0.82, 0, 1)
        else
            UI.layersHeadingText:SetTextColor(1, 0.2, 0.2, 1)
        end
    end

    if UI.lineLockOverlay then
        if unlocked then
            UI.lineLockOverlay:Hide()
        else
            UI.lineLockOverlay:Show()
        end
    end

    if UI.layersLockOverlay then
        if unlocked then
            UI.layersLockOverlay:Hide()
        else
            UI.layersLockOverlay:Show()
        end
    end
end

local function FindEntryByValue(entries, value)
    for _, entry in ipairs(entries) do
        if entry.value == value then
            return entry
        end
    end
    return entries[1]
end

local function Clamp(value, minValue, maxValue)
    if value < minValue then
        return minValue
    end
    if value > maxValue then
        return maxValue
    end
    return value
end

local function SplitString(str, sep)
    local out = {}
    if str == nil or str == "" then
        return out
    end

    local start = 1

    while true do
        local pos = string.find(str, sep, start, true)
        if not pos then
            tinsert(out, string.sub(str, start))
            break
        end
        tinsert(out, string.sub(str, start, pos - 1))
        start = pos + 1
    end

    return out
end

local function EncodeBase64(data)
    local b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    return ((data:gsub(".", function(x)
        local r, byte = "", x:byte()
        for i = 8, 1, -1 do
            r = r .. ((byte % 2 ^ i - byte % 2 ^ (i - 1) > 0) and "1" or "0")
        end
        return r
    end) .. "0000"):gsub("%d%d%d?%d?%d?%d?", function(x)
        if #x < 6 then
            return ""
        end
        local c = 0
        for i = 1, 6 do
            if x:sub(i, i) == "1" then
                c = c + 2 ^ (6 - i)
            end
        end
        return b:sub(c + 1, c + 1)
    end) .. ({ "", "==", "=" })[#data % 3 + 1])
end

local function DecodeBase64(data)
    local b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    data = string.gsub(data or "", "[^" .. b .. "=]", "")

    return (data:gsub(".", function(x)
        if x == "=" then
            return ""
        end

        local r, f = "", (b:find(x, 1, true) or 1) - 1
        for i = 6, 1, -1 do
            r = r .. ((f % 2 ^ i - f % 2 ^ (i - 1) > 0) and "1" or "0")
        end
        return r
    end):gsub("%d%d%d?%d?%d?%d?%d?%d?", function(x)
        if #x ~= 8 then
            return ""
        end

        local c = 0
        for i = 1, 8 do
            if x:sub(i, i) == "1" then
                c = c + 2 ^ (8 - i)
            end
        end
        return string.char(c)
    end))
end

local function SerializeColor(color)
    color = color or {1, 1, 1}
    local r = Clamp(tonumber(color[1]) or 1, 0, 1)
    local g = Clamp(tonumber(color[2]) or 1, 0, 1)
    local b = Clamp(tonumber(color[3]) or 1, 0, 1)
    return format("%.3f,%.3f,%.3f", r, g, b)
end

local function ParseColor(str)
    local parts = SplitString(str or "", ",")
    local r = Clamp(tonumber(parts[1] or "") or 1, 0, 1)
    local g = Clamp(tonumber(parts[2] or "") or 1, 0, 1)
    local b = Clamp(tonumber(parts[3] or "") or 1, 0, 1)
    return { r, g, b }
end

-- Encodes the color reference for a layer or line into a compact tag:
--   ""           = plain RGB, no reference
--   "CC:KEY"     = class color reference (e.g. "CC:WARRIOR")
--   "CU"         = current-class reference
--   "CV:NAME"    = color variable reference; NAME is the variable's display name.
--                  On import: restore the ref if a variable with that name exists,
--                  otherwise fall back to the baked RGB.
local function SerializeColorRef(colorRef, colorCurrentClass, colorVarRef)
    if colorCurrentClass then
        return "CU"
    elseif colorRef and colorRef ~= "" then
        return "CC:" .. colorRef
    elseif colorVarRef then
        local v = GetColorVarByID(colorVarRef)
        return "CV:" .. (v and v.name or "")
    end
    return ""
end

local function SerializeLayer(layer)
    return tconcat({
        layer.TEXTURE or "",
        tostring(Clamp(floor((tonumber(layer.SIZE) or 64) + 0.5), 1, 9999)),
        format("%.2f", Clamp(tonumber(layer.OPACITY) or 1, 0, 1)),
        SerializeColor(layer.COLOR),
        layer.BLEND or "BLEND",
        tostring(Clamp(floor((tonumber(layer.ROTATION) or 0) + 0.5), 0, 999)),
        SerializeColorRef(layer.COLOR_REF, layer.COLOR_CURRENT_CLASS, layer.COLOR_VAR_REF),
    }, "~")
end

local function ParseLayer(str, classColorResolver)
    local parts = SplitString(str or "", "~")
    if #parts < 6 or parts[1] == "" then
        return nil
    end

    local layer = {
        TEXTURE  = parts[1],
        SIZE     = Clamp(floor((tonumber(parts[2]) or 64) + 0.5), 1, 9999),
        OPACITY  = Clamp(tonumber(parts[3]) or 1, 0, 1),
        COLOR    = ParseColor(parts[4]),
        BLEND    = parts[5] ~= "" and parts[5] or "BLEND",
        ROTATION = Clamp(floor((tonumber(parts[6]) or 0) + 0.5), 0, 999),
    }

    -- Field 7 is the color ref tag (present in format version 2+).
    local refTag = parts[7] or ""
    if refTag == "CU" then
        layer.COLOR_CURRENT_CLASS = true
    elseif refTag:sub(1, 3) == "CC:" then
        local classKey = refTag:sub(4)
        if classKey ~= "" then
            layer.COLOR_REF = classKey
            -- Sync the baked color to the current (possibly user-overridden) class color.
            if classColorResolver then
                local r, g, b = classColorResolver(classKey)
                layer.COLOR = { r, g, b }
            end
        end
    elseif refTag:sub(1, 3) == "CV:" then
        local varName = refTag:sub(4)
        -- Try to find a color variable with this name; restore the ref if found.
        for id, v in pairs(GetColorVarsDB()) do
            if v.name == varName then
                layer.COLOR_VAR_REF = id
                layer.COLOR = { v.r, v.g, v.b }
                break
            end
        end
        -- If no match: baked RGB in layer.COLOR is used as plain color, no ref.
    end

    return layer
end

local function GenerateProfileString()
    local profile = GetProfileDB()
    local layerStrings = {}

    for _, layer in ipairs(profile.LAYERS or {}) do
        tinsert(layerStrings, SerializeLayer(layer))
    end

    local raw = tconcat({
        "OMC",
        PROFILE_VERSION,
        tostring(Clamp(floor((tonumber(profile.LINE_GAP_SIZE) or 42) + 0.5), 0, 512)),
        profile.LINE_BLEND or "BLEND",
        format("%.2f", Clamp(tonumber(profile.LINE_OPACITY) or 0.25, 0, 1)),
        tostring(Clamp(floor((tonumber(profile.LINE_THICKNESS) or 1) + 0.5), 1, 10)),
        SerializeColor(profile.LINE_COLOR),
        profile.LINE_LENGTH_MODE or "SCREEN",
        tostring(math.max(1, floor((tonumber(profile.LINE_EDGE_OFFSET_X) or 1) + 0.5))),
        tostring(math.max(1, floor((tonumber(profile.LINE_EDGE_OFFSET_Y) or 1) + 0.5))),
        SerializeColorRef(profile.LINE_COLOR_REF, profile.LINE_COLOR_CURRENT_CLASS, profile.LINE_COLOR_VAR_REF),
        tconcat(layerStrings, "^")
    }, "|")

    return PROFILE_PREFIX .. EncodeBase64(raw)
end

local function ApplyProfileString(profileString)
    if not profileString or profileString == "" then
        return false, "Import box is empty."
    end

    local cleaned = profileString:gsub("%s+", "")
    if string.sub(cleaned, 1, #PROFILE_PREFIX) ~= PROFILE_PREFIX then
        return false, "Invalid prefix."
    end

    local encoded = string.sub(cleaned, #PROFILE_PREFIX + 1)
    local raw = DecodeBase64(encoded)
    if not raw or raw == "" then
        return false, "Could not decode profile string."
    end

    local fields = SplitString(raw, "|")
    if #fields < 10 then
        return false, "Profile string is incomplete."
    end

    if fields[1] ~= "OMC" then
        return false, "Unknown profile format."
    end

    local version = fields[2]
    if version ~= "1" and version ~= "2" then
        return false, "Unsupported profile version."
    end

    local db = GetDB()
    local profileName = GetEditingProfileName()
    local profile = db.PROFILES and db.PROFILES[profileName]
    if not profile then
        return false, "Target profile not found."
    end

    -- Build a resolver that maps a class key to its current r,g,b.
    local function classColorResolver(key)
        for _, e in ipairs(CLASS_COLORS_FLAT) do
            if e.key == key then return e.r, e.g, e.b end
        end
        return 1, 1, 1
    end

    profile.LINE_GAP_SIZE      = Clamp(floor((tonumber(fields[3]) or 42) + 0.5), 0, 512)
    profile.LINE_BLEND         = (fields[4] ~= "" and fields[4]) or "BLEND"
    profile.LINE_OPACITY       = Clamp(tonumber(fields[5]) or 0.25, 0, 1)
    profile.LINE_THICKNESS     = Clamp(floor((tonumber(fields[6]) or 1) + 0.5), 1, 10)
    profile.LINE_COLOR         = ParseColor(fields[7])
    profile.LINE_LENGTH_MODE   = (fields[8] == "OFFSET") and "OFFSET" or "SCREEN"
    profile.LINE_EDGE_OFFSET_X = math.max(1, floor((tonumber(fields[9])  or 1) + 0.5))
    profile.LINE_EDGE_OFFSET_Y = math.max(1, floor((tonumber(fields[10]) or 1) + 0.5))

    -- v2: field 11 = line color ref tag, field 12 = layers blob
    -- v1: field 11 = layers blob (no refs)
    local lineRefTag = ""
    local layerBlob  = ""
    if version == "2" then
        lineRefTag = fields[11] or ""
        layerBlob  = fields[12] or ""
    else
        layerBlob  = fields[11] or ""
    end

    -- Restore the line color reference (clears all three ref fields first).
    profile.LINE_COLOR_REF           = nil
    profile.LINE_COLOR_CURRENT_CLASS = nil
    profile.LINE_COLOR_VAR_REF       = nil
    if lineRefTag == "CU" then
        profile.LINE_COLOR_CURRENT_CLASS = true
    elseif lineRefTag:sub(1, 3) == "CC:" then
        local classKey = lineRefTag:sub(4)
        if classKey ~= "" then
            profile.LINE_COLOR_REF = classKey
            local r, g, b = classColorResolver(classKey)
            profile.LINE_COLOR = { r, g, b }
        end
    elseif lineRefTag:sub(1, 3) == "CV:" then
        local varName = lineRefTag:sub(4)
        for id, v in pairs(GetColorVarsDB()) do
            if v.name == varName then
                profile.LINE_COLOR_VAR_REF = id
                profile.LINE_COLOR = { v.r, v.g, v.b }
                break
            end
        end
        -- If no match: baked RGB in LINE_COLOR is used as plain color, no ref.
    end

    profile.LAYERS = {}

    if layerBlob ~= "" then
        local rawLayers = SplitString(layerBlob, "^")
        for _, rawLayer in ipairs(rawLayers) do
            local parsedLayer = ParseLayer(rawLayer, classColorResolver)
            if parsedLayer then
                tinsert(profile.LAYERS, parsedLayer)
            end
        end
    end

    if #profile.LAYERS == 0 then
        tinsert(profile.LAYERS, {
            TEXTURE = availableTextures[1].value,
            SIZE = 64,
            OPACITY = 1.0,
            COLOR = {1, 1, 1},
            BLEND = "BLEND",
            ROTATION = 0,
        })
    end

    return true, "Profile imported."
end

local function SetProfileStatus(target, message, isError)
    local textWidget = nil

    if target == "EXPORT" then
        textWidget = UI.exportStatusText
    elseif target == "IMPORT" then
        textWidget = UI.importStatusText
    elseif target == "PROFILES" then
        textWidget = UI.profilesStatusText
    end

    if textWidget then
        textWidget:SetText(message or "")
        if isError then
            textWidget:SetTextColor(1, 0.2, 0.2)
        else
            textWidget:SetTextColor(0.2, 1, 0.2)
        end
    end
end

local function CreateMultiLineBox(parent, name, width, height)
    local holder = CreateFrame("Frame", name .. "Holder", parent, "BackdropTemplate")
    holder:SetSize(width, height)
    holder:SetBackdrop({
        bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true,
        tileSize = 16,
        edgeSize = 12,
        insets = { left = 3, right = 3, top = 3, bottom = 3 }
    })
    holder:SetBackdropColor(0, 0, 0, 0.6)
    holder:SetBackdropBorderColor(0.3, 0.3, 0.3, 1)

    local scrollFrame = CreateFrame("ScrollFrame", name .. "ScrollFrame", holder, "UIPanelScrollFrameTemplate")
    scrollFrame:SetPoint("TOPLEFT", 4, -4)
    scrollFrame:SetPoint("BOTTOMRIGHT", -26, 4)

    local editBox = CreateFrame("EditBox", name, scrollFrame)
    editBox:SetMultiLine(true)
    editBox:SetFontObject("ChatFontNormal")
    editBox:SetWidth(width - 34)
    editBox:SetAutoFocus(false)
    editBox:SetTextInsets(4, 4, 4, 4)

    editBox:SetScript("OnEscapePressed", function(self)
        self:ClearFocus()
    end)

    editBox:SetScript("OnTextChanged", function(self)
        local textHeight = self:GetStringHeight()
        local minHeight = height - 8
        if textHeight < minHeight then
            textHeight = minHeight
        end
        self:SetHeight(textHeight + 8)
    end)

    scrollFrame:SetScrollChild(editBox)
    editBox:SetHeight(height - 8)

    local clickCatcher = CreateFrame("Button", nil, holder)
    clickCatcher:SetPoint("TOPLEFT", 4, -4)
    clickCatcher:SetPoint("BOTTOMRIGHT", -26, 4)
    clickCatcher:SetFrameLevel(scrollFrame:GetFrameLevel() - 1)
    clickCatcher:SetScript("OnMouseDown", function()
        editBox:SetFocus()
    end)

    editBox:SetFrameLevel(scrollFrame:GetFrameLevel() + 1)

    holder.scrollFrame = scrollFrame

    return holder, editBox
end

local function SetWidgetEnabled(widget, enabled)
    if not widget then
        return
    end

    if widget.SetEnabled then
        widget:SetEnabled(enabled)
    end

    if widget.Enable then
        if enabled then
            widget:Enable()
        else
            widget:Disable()
        end
    end

    if widget.SetAlpha then
        widget:SetAlpha(enabled and 1 or 0.45)
    end
end

local function CreateEditLockOverlay(parent, topOffset)
    local overlay = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    overlay:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, topOffset or -28)
    overlay:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 0)
    overlay:SetFrameStrata("FULLSCREEN_DIALOG")
    overlay:SetFrameLevel(parent:GetFrameLevel() + 50)
    overlay:EnableMouse(true)
    overlay:SetBackdrop({
        bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
        tile = true,
        tileSize = 16,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    overlay:SetBackdropColor(0, 0, 0, 0.85)

    local text = overlay:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    text:SetPoint("CENTER", overlay, "CENTER", 0, 10)
    text:SetText("Edit & Preview is required")
    text:SetTextColor(1, 0.2, 0.2, 1)

    local subText = overlay:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
    subText:SetPoint("TOP", text, "BOTTOM", 0, -10)
    subText:SetText("Use the button at the top-right to unlock live editing.")
    subText:SetTextColor(1, 1, 1, 1)

    overlay:SetScript("OnMouseDown", function()
    end)

    overlay:Hide()
    return overlay
end

local function GetSavedProfileEntries(excludeName)
    if _G.OldManCursor_GetProfileEntries then
        return _G.OldManCursor_GetProfileEntries(excludeName)
    end

    local db = GetDB()
    local out = {}

    for name in pairs(db.PROFILES or {}) do
        if name ~= excludeName then
            out[#out + 1] = { text = name, value = name }
        end
    end

    table.sort(out, function(a, b)
        if a.value == "DEFAULT" then
            return true
        end
        if b.value == "DEFAULT" then
            return false
        end
        return string.lower(a.text) < string.lower(b.text)
    end)

    return out
end

local function RefreshDropdownSelection(dropdown, entries, selectedValue, fallbackText)
    if not dropdown then
        return
    end

    if not entries or #entries == 0 then
        UIDropDownMenu_SetSelectedValue(dropdown, nil)
        UIDropDownMenu_SetText(dropdown, fallbackText or "")
        return
    end

    local selected = FindEntryByValue(entries, selectedValue or entries[1].value)
    UIDropDownMenu_SetSelectedValue(dropdown, selected.value)
    UIDropDownMenu_SetText(dropdown, selected.text)
end

local function RefreshLineWidgets()
    if UI.linesEnabledCheck then
        UI.linesEnabledCheck:SetChecked(GetProfileDB().LINES_ENABLED ~= false)
    end

    if UI.lineThicknessWidgets and UI.lineThicknessWidgets.RefreshFromDB then
        UI.lineThicknessWidgets:RefreshFromDB()
    end

    if UI.lineGapWidgets and UI.lineGapWidgets.RefreshFromDB then
        UI.lineGapWidgets:RefreshFromDB()
    end

    if UI.lineOpacityWidgets and UI.lineOpacityWidgets.RefreshFromDB then
        UI.lineOpacityWidgets:RefreshFromDB()
    end

    if UI.lineBlendDropdown and UI.lineBlendDropdown.RefreshFromDB then
        UI.lineBlendDropdown:RefreshFromDB()
    end

    if UI.lineColorWidgets and UI.lineColorWidgets.RefreshFromDB then
        UI.lineColorWidgets:RefreshFromDB()
        if UI.lineColorWidgets.refreshLabel then
            UI.lineColorWidgets.refreshLabel()
        end
    end

    if UI.lineLengthModeWidgets then
        if UI.lineLengthModeWidgets.RefreshLimits then
            UI.lineLengthModeWidgets.RefreshLimits()
        end
        if UI.lineLengthModeWidgets.UpdateModeUI then
            UI.lineLengthModeWidgets.UpdateModeUI()
        end
    end

    UpdateEditingProfileTexts()
    UpdatePreviewButton()
    UpdateEditModeUI()
end

local function RefreshAllProfileWidgets()
    local db = GetDB()

    if not db.EXISTING_PROFILE_SELECT or db.EXISTING_PROFILE_SELECT == "" then
        db.EXISTING_PROFILE_SELECT = GetCharDB().CURRENT_PROFILE or "DEFAULT"
    end

    if UI.existingProfilesDropdown and UI.existingProfilesDropdown.RefreshList then
        UI.existingProfilesDropdown:RefreshList()
    end

    if UI.generalVisibilityWidgets and UI.generalVisibilityWidgets.RefreshProfileDropdowns then
        UI.generalVisibilityWidgets:RefreshProfileDropdowns()
    end

    if UI.newProfileEditBox then
        UI.newProfileEditBox:SetText(db.NEW_PROFILE_NAME or "")
    end

    if UI.copyDropdown and UI.copyDropdown.RefreshList then UI.copyDropdown:RefreshList() end
    if UI.deleteDropdown and UI.deleteDropdown.RefreshList then UI.deleteDropdown:RefreshList() end

    State.previewActive = IsPreviewActive()
    RefreshLineWidgets()
    UpdateEditingProfileTexts()
    UpdatePreviewButton()
    UpdateEditModeUI()
end

-- dbGetter is an optional function returning the table that owns `key`.
-- Pass GetProfileDB for profile-level keys, or omit for root-DB keys.
local function CreateValueDropdown(parent, key, labelText, entries, xOffset, yOffset, dbGetter)
    dbGetter = dbGetter or GetDB

    local label = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    label:SetPoint("TOPLEFT", xOffset, yOffset)
    label:SetText(labelText)

    local frameName = ADDON_NAME .. key .. "Dropdown"
    local dropdown = CreateFrame("Frame", frameName, parent, "UIDropDownMenuTemplate")
    dropdown:SetPoint("TOPLEFT", label, "BOTTOMLEFT", -16, -6)
    UIDropDownMenu_SetWidth(dropdown, 160)

    UIDropDownMenu_Initialize(dropdown, function(self, level)
        local db = dbGetter()
        for _, entry in ipairs(entries) do
            local info = UIDropDownMenu_CreateInfo()
            info.text = entry.text
            info.value = entry.value
            info.func = function()
                if not CanApplyLiveEdit() then
                    dropdown:RefreshFromDB()
                    return
                end

                dbGetter()[key] = entry.value
                UIDropDownMenu_SetSelectedValue(dropdown, entry.value)
                UIDropDownMenu_SetText(dropdown, entry.text)
                RefreshReticle()
                UpdateEditingProfileTexts()
                UpdatePreviewButton()
            end
            info.checked = (db[key] == entry.value)
            UIDropDownMenu_AddButton(info, level)
        end
    end)

    dropdown.RefreshFromDB = function(self)
        local db = dbGetter()
        local entry = FindEntryByValue(entries, db[key] or entries[1].value)
        db[key] = entry.value
        UIDropDownMenu_SetSelectedValue(self, entry.value)
        UIDropDownMenu_SetText(self, entry.text)
    end

    dropdown:RefreshFromDB()

    return dropdown
end

local function CreateCompactNumberBox(parent, width, maxLetters, initialValue, formatter)
    local editBox = CreateFrame("EditBox", nil, parent, "InputBoxTemplate")
    editBox:SetSize(width, 20)
    editBox:SetAutoFocus(false)
    editBox:SetNumeric(false)
    editBox:SetMaxLetters(maxLetters)
    editBox:SetAltArrowKeyMode(false)
    editBox:SetMultiLine(false)

    local currentValue = initialValue

    local function SetDisplayValue(value)
        currentValue = value
        if formatter then
            editBox:SetText(formatter(value))
        else
            editBox:SetText(tostring(value or ""))
        end
    end

    editBox:SetScript("OnEscapePressed", function(self)
        SetDisplayValue(currentValue)
        self:ClearFocus()
    end)

    editBox:SetScript("OnEnterPressed", function(self)
        self:ClearFocus()
    end)

    editBox:SetScript("OnEditFocusLost", function(self)
    end)

    SetDisplayValue(initialValue)
    editBox.SetDisplayValue = SetDisplayValue
    return editBox
end

-- getColorRef:    optional function() returning the currently stored class key or nil
-- setColorRef:    optional function(key) writing the class key (or nil) to the profile
-- getColorVarRef: optional function() returning the currently stored color var ID or nil
-- setColorVarRef: optional function(id) writing the color var ID (or nil) to the profile
local function CreateColorButton(parent, getColor, setColor, getColorRef, setColorRef, getColorVarRef, setColorVarRef, getColorCurrentClass, setColorCurrentClass)
    local swatch = CreateFrame("Button", nil, parent)
    swatch:SetSize(12, 12)

    local preview = swatch:CreateTexture(nil, "ARTWORK")
    preview:SetAllPoints(swatch)
    preview:SetTexture("Interface\\Buttons\\WHITE8X8")

    local function UpdatePreview()
        local r, g, b = getColor()
        preview:SetVertexColor(r, g, b, 1)
    end

    swatch:SetScript("OnClick", function()
        if not CanApplyLiveEdit() then return end
        local r, g, b = getColor()
        local currentRef          = getColorRef          and getColorRef()          or nil
        local currentVarRef       = getColorVarRef       and getColorVarRef()       or nil
        local currentCurrentClass = getColorCurrentClass and getColorCurrentClass() or nil
        OMCColorPicker.SetupAndShow({
            r = r, g = g, b = b,
            classRef         = currentRef,
            varRef           = currentVarRef,
            currentClassRef  = currentCurrentClass,
            showColorVariables = true,
            swatchFunc = function()
                if not CanApplyLiveEdit() then return end
                local nr, ng, nb = OMCColorPicker.GetColorRGB()
                setColor(nr, ng, nb)
                if setColorRef          then setColorRef(OMCColorPicker.GetClassRef()) end
                if setColorVarRef       then setColorVarRef(OMCColorPicker.GetVarRef()) end
                if setColorCurrentClass then setColorCurrentClass(OMCColorPicker.GetCurrentClassRef()) end
                UpdatePreview()
                if swatch.OnColorSet then swatch.OnColorSet() end
                RefreshReticle()
                UpdateEditingProfileTexts()
                UpdatePreviewButton()
            end,
            cancelFunc = function(previousValues)
                if previousValues and CanApplyLiveEdit() then
                    setColor(previousValues.r, previousValues.g, previousValues.b)
                    if setColorRef          then setColorRef(previousValues.classRef) end
                    if setColorVarRef       then setColorVarRef(previousValues.varRef) end
                    if setColorCurrentClass then setColorCurrentClass(previousValues.currentClassRef) end
                    UpdatePreview()
                    if swatch.OnColorSet then swatch.OnColorSet() end
                    RefreshReticle()
                    UpdateEditingProfileTexts()
                    UpdatePreviewButton()
                end
            end,
        })
    end)

    UpdatePreview()
    swatch.UpdatePreview = UpdatePreview
    return swatch
end

local layerTexturePopup = nil

local function EnsureLayerTexturePopup()
    if layerTexturePopup then return layerTexturePopup end

    local popup = CreateFrame("Frame", nil, UIParent, "TooltipBackdropTemplate")
    popup:SetSize(196, 200)
    popup:SetFrameStrata("FULLSCREEN_DIALOG")
    popup:SetFrameLevel(100)
    popup:SetBackdropColor(0, 0, 0, 1)
    local solidBg = popup:CreateTexture(nil, "BACKGROUND", nil, -8)
    solidBg:SetAllPoints(popup)
    solidBg:SetColorTexture(0, 0, 0, 0.75)
    popup:Hide()

    local catcher = CreateFrame("Frame", nil, UIParent)
    catcher:SetAllPoints(UIParent)
    catcher:SetFrameStrata("FULLSCREEN_DIALOG")
    catcher:SetFrameLevel(99)
    catcher:EnableMouse(true)
    catcher:Hide()
    catcher:SetScript("OnMouseDown", function()
        popup:Hide()
        catcher:Hide()
    end)

    local scrollBar = CreateFrame("EventFrame", nil, popup, "MinimalScrollBar")
    scrollBar:SetPoint("TOPRIGHT", popup, "TOPRIGHT", -2, -4)
    scrollBar:SetPoint("BOTTOMRIGHT", popup, "BOTTOMRIGHT", -2, 4)

    local scrollBox = CreateFrame("Frame", nil, popup, "WowScrollBoxList")
    scrollBox:SetPoint("TOPLEFT", popup, "TOPLEFT", 2, -4)
    scrollBox:SetPoint("BOTTOMRIGHT", scrollBar, "BOTTOMLEFT", -2, 4)

    local view = CreateScrollBoxListLinearView()
    view:SetElementExtent(20)
    view:SetElementInitializer("Button", function(row, elementData)
        row:SetPushedTextOffset(0, 0)
        row:SetNormalFontObject(GameFontHighlightSmall)
        row:SetHighlightAtlas("search-highlight")
        row:SetText(elementData.text)
        row:GetFontString():SetJustifyH("LEFT")
        row:GetFontString():SetPoint("LEFT", row, "LEFT", 4, 0)
        row:GetFontString():SetPoint("RIGHT", row, "RIGHT", -4, 0)
        if not row.selectionBg then
            local bg = row:CreateTexture(nil, "BACKGROUND")
            bg:SetAllPoints(row)
            bg:SetColorTexture(1, 1, 1, 0.1)
            row.selectionBg = bg
        end
        if not row.selectionBorder then
            local border = row:CreateTexture(nil, "ARTWORK")
            border:SetAllPoints(row)
            border:SetAtlas("search-highlight", true)
            border:SetVertexColor(0.6, 0.6, 0.6, 1)
            border:SetDesaturated(true)
            row.selectionBorder = border
        end
        local isSelected = elementData.value == popup.currentValue
        row.selectionBg:SetShown(isSelected)
        row.selectionBorder:SetShown(isSelected)
        local fontFile, fontSize = GameFontHighlightSmall:GetFont()
        row:GetFontString():SetFont(fontFile, fontSize, isSelected and "OUTLINE" or "")
        row:SetScript("OnClick", function()
            if popup.onSelect then popup.onSelect(elementData) end
            popup:Hide()
            catcher:Hide()
        end)
    end)

    ScrollUtil.InitScrollBoxListWithScrollBar(scrollBox, scrollBar, view)

    popup.catcher = catcher
    popup.scrollBox = scrollBox
    popup.Open = function(self, anchorBtn, currentValue, onSelect)
        self.onSelect = onSelect
        self.owner = anchorBtn
        self.currentValue = currentValue
        scrollBox:SetDataProvider(CreateDataProvider(availableTextures))
        self:ClearAllPoints()
        self:SetPoint("TOPLEFT", anchorBtn, "BOTTOMLEFT", 0, -2)
        self:SetWidth(anchorBtn:GetWidth())
        self:Show()
        catcher:Show()
        if currentValue then
            C_Timer.After(0, function()
                for idx, entry in ipairs(availableTextures) do
                    if entry.value == currentValue then
                        local itemHeight = 20
                        local totalHeight = #availableTextures * itemHeight
                        local viewHeight = scrollBox:GetHeight()
                        local maxScroll = totalHeight - viewHeight
                        if maxScroll > 0 then
                            local target = math.max(0, math.min(maxScroll, (idx - 1) * itemHeight))
                            scrollBox:SetScrollPercentage(target / maxScroll, 0)
                        end
                        break
                    end
                end
            end)
        end
    end

    layerTexturePopup = popup
    return popup
end

local function CreateLayerTextureDropdown(parent, layerIndex)
    local btn = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
    btn:SetSize(196, 22)

    btn.UpdateBtnText = function()
        local l = GetProfileDB().LAYERS
        if l[layerIndex] then
            local selected = FindEntryByValue(availableTextures, l[layerIndex].TEXTURE)
            btn:SetText(selected and selected.text or "")
        end
    end

    btn:SetScript("OnClick", function()
        local popup = EnsureLayerTexturePopup()
        if popup:IsShown() and popup.owner == btn then
            popup:Hide()
            popup.catcher:Hide()
        else
            local currentValue = (GetProfileDB().LAYERS[layerIndex] or {}).TEXTURE
            popup:Open(btn, currentValue, function(elementData)
                if not CanApplyLiveEdit() then return end
                local l = GetProfileDB().LAYERS
                if l[layerIndex] then
                    l[layerIndex].TEXTURE = elementData.value
                    btn:SetText(elementData.text)
                    RefreshReticle()
                    UpdateEditingProfileTexts()
                    UpdatePreviewButton()
                end
            end)
        end
    end)

    btn.UpdateBtnText()
    return btn
end

local function CreateLayerBlendDropdown(parent, layerIndex)
    local dropdown = CreateFrame("Frame", nil, parent, "UIDropDownMenuTemplate")
    UIDropDownMenu_SetWidth(dropdown, 100)
    UIDropDownMenu_SetText(dropdown, "")

    UIDropDownMenu_Initialize(dropdown, function(self, level)
        local layers = GetProfileDB().LAYERS
        for _, entry in ipairs(availableBlendModes) do
            local info = UIDropDownMenu_CreateInfo()
            info.text = entry.text
            info.value = entry.value
            info.func = function()
                if not CanApplyLiveEdit() then return end
                local l = GetProfileDB().LAYERS
                if l[layerIndex] then
                    l[layerIndex].BLEND = entry.value
                    UIDropDownMenu_SetSelectedValue(dropdown, entry.value)
                    UIDropDownMenu_SetText(dropdown, entry.text)
                    RefreshReticle()
                    UpdateEditingProfileTexts()
                    UpdatePreviewButton()
                end
            end
            info.checked = (layers[layerIndex] and layers[layerIndex].BLEND == entry.value)
            UIDropDownMenu_AddButton(info, level)
        end
    end)

    local layers = GetProfileDB().LAYERS
    if layers[layerIndex] then
        local selected = FindEntryByValue(availableBlendModes, layers[layerIndex].BLEND or "BLEND")
        layers[layerIndex].BLEND = selected.value
        UIDropDownMenu_SetSelectedValue(dropdown, selected.value)
        UIDropDownMenu_SetText(dropdown, selected.text)
    end

    return dropdown
end

local function ClearLayerRows()
    for _, row in ipairs(Lists.layerRows) do
        row:Hide()
    end
    wipe(Lists.layerRows)
    wipe(Lists.layerRowWidgets)
end

local function MoveLayerUp(index)
    local layers = GetProfileDB().LAYERS
    if index <= 1 then return end
    layers[index], layers[index - 1] = layers[index - 1], layers[index]
end

local function MoveLayerDown(index)
    local layers = GetProfileDB().LAYERS
    if index >= #layers then return end
    layers[index], layers[index + 1] = layers[index + 1], layers[index]
end


local function CreateStyledLayerSlider(parent, width, minVal, maxVal, step, lowText, highText)
    -- Anonymous frame: no global name, so no stale _G child pointers across rebuilds.
    -- OptionsSliderTemplate in WoW 10+ exposes Low/High/Text as parentKey fields directly
    -- on the slider frame, so we can set them without _G lookups.
    local slider = CreateFrame("Slider", nil, parent, "OptionsSliderTemplate")
    slider:SetWidth(width)
    slider:SetMinMaxValues(minVal, maxVal)
    slider:SetValueStep(step)
    slider:SetObeyStepOnDrag(true)
    if slider.Low  then
        slider.Low:SetText(tostring(lowText))
        slider.Low:SetPoint("TOPLEFT", slider, "BOTTOMLEFT", 3, 3)
    end
    if slider.High then
        slider.High:SetText(tostring(highText))
        slider.High:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", -3, 3)
    end
    if slider.Text then slider.Text:SetText("") end
    return slider
end

local RebuildLayerList

RebuildLayerList = function()
    if not UI.layerContent then
        return
    end

    if layerTexturePopup and layerTexturePopup:IsShown() then
        layerTexturePopup:Hide()
        layerTexturePopup.catcher:Hide()
    end

    local profile = GetProfileDB()
    ClearLayerRows()

    local rowHeight = 130
    local startY = -6

    for i, layer in ipairs(profile.LAYERS or {}) do
        local row = CreateFrame("Frame", nil, UI.layerContent, "InsetFrameTemplate")
        row:SetSize(350, rowHeight)
        row:SetPoint("TOPLEFT", 6, startY - ((i - 1) * rowHeight))
        Lists.layerRows[i] = row

        if i % 2 == 0 then
            local highlight = row:CreateTexture(nil, "BACKGROUND")
            highlight:SetAllPoints(row)
            highlight:SetColorTexture(1, 1, 1, 0.06)
        end

        -- Sub-row 1: number, up/down, color button, dupe button, delete button
        local numberText = row:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        numberText:SetPoint("TOPLEFT", row, "TOPLEFT", 6, -8)
        numberText:SetText("#" .. i)

        local upButton = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
        upButton:SetSize(20, 20)
        upButton:SetText("")
        upButton:SetPoint("TOPLEFT", numberText, "TOPRIGHT", 4, 2)
        upButton:SetScript("OnClick", function()
            if not CanApplyLiveEdit() then return end
            MoveLayerUp(i)
            RebuildLayerList()
            RefreshReticle()
            UpdateEditingProfileTexts()
            UpdatePreviewButton()
        end)

        local upTex = upButton:CreateTexture(nil, "ARTWORK")
        upTex:SetSize(36, 36)
        upTex:SetPoint("CENTER", upButton, "CENTER", 0, 0)
        upTex:SetTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Up")

        local downButton = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
        downButton:SetSize(20, 20)
        downButton:SetText("")
        downButton:SetPoint("LEFT", upButton, "RIGHT", 2, 0)
        downButton:SetScript("OnClick", function()
            if not CanApplyLiveEdit() then return end
            MoveLayerDown(i)
            RebuildLayerList()
            RefreshReticle()
            UpdateEditingProfileTexts()
            UpdatePreviewButton()
        end)

        local downTex = downButton:CreateTexture(nil, "ARTWORK")
        downTex:SetSize(36, 36)
        downTex:SetPoint("CENTER", downButton, "CENTER", 0, 0)
        downTex:SetTexture("Interface\\Buttons\\UI-ScrollBar-ScrollDownButton-Up")

        local colorButton = CreateColorButton(
            row,
            function()
                local layers = GetProfileDB().LAYERS
                local layer = layers[i]
                local varRef = layer and layer.COLOR_VAR_REF
                if varRef then
                    local v = GetColorVarByID(varRef)
                    if v then return v.r, v.g, v.b end
                end
                if layer and layer.COLOR_CURRENT_CLASS then
                    local _, classFilename = UnitClass("player")
                    if classFilename then
                        for _, e in ipairs(CLASS_COLORS_FLAT) do
                            if e.key == classFilename then return e.r, e.g, e.b end
                        end
                    end
                end
                if layer and layer.COLOR_REF then
                    for _, e in ipairs(CLASS_COLORS_FLAT) do
                        if e.key == layer.COLOR_REF then return e.r, e.g, e.b end
                    end
                end
                local color = layer and layer.COLOR or {1, 1, 1}
                return color[1] or 1, color[2] or 1, color[3] or 1
            end,
            function(r, g, b)
                local layers = GetProfileDB().LAYERS
                if layers[i] then layers[i].COLOR = { r, g, b } end
            end,
            function()
                local layers = GetProfileDB().LAYERS
                return layers[i] and layers[i].COLOR_REF or nil
            end,
            function(ref)
                local layers = GetProfileDB().LAYERS
                if layers[i] then layers[i].COLOR_REF = ref end
            end,
            function()
                local layers = GetProfileDB().LAYERS
                return layers[i] and layers[i].COLOR_VAR_REF or nil
            end,
            function(id)
                local layers = GetProfileDB().LAYERS
                if layers[i] then layers[i].COLOR_VAR_REF = id end
            end,
            function()
                local layers = GetProfileDB().LAYERS
                return layers[i] and layers[i].COLOR_CURRENT_CLASS or nil
            end,
            function(val)
                local layers = GetProfileDB().LAYERS
                if layers[i] then layers[i].COLOR_CURRENT_CLASS = val end
            end
        )
        colorButton:SetSize(14, 14)

        local colorLabel = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        colorLabel:SetPoint("LEFT", downButton, "RIGHT", 10, 1)
        colorLabel:SetText("Color")
        colorButton:SetPoint("LEFT", colorLabel, "RIGHT", 4, 0)

        local refLabel = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        refLabel:SetPoint("LEFT", colorButton, "RIGHT", 4, 0)

        local function UpdateLayerRefLabel()
            local layers = GetProfileDB().LAYERS
            local layer = layers[i]
            if layer and layer.COLOR_VAR_REF then
                refLabel:SetText("CV")
            elseif layer and layer.COLOR_CURRENT_CLASS then
                refLabel:SetText("Cu")
            elseif layer and layer.COLOR_REF then
                refLabel:SetText("CC")
            else
                refLabel:SetText("")
            end
        end

        colorButton.OnColorSet = UpdateLayerRefLabel
        UpdateLayerRefLabel()

        local deleteButton = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
        deleteButton:SetSize(64, 20)
        deleteButton:SetText("Delete")
        deleteButton:SetPoint("TOPRIGHT", row, "TOPRIGHT", -4, -5)
        deleteButton:SetScript("OnClick", function()
            if not CanApplyLiveEdit() then return end
            tremove(GetProfileDB().LAYERS, i)
            RebuildLayerList()
            RefreshReticle()
            UpdateEditingProfileTexts()
            UpdatePreviewButton()
        end)

        local dupeButton = CreateFrame("Button", nil, row, "UIPanelButtonTemplate")
        dupeButton:SetSize(48, 20)
        dupeButton:SetText("Dupe")
        dupeButton:SetPoint("RIGHT", deleteButton, "LEFT", -8, 0)
        dupeButton:SetScript("OnClick", function()
            if not CanApplyLiveEdit() then return end
            local layers = GetProfileDB().LAYERS
            if layers[i] then
                local src = layers[i]
                local copy = {
                    TEXTURE  = src.TEXTURE,
                    SIZE     = src.SIZE,
                    OPACITY  = src.OPACITY,
                    COLOR    = { (src.COLOR or {})[1] or 1, (src.COLOR or {})[2] or 1, (src.COLOR or {})[3] or 1 },
                    BLEND    = src.BLEND,
                    ROTATION = src.ROTATION,
                }
                tinsert(layers, i + 1, copy)
                RebuildLayerList()
                RefreshReticle()
                UpdateEditingProfileTexts()
                UpdatePreviewButton()
            end
        end)

        -- Sub-row 2: texture dropdown (left, widened) | blend dropdown (right)
        -- Blend frame width ≈ SetWidth+32 = 132; texture fills from x=8 to 8px before blend.
        local textureDropdown = CreateLayerTextureDropdown(row, i)
        textureDropdown:SetPoint("TOPLEFT", row, "TOPLEFT", 8, -34)

        local blendDropdown = CreateLayerBlendDropdown(row, i)
        blendDropdown:SetPoint("TOPRIGHT", row, "TOPRIGHT", -6, -34)

        -- Sub-row 3: size label + box + slider (fills remainder of row)
        local sizeLabel = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        sizeLabel:SetPoint("TOPLEFT", row, "TOPLEFT", 10, -68)
        sizeLabel:SetText("Size")

        local sizeBox = CreateCompactNumberBox(row, 42, 4, layer.SIZE or 64)
        sizeBox:SetPoint("LEFT", sizeLabel, "RIGHT", 8, 0)

        local sizeSlider = CreateStyledLayerSlider(row, 230, 2, 2048, 1, "2", "2048")
        sizeSlider:SetPoint("LEFT", sizeBox, "RIGHT", 15, 0)

        local resettingSize = false
        local function SetSizeSlider(v)
            resettingSize = true; sizeSlider:SetValue(v); resettingSize = false
        end
        local function GetLayerSize() return (GetProfileDB().LAYERS[i] and GetProfileDB().LAYERS[i].SIZE) or 64 end

        sizeSlider:SetScript("OnValueChanged", function(self, value)
            if resettingSize then return end
            if not CanApplyLiveEdit() then
                local sz = GetLayerSize()
                SetSizeSlider(sz); sizeBox:SetText(tostring(sz)); return
            end
            value = Clamp(floor(value + 0.5), 2, 2048)
            local layers = GetProfileDB().LAYERS
            if layers[i] then layers[i].SIZE = value end
            sizeBox:SetText(tostring(value))
            RefreshReticle(); UpdateEditingProfileTexts(); UpdatePreviewButton()
        end)
        sizeBox:SetScript("OnEnterPressed", function(self)
            if not CanApplyLiveEdit() then
                self:SetText(tostring(GetLayerSize())); self:ClearFocus(); return
            end
            local v = Clamp(floor((tonumber(self:GetText()) or GetLayerSize()) + 0.5), 2, 2048)
            local layers = GetProfileDB().LAYERS
            if layers[i] then layers[i].SIZE = v end
            SetSizeSlider(v); self:SetText(tostring(v)); self:ClearFocus()
            RefreshReticle(); UpdateEditingProfileTexts(); UpdatePreviewButton()
        end)
        sizeBox:SetScript("OnEscapePressed", function(self)
            self:SetText(tostring(GetLayerSize())); self:ClearFocus()
        end)
        sizeBox:SetScript("OnEditFocusLost", function(self)
            if not CanApplyLiveEdit() then
                self:SetText(tostring(GetLayerSize())); return
            end
            local v = Clamp(floor((tonumber(self:GetText()) or GetLayerSize()) + 0.5), 2, 2048)
            local layers = GetProfileDB().LAYERS
            if layers[i] then layers[i].SIZE = v end
            SetSizeSlider(v); self:SetText(tostring(v))
            RefreshReticle(); UpdateEditingProfileTexts(); UpdatePreviewButton()
        end)
        do
            local sz = Clamp(floor(((layer.SIZE) or 64) + 0.5), 2, 2048)
            SetSizeSlider(sz); sizeBox:SetText(tostring(sz))
        end

        -- Sub-row 4: opacity (left half) | rotation (right half)
        local opacityLabel = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        opacityLabel:SetPoint("TOPLEFT", row, "TOPLEFT", 10, -102)
        opacityLabel:SetText("Opac")

        local opacityBox = CreateCompactNumberBox(row, 42, 5, layer.OPACITY or 1, function(v) return format("%.2f", v or 1) end)
        opacityBox:SetPoint("LEFT", opacityLabel, "RIGHT", 8, 0)

        local opacSlider = CreateStyledLayerSlider(row, 70, 0, 1, 0.01, "0", "1")
        opacSlider:SetPoint("LEFT", opacityBox, "RIGHT", 15, 0)

        local resettingOpac = false
        local function SetOpacSlider(v)
            resettingOpac = true; opacSlider:SetValue(v); resettingOpac = false
        end
        local function GetLayerOpac() return (GetProfileDB().LAYERS[i] and GetProfileDB().LAYERS[i].OPACITY) or 1 end
        local function NormalizeOpac(v) return Clamp(tonumber(format("%.2f", tonumber(v) or 1)) or 1, 0, 1) end

        opacSlider:SetScript("OnValueChanged", function(self, value)
            if resettingOpac then return end
            if not CanApplyLiveEdit() then
                local op = GetLayerOpac()
                SetOpacSlider(op); opacityBox:SetText(format("%.2f", op)); return
            end
            value = NormalizeOpac(value)
            local layers = GetProfileDB().LAYERS
            if layers[i] then layers[i].OPACITY = value end
            opacityBox:SetText(format("%.2f", value))
            RefreshReticle(); UpdateEditingProfileTexts(); UpdatePreviewButton()
        end)
        opacityBox:SetScript("OnEnterPressed", function(self)
            if not CanApplyLiveEdit() then
                self:SetText(format("%.2f", GetLayerOpac())); self:ClearFocus(); return
            end
            local v = NormalizeOpac(self:GetText())
            local layers = GetProfileDB().LAYERS
            if layers[i] then layers[i].OPACITY = v end
            SetOpacSlider(v); self:SetText(format("%.2f", v)); self:ClearFocus()
            RefreshReticle(); UpdateEditingProfileTexts(); UpdatePreviewButton()
        end)
        opacityBox:SetScript("OnEscapePressed", function(self)
            self:SetText(format("%.2f", GetLayerOpac())); self:ClearFocus()
        end)
        opacityBox:SetScript("OnEditFocusLost", function(self)
            if not CanApplyLiveEdit() then
                self:SetText(format("%.2f", GetLayerOpac())); return
            end
            local v = NormalizeOpac(self:GetText())
            local layers = GetProfileDB().LAYERS
            if layers[i] then layers[i].OPACITY = v end
            SetOpacSlider(v); self:SetText(format("%.2f", v))
            RefreshReticle(); UpdateEditingProfileTexts(); UpdatePreviewButton()
        end)
        do
            local op = NormalizeOpac((layer.OPACITY) or 1)
            SetOpacSlider(op); opacityBox:SetText(format("%.2f", op))
        end

        local rotationLabel = row:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        rotationLabel:SetPoint("LEFT", opacSlider, "RIGHT", 8, 0)
        rotationLabel:SetText("Rota")

        local rotationBox = CreateCompactNumberBox(row, 36, 3, layer.ROTATION or 0)
        rotationBox:SetPoint("LEFT", rotationLabel, "RIGHT", 8, 0)

        local rotaSlider = CreateStyledLayerSlider(row, 70, 0, 359, 1, "0", "359")
        rotaSlider:SetPoint("LEFT", rotationBox, "RIGHT", 15, 0)

        local resettingRota = false
        local function SetRotaSlider(v)
            resettingRota = true; rotaSlider:SetValue(v); resettingRota = false
        end
        local function GetLayerRota() return (GetProfileDB().LAYERS[i] and GetProfileDB().LAYERS[i].ROTATION) or 0 end

        rotaSlider:SetScript("OnValueChanged", function(self, value)
            if resettingRota then return end
            if not CanApplyLiveEdit() then
                local rot = GetLayerRota()
                SetRotaSlider(rot); rotationBox:SetText(tostring(rot)); return
            end
            value = Clamp(floor(value + 0.5), 0, 359)
            local layers = GetProfileDB().LAYERS
            if layers[i] then layers[i].ROTATION = value end
            rotationBox:SetText(tostring(value))
            RefreshReticle(); UpdateEditingProfileTexts(); UpdatePreviewButton()
        end)
        rotationBox:SetScript("OnEnterPressed", function(self)
            if not CanApplyLiveEdit() then
                self:SetText(tostring(GetLayerRota())); self:ClearFocus(); return
            end
            local v = Clamp(floor((tonumber(self:GetText()) or GetLayerRota()) + 0.5), 0, 359)
            local layers = GetProfileDB().LAYERS
            if layers[i] then layers[i].ROTATION = v end
            SetRotaSlider(v); self:SetText(tostring(v)); self:ClearFocus()
            RefreshReticle(); UpdateEditingProfileTexts(); UpdatePreviewButton()
        end)
        rotationBox:SetScript("OnEscapePressed", function(self)
            self:SetText(tostring(GetLayerRota())); self:ClearFocus()
        end)
        rotationBox:SetScript("OnEditFocusLost", function(self)
            if not CanApplyLiveEdit() then
                self:SetText(tostring(GetLayerRota())); return
            end
            local v = Clamp(floor((tonumber(self:GetText()) or GetLayerRota()) + 0.5), 0, 359)
            local layers = GetProfileDB().LAYERS
            if layers[i] then layers[i].ROTATION = v end
            SetRotaSlider(v); self:SetText(tostring(v))
            RefreshReticle(); UpdateEditingProfileTexts(); UpdatePreviewButton()
        end)
        do
            local rot = Clamp(floor(((layer.ROTATION) or 0) + 0.5), 0, 359)
            SetRotaSlider(rot); rotationBox:SetText(tostring(rot))
        end

        Lists.layerRowWidgets[i] = {
            sizeBox = sizeBox,
            opacityBox = opacityBox,
            rotationBox = rotationBox,
        }
    end

    local contentHeight = math.max(230, (#(profile.LAYERS or {}) * rowHeight) + 12)
    UI.layerContent:SetSize(350, contentHeight)
end

local function AddLayer()
    if not CanApplyLiveEdit() then return end

    local profile = GetProfileDB()
    tinsert(profile.LAYERS, {
        TEXTURE = availableTextures[1].value,
        SIZE = 64,
        OPACITY = 1.0,
        COLOR = {1, 1, 1},
        BLEND = "BLEND",
        ROTATION = 0,
    })

    RebuildLayerList()
    RefreshReticle()
    UpdateEditingProfileTexts()
    UpdatePreviewButton()
end

local function CreateLineThicknessControl(parent, xOffset, yOffset)
    local label = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    label:SetPoint("TOPLEFT", xOffset, yOffset)
    label:SetText("Line Thickness")

    local editBox = CreateFrame("EditBox", ADDON_NAME .. "LineThicknessEditBox", parent, "InputBoxTemplate")
    editBox:SetSize(42, 20)
    editBox:SetPoint("TOPLEFT", label, "BOTTOMLEFT", 6, -6)
    editBox:SetAutoFocus(false)
    editBox:SetNumeric(false)
    editBox:SetMaxLetters(2)
    editBox:SetAltArrowKeyMode(false)
    editBox:SetPropagateKeyboardInput(false)

    local sliderName = ADDON_NAME .. "LineThicknessSlider"
    local slider = CreateFrame("Slider", sliderName, parent, "OptionsSliderTemplate")
    slider:SetPoint("LEFT", editBox, "RIGHT", 15, 0)
    slider:SetWidth(240)
    slider:SetMinMaxValues(1, 10)
    slider:SetValueStep(1)
    slider:SetObeyStepOnDrag(true)

    _G[sliderName .. "Low"]:SetText("1")
    _G[sliderName .. "Low"]:SetPoint("TOPLEFT", slider, "BOTTOMLEFT", 3, 3)
    _G[sliderName .. "High"]:SetText("10")
    _G[sliderName .. "High"]:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", -3, 3)
    _G[sliderName .. "Text"]:SetText("")

    local resetting = false
    local function SetSliderValue(v)
        resetting = true
        slider:SetValue(v)
        resetting = false
    end

    local function ApplyValue(value)
        local profile = GetProfileDB()
        value = Clamp(floor((tonumber(value) or profile.LINE_THICKNESS or 1) + 0.5), 1, 10)
        profile.LINE_THICKNESS = value
        SetSliderValue(value)
        editBox:SetText(tostring(value))
        RefreshReticle()
        UpdateEditingProfileTexts()
        UpdatePreviewButton()
    end

    slider:SetScript("OnValueChanged", function(self, value)
        if resetting then return end
        if not CanApplyLiveEdit() then
            local profile = GetProfileDB()
            SetSliderValue(profile.LINE_THICKNESS or 1)
            editBox:SetText(tostring(profile.LINE_THICKNESS or 1))
            return
        end
        value = floor(value + 0.5)
        GetProfileDB().LINE_THICKNESS = value
        editBox:SetText(tostring(value))
        RefreshReticle()
        UpdateEditingProfileTexts()
        UpdatePreviewButton()
    end)

    editBox:SetScript("OnEnterPressed", function(self)
        if not CanApplyLiveEdit() then
            self:SetText(tostring(GetProfileDB().LINE_THICKNESS or 1))
            self:ClearFocus()
            return
        end
        ApplyValue(self:GetText())
        self:ClearFocus()
    end)

    editBox:SetScript("OnEscapePressed", function(self)
        self:SetText(tostring(GetProfileDB().LINE_THICKNESS or 1))
        self:ClearFocus()
    end)

    editBox:SetScript("OnEditFocusLost", function(self)
        if not CanApplyLiveEdit() then
            self:SetText(tostring(GetProfileDB().LINE_THICKNESS or 1))
            return
        end
        ApplyValue(self:GetText())
    end)

    local widgets = {
        label = label,
        editBox = editBox,
        slider = slider,
    }

    function widgets:RefreshFromDB()
        local profile = GetProfileDB()
        local value = Clamp(floor((tonumber(profile.LINE_THICKNESS) or 1) + 0.5), 1, 10)
        profile.LINE_THICKNESS = value
        SetSliderValue(value)
        self.editBox:SetText(tostring(value))
    end

    widgets:RefreshFromDB()
    return widgets
end

local function CreateLineGapControl(parent, xOffset, yOffset)
    local label = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    label:SetPoint("TOPLEFT", xOffset, yOffset)
    label:SetText("Center Gap Size")

    local editBox = CreateFrame("EditBox", ADDON_NAME .. "LineGapEditBox", parent, "InputBoxTemplate")
    editBox:SetSize(42, 20)
    editBox:SetPoint("TOPLEFT", label, "BOTTOMLEFT", 6, -6)
    editBox:SetAutoFocus(false)
    editBox:SetNumeric(false)
    editBox:SetMaxLetters(4)
    editBox:SetAltArrowKeyMode(false)
    editBox:SetPropagateKeyboardInput(false)

    local sliderName = ADDON_NAME .. "LineGapSlider"
    local slider = CreateFrame("Slider", sliderName, parent, "OptionsSliderTemplate")
    slider:SetPoint("LEFT", editBox, "RIGHT", 15, 0)
    slider:SetWidth(240)
    slider:SetMinMaxValues(0, 512)
    slider:SetValueStep(1)
    slider:SetObeyStepOnDrag(true)

    _G[sliderName .. "Low"]:SetText("0")
    _G[sliderName .. "Low"]:SetPoint("TOPLEFT", slider, "BOTTOMLEFT", 3, 3)
    _G[sliderName .. "High"]:SetText("512")
    _G[sliderName .. "High"]:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", -3, 3)
    _G[sliderName .. "Text"]:SetText("")

    local function RefreshOffsetLimits()
        if UI.lineLengthModeWidgets and UI.lineLengthModeWidgets.RefreshLimits then
            UI.lineLengthModeWidgets.RefreshLimits()
        end
    end

    local resetting = false
    local function SetSliderValue(v)
        resetting = true
        slider:SetValue(v)
        resetting = false
    end

    local function ApplyValue(value)
        local profile = GetProfileDB()
        value = Clamp(floor((tonumber(value) or profile.LINE_GAP_SIZE or 42) + 0.5), 0, 512)
        profile.LINE_GAP_SIZE = value
        SetSliderValue(value)
        editBox:SetText(tostring(value))
        RefreshOffsetLimits()
        RefreshReticle()
        UpdateEditingProfileTexts()
        UpdatePreviewButton()
    end

    slider:SetScript("OnValueChanged", function(self, value)
        if resetting then return end
        if not CanApplyLiveEdit() then
            local profile = GetProfileDB()
            SetSliderValue(profile.LINE_GAP_SIZE or 42)
            editBox:SetText(tostring(profile.LINE_GAP_SIZE or 42))
            return
        end
        value = floor(value + 0.5)
        GetProfileDB().LINE_GAP_SIZE = value
        editBox:SetText(tostring(value))
        RefreshOffsetLimits()
        RefreshReticle()
        UpdateEditingProfileTexts()
        UpdatePreviewButton()
    end)

    editBox:SetScript("OnEnterPressed", function(self)
        if not CanApplyLiveEdit() then
            self:SetText(tostring(GetProfileDB().LINE_GAP_SIZE or 42))
            self:ClearFocus()
            return
        end
        ApplyValue(self:GetText())
        self:ClearFocus()
    end)

    editBox:SetScript("OnEscapePressed", function(self)
        self:SetText(tostring(GetProfileDB().LINE_GAP_SIZE or 42))
        self:ClearFocus()
    end)

    editBox:SetScript("OnEditFocusLost", function(self)
        if not CanApplyLiveEdit() then
            self:SetText(tostring(GetProfileDB().LINE_GAP_SIZE or 42))
            return
        end
        ApplyValue(self:GetText())
    end)

    local widgets = {
        label = label,
        editBox = editBox,
        slider = slider,
    }

    function widgets:RefreshFromDB()
        local profile = GetProfileDB()
        local value = Clamp(floor((tonumber(profile.LINE_GAP_SIZE) or 42) + 0.5), 0, 512)
        profile.LINE_GAP_SIZE = value
        SetSliderValue(value)
        self.editBox:SetText(tostring(value))
        if UI.lineLengthModeWidgets and UI.lineLengthModeWidgets.RefreshLimits then
            UI.lineLengthModeWidgets.RefreshLimits()
        end
    end

    widgets:RefreshFromDB()
    return widgets
end

local function CreateLineOpacityControl(parent, xOffset, yOffset)
    local label = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    label:SetPoint("TOPLEFT", xOffset, yOffset)
    label:SetText("Opacity")

    local editBox = CreateFrame("EditBox", ADDON_NAME .. "LineOpacityEditBox", parent, "InputBoxTemplate")
    editBox:SetSize(42, 20)
    editBox:SetPoint("TOPLEFT", label, "BOTTOMLEFT", 6, -6)
    editBox:SetAutoFocus(false)
    editBox:SetNumeric(false)
    editBox:SetMaxLetters(4)
    editBox:SetAltArrowKeyMode(false)
    editBox:SetPropagateKeyboardInput(false)

    local sliderName = ADDON_NAME .. "LineOpacitySlider"
    local slider = CreateFrame("Slider", sliderName, parent, "OptionsSliderTemplate")
    slider:SetPoint("LEFT", editBox, "RIGHT", 15, 0)
    slider:SetWidth(240)
    slider:SetMinMaxValues(0, 1)
    slider:SetValueStep(0.01)
    slider:SetObeyStepOnDrag(true)

    _G[sliderName .. "Low"]:SetText("0")
    _G[sliderName .. "Low"]:SetPoint("TOPLEFT", slider, "BOTTOMLEFT", 3, 3)
    _G[sliderName .. "High"]:SetText("1")
    _G[sliderName .. "High"]:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", -3, 3)
    _G[sliderName .. "Text"]:SetText("")

    local resetting = false
    local function SetSliderValue(v)
        resetting = true
        slider:SetValue(v)
        resetting = false
    end

    local function NormalizeOpacity(value)
        return Clamp(tonumber(format("%.2f", tonumber(value) or 0.25)) or 0.25, 0, 1)
    end

    local function ApplyValue(value)
        local profile = GetProfileDB()
        value = NormalizeOpacity(value)
        profile.LINE_OPACITY = value
        SetSliderValue(value)
        editBox:SetText(format("%.2f", value))
        RefreshReticle()
        UpdateEditingProfileTexts()
        UpdatePreviewButton()
    end

    slider:SetScript("OnValueChanged", function(self, value)
        if resetting then return end
        if not CanApplyLiveEdit() then
            local profile = GetProfileDB()
            SetSliderValue(profile.LINE_OPACITY or 0.25)
            editBox:SetText(format("%.2f", profile.LINE_OPACITY or 0.25))
            return
        end
        value = NormalizeOpacity(value)
        GetProfileDB().LINE_OPACITY = value
        editBox:SetText(format("%.2f", value))
        RefreshReticle()
        UpdateEditingProfileTexts()
        UpdatePreviewButton()
    end)

    editBox:SetScript("OnEnterPressed", function(self)
        if not CanApplyLiveEdit() then
            self:SetText(format("%.2f", GetProfileDB().LINE_OPACITY or 0.25))
            self:ClearFocus()
            return
        end
        ApplyValue(self:GetText())
        self:ClearFocus()
    end)

    editBox:SetScript("OnEscapePressed", function(self)
        self:SetText(format("%.2f", GetProfileDB().LINE_OPACITY or 0.25))
        self:ClearFocus()
    end)

    editBox:SetScript("OnEditFocusLost", function(self)
        if not CanApplyLiveEdit() then
            self:SetText(format("%.2f", GetProfileDB().LINE_OPACITY or 0.25))
            return
        end
        ApplyValue(self:GetText())
    end)

    local widgets = {
        label = label,
        editBox = editBox,
        slider = slider,
    }

    function widgets:RefreshFromDB()
        local profile = GetProfileDB()
        local value = NormalizeOpacity(profile.LINE_OPACITY or 0.25)
        profile.LINE_OPACITY = value
        SetSliderValue(value)
        self.editBox:SetText(format("%.2f", value))
    end

    widgets:RefreshFromDB()
    return widgets
end

local function CreateLineColorControl(parent, xOffset, yOffset)
    local label = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    label:SetPoint("TOPLEFT", xOffset, yOffset)
    label:SetText("Color")

    local colorButton = CreateColorButton(
        parent,
        function()
            local profile = GetProfileDB()
            local varRef = profile.LINE_COLOR_VAR_REF
            if varRef then
                local v = GetColorVarByID(varRef)
                if v then return v.r, v.g, v.b end
            end
            if profile.LINE_COLOR_CURRENT_CLASS then
                local _, classFilename = UnitClass("player")
                if classFilename then
                    for _, e in ipairs(CLASS_COLORS_FLAT) do
                        if e.key == classFilename then return e.r, e.g, e.b end
                    end
                end
            end
            local ref = profile.LINE_COLOR_REF
            if ref then
                for _, e in ipairs(CLASS_COLORS_FLAT) do
                    if e.key == ref then return e.r, e.g, e.b end
                end
            end
            local color = profile.LINE_COLOR or {1, 1, 1}
            return color[1] or 1, color[2] or 1, color[3] or 1
        end,
        function(r, g, b)
            local profile = GetProfileDB()
            profile.LINE_COLOR = { r, g, b }
        end,
        function()
            return GetProfileDB().LINE_COLOR_REF
        end,
        function(ref)
            GetProfileDB().LINE_COLOR_REF = ref
        end,
        function()
            return GetProfileDB().LINE_COLOR_VAR_REF
        end,
        function(id)
            GetProfileDB().LINE_COLOR_VAR_REF = id
        end,
        function()
            return GetProfileDB().LINE_COLOR_CURRENT_CLASS or nil
        end,
        function(val)
            GetProfileDB().LINE_COLOR_CURRENT_CLASS = val
        end
    )
    colorButton:SetSize(20, 20)
    colorButton:SetPoint("TOPLEFT", label, "BOTTOMLEFT", 6, -8)

    local refLabel = colorButton:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    refLabel:SetPoint("LEFT", colorButton, "RIGHT", 4, 0)

    local function UpdateRefLabel()
        local profile = GetProfileDB()
        if profile.LINE_COLOR_VAR_REF then
            refLabel:SetText("CV")
        elseif profile.LINE_COLOR_CURRENT_CLASS then
            refLabel:SetText("Cu")
        elseif profile.LINE_COLOR_REF then
            refLabel:SetText("CC")
        else
            refLabel:SetText("")
        end
    end

    colorButton.OnColorSet = UpdateRefLabel

    local widgets = {
        label = label,
        button = colorButton,
        refreshLabel = UpdateRefLabel,
    }

    function widgets:RefreshFromDB()
        local profile = GetProfileDB()
        profile.LINE_COLOR = profile.LINE_COLOR or {1, 1, 1}
        if self.button and self.button.UpdatePreview then
            self.button:UpdatePreview()
        end
        UpdateRefLabel()
    end

    widgets:RefreshFromDB()
    return widgets
end

local function CreateLineLengthModeControls(parent, xOffset, yOffset)
    local screenRadio = CreateFrame("CheckButton", nil, parent, "UIRadioButtonTemplate")
    screenRadio:SetPoint("TOPLEFT", xOffset, yOffset)

    local screenText = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    screenText:SetPoint("LEFT", screenRadio, "RIGHT", 4, 0)
    screenText:SetText("Line Length: Screen Size")
    ExtendRadioHitToLabel(screenRadio, screenText)

    local offsetRadio = CreateFrame("CheckButton", nil, parent, "UIRadioButtonTemplate")
    offsetRadio:SetPoint("TOPLEFT", xOffset, yOffset - 28)

    local offsetText = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    offsetText:SetPoint("LEFT", offsetRadio, "RIGHT", 4, 0)
    offsetText:SetText("Line Length: Reduction")
    ExtendRadioHitToLabel(offsetRadio, offsetText)

    local hLabel = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    hLabel:SetPoint("TOPLEFT", xOffset + 24, yOffset - 56)
    hLabel:SetText("Horizontal Reduction")

    local hEditBox = CreateFrame("EditBox", ADDON_NAME .. "LineEdgeOffsetXEditBox", parent, "InputBoxTemplate")
    hEditBox:SetSize(42, 20)
    hEditBox:SetPoint("TOPLEFT", hLabel, "BOTTOMLEFT", 6, -6)
    hEditBox:SetAutoFocus(false)
    hEditBox:SetNumeric(false)
    hEditBox:SetMaxLetters(5)
    hEditBox:SetAltArrowKeyMode(false)
    hEditBox:SetPropagateKeyboardInput(false)

    local hSlider = CreateFrame("Slider", nil, parent, "OptionsSliderTemplate")
    hSlider:SetPoint("LEFT", hEditBox, "RIGHT", 15, 0)
    hSlider:SetWidth(240)
    hSlider:SetMinMaxValues(1, UIParent:GetWidth() * 2)
    hSlider:SetValueStep(1)
    hSlider:SetObeyStepOnDrag(true)
    if hSlider.Low  then hSlider.Low:SetText("1"); hSlider.Low:SetPoint("TOPLEFT", hSlider, "BOTTOMLEFT", 3, 3) end
    if hSlider.High then hSlider.High:SetPoint("TOPRIGHT", hSlider, "BOTTOMRIGHT", -3, 3) end
    if hSlider.Text then hSlider.Text:SetText("") end

    local vLabel = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    vLabel:SetPoint("TOPLEFT", xOffset + 24, yOffset - 114)
    vLabel:SetText("Vertical Reduction")

    local vEditBox = CreateFrame("EditBox", ADDON_NAME .. "LineEdgeOffsetYEditBox", parent, "InputBoxTemplate")
    vEditBox:SetSize(42, 20)
    vEditBox:SetPoint("TOPLEFT", vLabel, "BOTTOMLEFT", 6, -6)
    vEditBox:SetAutoFocus(false)
    vEditBox:SetNumeric(false)
    vEditBox:SetMaxLetters(5)
    vEditBox:SetAltArrowKeyMode(false)
    vEditBox:SetPropagateKeyboardInput(false)

    local vSlider = CreateFrame("Slider", nil, parent, "OptionsSliderTemplate")
    vSlider:SetPoint("LEFT", vEditBox, "RIGHT", 15, 0)
    vSlider:SetWidth(240)
    vSlider:SetMinMaxValues(1, UIParent:GetHeight() * 2)
    vSlider:SetValueStep(1)
    vSlider:SetObeyStepOnDrag(true)
    if vSlider.Low  then vSlider.Low:SetText("1"); vSlider.Low:SetPoint("TOPLEFT", vSlider, "BOTTOMLEFT", 3, 3) end
    if vSlider.High then vSlider.High:SetPoint("TOPRIGHT", vSlider, "BOTTOMRIGHT", -3, 3) end
    if vSlider.Text then vSlider.Text:SetText("") end

    local resettingH = false
    local function SetHSliderValue(v)
        resettingH = true
        hSlider:SetValue(v)
        resettingH = false
    end

    local resettingV = false
    local function SetVSliderValue(v)
        resettingV = true
        vSlider:SetValue(v)
        resettingV = false
    end

    local function ApplyHorizontal(value)
        local profile = GetProfileDB()
        local maxValue = floor(UIParent:GetWidth() * 2)
        value = Clamp(floor((tonumber(value) or profile.LINE_EDGE_OFFSET_X or 1) + 0.5), 1, maxValue)
        profile.LINE_EDGE_OFFSET_X = value
        SetHSliderValue(value)
        hEditBox:SetText(tostring(value))
        RefreshReticle()
        UpdateEditingProfileTexts()
        UpdatePreviewButton()
    end

    local function ApplyVertical(value)
        local profile = GetProfileDB()
        local maxValue = floor(UIParent:GetHeight() * 2)
        value = Clamp(floor((tonumber(value) or profile.LINE_EDGE_OFFSET_Y or 1) + 0.5), 1, maxValue)
        profile.LINE_EDGE_OFFSET_Y = value
        SetVSliderValue(value)
        vEditBox:SetText(tostring(value))
        RefreshReticle()
        UpdateEditingProfileTexts()
        UpdatePreviewButton()
    end

    hSlider:SetScript("OnValueChanged", function(self, value)
        if resettingH then return end
        if not CanApplyLiveEdit() then
            local profile = GetProfileDB()
            SetHSliderValue(profile.LINE_EDGE_OFFSET_X or 1)
            hEditBox:SetText(tostring(profile.LINE_EDGE_OFFSET_X or 1))
            return
        end
        value = floor(value + 0.5)
        GetProfileDB().LINE_EDGE_OFFSET_X = value
        hEditBox:SetText(tostring(value))
        RefreshReticle()
        UpdateEditingProfileTexts()
        UpdatePreviewButton()
    end)

    vSlider:SetScript("OnValueChanged", function(self, value)
        if resettingV then return end
        if not CanApplyLiveEdit() then
            local profile = GetProfileDB()
            SetVSliderValue(profile.LINE_EDGE_OFFSET_Y or 1)
            vEditBox:SetText(tostring(profile.LINE_EDGE_OFFSET_Y or 1))
            return
        end
        value = floor(value + 0.5)
        GetProfileDB().LINE_EDGE_OFFSET_Y = value
        vEditBox:SetText(tostring(value))
        RefreshReticle()
        UpdateEditingProfileTexts()
        UpdatePreviewButton()
    end)

    hEditBox:SetScript("OnEnterPressed", function(self)
        if not CanApplyLiveEdit() then
            self:SetText(tostring(GetProfileDB().LINE_EDGE_OFFSET_X or 1))
            self:ClearFocus()
            return
        end
        ApplyHorizontal(self:GetText())
        self:ClearFocus()
    end)

    hEditBox:SetScript("OnEscapePressed", function(self)
        self:SetText(tostring(GetProfileDB().LINE_EDGE_OFFSET_X or 1))
        self:ClearFocus()
    end)

    hEditBox:SetScript("OnEditFocusLost", function(self)
        if not CanApplyLiveEdit() then
            self:SetText(tostring(GetProfileDB().LINE_EDGE_OFFSET_X or 1))
            return
        end
        ApplyHorizontal(self:GetText())
    end)

    vEditBox:SetScript("OnEnterPressed", function(self)
        if not CanApplyLiveEdit() then
            self:SetText(tostring(GetProfileDB().LINE_EDGE_OFFSET_Y or 1))
            self:ClearFocus()
            return
        end
        ApplyVertical(self:GetText())
        self:ClearFocus()
    end)

    vEditBox:SetScript("OnEscapePressed", function(self)
        self:SetText(tostring(GetProfileDB().LINE_EDGE_OFFSET_Y or 1))
        self:ClearFocus()
    end)

    vEditBox:SetScript("OnEditFocusLost", function(self)
        if not CanApplyLiveEdit() then
            self:SetText(tostring(GetProfileDB().LINE_EDGE_OFFSET_Y or 1))
            return
        end
        ApplyVertical(self:GetText())
    end)

    local function UpdateModeUI()
        local profile = GetProfileDB()
        local useOffset = (profile.LINE_LENGTH_MODE == "OFFSET")

        screenRadio:SetChecked(profile.LINE_LENGTH_MODE == "SCREEN")
        offsetRadio:SetChecked(useOffset)

        SetWidgetEnabled(hLabel, useOffset)
        SetWidgetEnabled(hEditBox, useOffset)
        SetWidgetEnabled(hSlider, useOffset)

        SetWidgetEnabled(vLabel, useOffset)
        SetWidgetEnabled(vEditBox, useOffset)
        SetWidgetEnabled(vSlider, useOffset)
    end

    local function RefreshLimits()
        local profile = GetProfileDB()
        -- *1.01 because *1 somehow bugs out if the reduction is maxed out, with additional line thickness it forms a nice cross
        local hMax = floor(UIParent:GetWidth() * 1.01) 
        local vMax = floor(UIParent:GetHeight() * 1.01)

        hSlider:SetMinMaxValues(1, hMax)
        vSlider:SetMinMaxValues(1, vMax)

        if hSlider.High then hSlider.High:SetText(tostring(hMax)) end
        if vSlider.High then vSlider.High:SetText(tostring(vMax)) end

        if (profile.LINE_EDGE_OFFSET_X or 1) > hMax then profile.LINE_EDGE_OFFSET_X = hMax end
        if (profile.LINE_EDGE_OFFSET_Y or 1) > vMax then profile.LINE_EDGE_OFFSET_Y = vMax end

        SetHSliderValue(profile.LINE_EDGE_OFFSET_X or 1)
        SetVSliderValue(profile.LINE_EDGE_OFFSET_Y or 1)

        hEditBox:SetText(tostring(profile.LINE_EDGE_OFFSET_X or 1))
        vEditBox:SetText(tostring(profile.LINE_EDGE_OFFSET_Y or 1))

        screenRadio:SetChecked(profile.LINE_LENGTH_MODE == "SCREEN")
        offsetRadio:SetChecked(profile.LINE_LENGTH_MODE == "OFFSET")
    end

    screenRadio:SetScript("OnClick", function()
        if not CanApplyLiveEdit() then
            UpdateModeUI()
            return
        end
        GetProfileDB().LINE_LENGTH_MODE = "SCREEN"
        UpdateModeUI()
        RefreshReticle()
        UpdateEditingProfileTexts()
        UpdatePreviewButton()
    end)

    offsetRadio:SetScript("OnClick", function()
        if not CanApplyLiveEdit() then
            UpdateModeUI()
            return
        end
        GetProfileDB().LINE_LENGTH_MODE = "OFFSET"
        UpdateModeUI()
        RefreshLimits()
        RefreshReticle()
        UpdateEditingProfileTexts()
        UpdatePreviewButton()
    end)

    RefreshLimits()
    UpdateModeUI()

    return {
        screenRadio = screenRadio,
        offsetRadio = offsetRadio,
        hLabel = hLabel,
        hEditBox = hEditBox,
        hSlider = hSlider,
        vLabel = vLabel,
        vEditBox = vEditBox,
        vSlider = vSlider,
        RefreshLimits = RefreshLimits,
        UpdateModeUI = UpdateModeUI,
    }
end

local function CreateDynamicProfileDropdown(parent, width, xOffset, yOffset, selectedValueKey, excludeCurrentProfile, onSelect)
    local db = GetCharDB()
    local dropdown = CreateFrame("Frame", nil, parent, "UIDropDownMenuTemplate")
    dropdown:SetPoint("TOPLEFT", xOffset, yOffset)
    UIDropDownMenu_SetWidth(dropdown, width or 180)

    dropdown.RefreshList = function(self)
        local excludeName = nil
        if excludeCurrentProfile then
            excludeName = (_G.OldManCursor_GetActiveProfileName and _G.OldManCursor_GetActiveProfileName()) or db.CURRENT_PROFILE
        end

        local entries = GetSavedProfileEntries(excludeName)
        if #entries == 0 then
            UIDropDownMenu_SetSelectedValue(self, nil)
            UIDropDownMenu_SetText(self, "")
            return
        end

        local selectedValue = db[selectedValueKey]
        local selected = FindEntryByValue(entries, selectedValue or entries[1].value)
        db[selectedValueKey] = selected.value
        UIDropDownMenu_SetSelectedValue(self, selected.value)
        UIDropDownMenu_SetText(self, selected.text)
    end

    UIDropDownMenu_Initialize(dropdown, function(self, level)
        local excludeName = nil
        if excludeCurrentProfile then
            excludeName = (_G.OldManCursor_GetActiveProfileName and _G.OldManCursor_GetActiveProfileName()) or db.CURRENT_PROFILE
        end

        local entries = GetSavedProfileEntries(excludeName)

        for _, entry in ipairs(entries) do
            local info = UIDropDownMenu_CreateInfo()
            info.text = entry.text
            info.value = entry.value
            info.func = function()
                db[selectedValueKey] = entry.value
                UIDropDownMenu_SetSelectedValue(dropdown, entry.value)
                UIDropDownMenu_SetText(dropdown, entry.text)
                if onSelect then
                    onSelect(entry.value, entry.text)
                end
                UpdateEditingProfileTexts()
                UpdatePreviewButton()
            end
            info.checked = (db[selectedValueKey] == entry.value)
            UIDropDownMenu_AddButton(info, level)
        end
    end)

    dropdown:RefreshList()
    return dropdown
end

local function CreateActionProfileDropdown(parent, width, anchorTo, anchorPoint, relativePoint, xOffset, yOffset, excludeCurrentProfile, excludeProfileName, onSelect)
    local dropdown = CreateFrame("Frame", nil, parent, "UIDropDownMenuTemplate")
    dropdown:SetPoint(anchorPoint, anchorTo, relativePoint, xOffset, yOffset)
    UIDropDownMenu_SetWidth(dropdown, width or 180)
    UIDropDownMenu_SetText(dropdown, "")

    dropdown.RefreshList = function(self)
        UIDropDownMenu_SetSelectedValue(self, nil)
        UIDropDownMenu_SetText(self, "")
    end

    UIDropDownMenu_Initialize(dropdown, function(self, level)
        local entries = GetSavedProfileEntries(nil)
        local filteredEntries = {}
        local currentProfile = (_G.OldManCursor_GetActiveProfileName and _G.OldManCursor_GetActiveProfileName()) or GetCharDB().CURRENT_PROFILE

        for _, entry in ipairs(entries) do
            local keep = true

            if excludeCurrentProfile and entry.value == currentProfile then
                keep = false
            end

            if type(excludeProfileName) == "function" and excludeProfileName(entry.value) then
                keep = false
            elseif excludeProfileName and entry.value == excludeProfileName then
                keep = false
            end

            if keep then
                tinsert(filteredEntries, entry)
            end
        end

        for _, entry in ipairs(filteredEntries) do
            local info = UIDropDownMenu_CreateInfo()
            info.text = entry.text
            info.value = entry.value
            info.func = function()
                if onSelect then
                    onSelect(entry.value, entry.text)
                end
                UIDropDownMenu_SetSelectedValue(dropdown, nil)
                UIDropDownMenu_SetText(dropdown, "")
            end
            info.checked = false
            UIDropDownMenu_AddButton(info, level)
        end
    end)

    return dropdown
end

local function CreateGeneralVisibilityControls(parent, xOffset, yOffset)
    local db = GetCharDB()

    local modeBox = CreateFrame("Frame", nil, parent, "InsetFrameTemplate")
    modeBox:SetPoint("TOPLEFT", xOffset, yOffset)
    modeBox:SetSize(400, 130)

    local onlyCombatRadio = CreateFrame("CheckButton", nil, parent, "UIRadioButtonTemplate")
    onlyCombatRadio:SetPoint("TOPLEFT", modeBox, "TOPLEFT", 12, -14)

    local onlyCombatText = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    onlyCombatText:SetPoint("LEFT", onlyCombatRadio, "RIGHT", 4, 0)
    onlyCombatText:SetText("Only show during combat")
    ExtendRadioHitToLabel(onlyCombatRadio, onlyCombatText)

    local alwaysShowRadio = CreateFrame("CheckButton", nil, parent, "UIRadioButtonTemplate")
    alwaysShowRadio:SetPoint("TOPLEFT", modeBox, "TOPLEFT", 12, -42)

    local alwaysShowText = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    alwaysShowText:SetPoint("LEFT", alwaysShowRadio, "RIGHT", 4, 0)
    alwaysShowText:SetText("Always show")
    ExtendRadioHitToLabel(alwaysShowRadio, alwaysShowText)

    local sharedProfileLabel = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    sharedProfileLabel:SetPoint("TOPLEFT", modeBox, "TOPLEFT", 36, -74)
    sharedProfileLabel:SetText("Profile")

    local sharedProfileDropdown = CreateDynamicProfileDropdown(
        parent,
        180,
        xOffset + 18,
        yOffset - 90,
        "CURRENT_PROFILE",
        false,
        function(value)
            db.CURRENT_PROFILE = value
            RefreshReticle()
            UpdateEditingProfileTexts()
            UpdatePreviewButton()
        end
    )

    local advancedBox = CreateFrame("Frame", nil, parent, "InsetFrameTemplate")
    advancedBox:SetPoint("TOPLEFT", modeBox, "BOTTOMLEFT", 0, -10)
    advancedBox:SetSize(400, 168)

    local advancedRadio = CreateFrame("CheckButton", nil, parent, "UIRadioButtonTemplate")
    advancedRadio:SetPoint("TOPLEFT", advancedBox, "TOPLEFT", 12, -14)

    local advancedText = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    advancedText:SetPoint("LEFT", advancedRadio, "RIGHT", 4, 0)
    advancedText:SetText("Advanced")
    ExtendRadioHitToLabel(advancedRadio, advancedText)

    local outsideLabel = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    outsideLabel:SetPoint("TOPLEFT", advancedBox, "TOPLEFT", 36, -50)
    outsideLabel:SetText("Profile outside of combat")

    local outsideDropdown = CreateDynamicProfileDropdown(
        parent,
        180,
        xOffset + 18,
        yOffset - 206,
        "PROFILE_OUTSIDE_COMBAT",
        false,
        function(value)
            db.PROFILE_OUTSIDE_COMBAT = value
            RefreshReticle()
            UpdateEditingProfileTexts()
            UpdatePreviewButton()
        end
    )

    local duringLabel = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    duringLabel:SetPoint("TOPLEFT", advancedBox, "TOPLEFT", 36, -108)
    duringLabel:SetText("Profile during combat")

    local duringDropdown = CreateDynamicProfileDropdown(
        parent,
        180,
        xOffset + 18,
        yOffset - 260,
        "PROFILE_DURING_COMBAT",
        false,
        function(value)
            db.PROFILE_DURING_COMBAT = value
            RefreshReticle()
            UpdateEditingProfileTexts()
            UpdatePreviewButton()
        end
    )

    local restedBox = CreateFrame("Frame", nil, parent, "InsetFrameTemplate")
    restedBox:SetPoint("TOPLEFT", advancedBox, "BOTTOMLEFT", 0, -10)
    restedBox:SetSize(400, 54)

    local restedCheck = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
    restedCheck:SetPoint("TOPLEFT", restedBox, "TOPLEFT", 12, -12)
    restedCheck:SetChecked(db.SHOW_IN_RESTED_ZONES and true or false)

    local restedText = parent:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    restedText:SetPoint("LEFT", restedCheck, "RIGHT", 4, 0)
    restedText:SetText("Show in Rested Zones")

    restedCheck:SetScript("OnClick", function(self)
        db.SHOW_IN_RESTED_ZONES = self:GetChecked() and true or false
        RefreshReticle()
        UpdateEditingProfileTexts()
        if UI.UpdatePreviewButtonState then
            UI.UpdatePreviewButtonState()
        end
        UpdatePreviewButton()
    end)

    local function UpdateModeUI()
        local isAdvanced = (db.VISIBILITY_MODE == "ADVANCED")
        local isRegular = not isAdvanced

        onlyCombatRadio:SetChecked(db.VISIBILITY_MODE == "COMBAT")
        alwaysShowRadio:SetChecked(db.VISIBILITY_MODE == "ALWAYS")
        advancedRadio:SetChecked(isAdvanced)

        SetWidgetEnabled(sharedProfileLabel, isRegular)
        SetWidgetEnabled(sharedProfileDropdown, isRegular)

        SetWidgetEnabled(outsideLabel, isAdvanced)
        SetWidgetEnabled(outsideDropdown, isAdvanced)

        SetWidgetEnabled(duringLabel, isAdvanced)
        SetWidgetEnabled(duringDropdown, isAdvanced)

        SetWidgetEnabled(restedCheck, true)
        SetWidgetEnabled(restedText, true)
    end

    onlyCombatRadio:SetScript("OnClick", function()
        db.VISIBILITY_MODE = "COMBAT"
        UpdateModeUI()
        RefreshReticle()
        UpdateEditingProfileTexts()
        if UI.UpdatePreviewButtonState then
            UI.UpdatePreviewButtonState()
        end
        UpdatePreviewButton()
    end)

    alwaysShowRadio:SetScript("OnClick", function()
        db.VISIBILITY_MODE = "ALWAYS"
        UpdateModeUI()
        RefreshReticle()
        UpdateEditingProfileTexts()
        if UI.UpdatePreviewButtonState then
            UI.UpdatePreviewButtonState()
        end
        UpdatePreviewButton()
    end)

    advancedRadio:SetScript("OnClick", function()
        db.VISIBILITY_MODE = "ADVANCED"
        UpdateModeUI()
        RefreshReticle()
        UpdateEditingProfileTexts()
        if UI.UpdatePreviewButtonState then
            UI.UpdatePreviewButtonState()
        end
        UpdatePreviewButton()
    end)

    local function RefreshProfileDropdowns()
        sharedProfileDropdown:RefreshList()
        outsideDropdown:RefreshList()
        duringDropdown:RefreshList()
        restedCheck:SetChecked(db.SHOW_IN_RESTED_ZONES and true or false)
    end

    RefreshProfileDropdowns()
    UpdateModeUI()

    return {
        modeBox = modeBox,
        advancedBox = advancedBox,
        restedBox = restedBox,
        onlyCombatRadio = onlyCombatRadio,
        alwaysShowRadio = alwaysShowRadio,
        sharedProfileLabel = sharedProfileLabel,
        sharedProfileDropdown = sharedProfileDropdown,
        advancedRadio = advancedRadio,
        outsideLabel = outsideLabel,
        outsideDropdown = outsideDropdown,
        duringLabel = duringLabel,
        duringDropdown = duringDropdown,
        restedCheck = restedCheck,
        restedText = restedText,
        UpdateModeUI = UpdateModeUI,
        RefreshProfileDropdowns = RefreshProfileDropdowns,
    }
end

local function ResetProfilesTabSelections()
    local db = GetDB()
    db.COPY_FROM_PROFILE = ""
    db.DELETE_PROFILE = ""
end

local function SelectTab(key)
    local previousTabKey = State.currentTabKey
    State.currentTabKey = key

    if previousTabKey == "LINE" and key ~= "LINE" then
        SaveActiveProfile()
    end

    if previousTabKey == "LAYERS" and key ~= "LAYERS" then
        SaveActiveProfile()
    end

    if previousTabKey == "IMPORT_EXPORT" and key ~= "IMPORT_EXPORT" then
        SaveActiveProfile()
    end

    if previousTabKey == "IMPORT_EXPORT" and key ~= "IMPORT_EXPORT" then
        SetProfileStatus("EXPORT", "", false)
        SetProfileStatus("IMPORT", "", false)

        if UI.exportEditBox then
            UI.exportEditBox:SetText("")
        end
        if UI.importEditBox then
            UI.importEditBox:SetText("")
        end
    end

    if previousTabKey == "PROFILES" and key ~= "PROFILES" then
        ResetProfilesTabSelections()
    end

    for _, tab in pairs(Lists.tabs) do
        if tab.frame then
            tab.frame:Hide()
        end
        if tab.button then
            tab.button:UnlockHighlight()
        end
    end

    local tab = Lists.tabs[key]
    if not tab then
        return
    end

    tab.frame:Show()
    tab.button:LockHighlight()

    if key == "LAYERS" then
        RebuildLayerList()
    elseif key == "LINE" then
        RefreshLineWidgets()
    elseif key == "PROFILES" or key == "GENERAL" then
        RefreshAllProfileWidgets()
    elseif key == "COLORS" then
        if UI.RefreshColorVarDropdowns then UI.RefreshColorVarDropdowns() end
    end

    UpdateEditingProfileTexts()
    UpdatePreviewButton()
    UpdateEditModeUI()
end

-- Custom confirm dialog — replaces StaticPopupDialogs for full hide reliability.
local OMCConfirm
do
    local dialog = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
    dialog:SetSize(340, 130)
    dialog:SetPoint("CENTER")
    dialog:SetFrameStrata("FULLSCREEN_DIALOG")
    dialog:SetFrameLevel(100)
    dialog:EnableMouse(true)
    dialog:SetMovable(true)
    dialog:RegisterForDrag("LeftButton")
    dialog:SetScript("OnDragStart", dialog.StartMoving)
    dialog:SetScript("OnDragStop",  dialog.StopMovingOrSizing)
    dialog:SetBackdrop({
        bgFile   = "Interface\\ChatFrame\\ChatFrameBackground",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 16,
        insets = { left = 4, right = 4, top = 4, bottom = 4 },
    })
    dialog:SetBackdropColor(0.1, 0.1, 0.1, 0.95)
    dialog:SetBackdropBorderColor(0.6, 0.6, 0.6, 1)

    local msgText = dialog:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    msgText:SetPoint("TOP", 0, -22)
    msgText:SetWidth(300)
    msgText:SetJustifyH("CENTER")

    local confirmBtn = CreateFrame("Button", nil, dialog, "UIPanelButtonTemplate")
    confirmBtn:SetSize(100, 24)
    confirmBtn:SetPoint("BOTTOMLEFT", 50, 16)

    local cancelBtn = CreateFrame("Button", nil, dialog, "UIPanelButtonTemplate")
    cancelBtn:SetSize(100, 24)
    cancelBtn:SetPoint("BOTTOMRIGHT", -50, 16)
    cancelBtn:SetText("Cancel")
    cancelBtn:SetScript("OnClick", function() dialog:Hide() end)

    dialog:SetScript("OnKeyDown", function(self, key)
        if key == "ESCAPE" then dialog:Hide() end
    end)

    dialog:Hide()

    OMCConfirm = function(text, confirmLabel, onConfirm)
        msgText:SetText(text or "")
        confirmBtn:SetText(confirmLabel or "OK")
        confirmBtn:SetScript("OnClick", function()
            dialog:Hide()
            if onConfirm then onConfirm() end
        end)
        dialog:Show()
    end
end

-- Custom color picker with integrated Color Variables section.
-- Replaces all direct ColorPickerFrame usage within OMC.
do
    local cpFrame = CreateFrame("Frame", "OMCColorPickerFrame", UIParent, "BackdropTemplate")
    cpFrame:SetSize(340, 400)
    cpFrame:SetPoint("CENTER")
    cpFrame:SetFrameStrata("FULLSCREEN_DIALOG")
    cpFrame:SetFrameLevel(100)
    cpFrame:EnableMouse(true)
    cpFrame:EnableKeyboard(true)
    cpFrame:SetMovable(true)
    cpFrame:RegisterForDrag("LeftButton")
    cpFrame:SetScript("OnDragStart", cpFrame.StartMoving)
    cpFrame:SetScript("OnDragStop",  cpFrame.StopMovingOrSizing)
    cpFrame:SetBackdrop({
        bgFile   = "Interface\\ChatFrame\\ChatFrameBackground",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 14,
        insets = { left = 4, right = 4, top = 4, bottom = 4 },
    })
    cpFrame:SetBackdropColor(0.1, 0.1, 0.1, 0.95)
    cpFrame:SetBackdropBorderColor(0.6, 0.6, 0.6, 1)
    cpFrame:Hide()

    -- Callbacks & color state
    local cbSwatchFunc, cbCancelFunc = nil, nil
    local curR, curG, curB = 1, 1, 1
    local prevR, prevG, prevB = 1, 1, 1
    local prevClassRef = nil
    local prevCurrentClassRef = nil
    local defaultR, defaultG, defaultB = nil, nil, nil
    local updating = false

    -- Forward declarations so slider/hex handlers (built before the radio widgets
    -- and Default button exist) can reference these functions.
    local SelectNone
    local UpdateDefaultBtn

    -- Title
    local cpTitle = cpFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    cpTitle:SetPoint("TOP", 0, -14)
    cpTitle:SetText("Color Picker")

    -- New / Previous swatches
    local newLabel = cpFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    newLabel:SetPoint("TOPLEFT", 20, -38)
    newLabel:SetText("New")

    local newSwatch = cpFrame:CreateTexture(nil, "ARTWORK")
    newSwatch:SetSize(60, 24)
    newSwatch:SetTexture("Interface\\Buttons\\WHITE8X8")
    newSwatch:SetPoint("TOPLEFT", 20, -52)

    local prevLabel = cpFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    prevLabel:SetPoint("TOPLEFT", 92, -38)
    prevLabel:SetText("Current")

    local prevSwatch = cpFrame:CreateTexture(nil, "ARTWORK")
    prevSwatch:SetSize(60, 24)
    prevSwatch:SetTexture("Interface\\Buttons\\WHITE8X8")
    prevSwatch:SetPoint("TOPLEFT", 92, -52)

    -- Divider 1
    local div1 = cpFrame:CreateTexture(nil, "ARTWORK")
    div1:SetSize(308, 1)
    div1:SetPoint("TOPLEFT", 16, -84)
    div1:SetColorTexture(0.4, 0.4, 0.4, 1)

    -- RGB sliders
    local sliderMeta = {
        { label = "R", key = "r" },
        { label = "G", key = "g" },
        { label = "B", key = "b" },
    }
    local sliderControls = {}

    local function GetCurByKey(k)
        if k == "r" then return curR elseif k == "g" then return curG else return curB end
    end
    local function SetCurByKey(k, v)
        if k == "r" then curR = v elseif k == "g" then curG = v else curB = v end
    end

    local function FireSwatch()
        newSwatch:SetVertexColor(curR, curG, curB, 1)
        if UpdateDefaultBtn then UpdateDefaultBtn() end
        if cbSwatchFunc then cbSwatchFunc() end
    end

    for i, sm in ipairs(sliderMeta) do
        local yBase = -94 - (i - 1) * 42

        local lbl = cpFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        lbl:SetPoint("TOPLEFT", 16, yBase)
        lbl:SetText(sm.label)

        local sl = CreateFrame("Slider", nil, cpFrame, "OptionsSliderTemplate")
        sl:SetPoint("TOPLEFT", 32, yBase - 4)
        sl:SetWidth(182)
        sl:SetMinMaxValues(0, 255)
        sl:SetValueStep(1)
        sl:SetObeyStepOnDrag(true)
        if sl.Low  then sl.Low:SetText("0");   sl.Low:SetPoint("TOPLEFT",  sl, "BOTTOMLEFT",   3, 3) end
        if sl.High then sl.High:SetText("255"); sl.High:SetPoint("TOPRIGHT", sl, "BOTTOMRIGHT", -3, 3) end
        if sl.Text then sl.Text:SetText("") end

        local bx = CreateFrame("EditBox", nil, cpFrame, "InputBoxTemplate")
        bx:SetSize(42, 20)
        bx:SetPoint("LEFT", sl, "RIGHT", 8, 0)
        bx:SetAutoFocus(false)
        bx:SetMaxLetters(3)
        bx:SetNumeric(false)
        bx:SetPropagateKeyboardInput(false)

        local guard = false
        local function SliderSet(v) guard = true; sl:SetValue(v); guard = false end

        sl:SetScript("OnValueChanged", function(_, value)
            if guard or updating then return end
            value = Clamp(floor(value + 0.5), 0, 255)
            SetCurByKey(sm.key, value / 255)
            bx:SetText(tostring(value))
            if SelectNone then SelectNone() end
            FireSwatch()
        end)

        local function ApplyBox()
            local v = Clamp(floor((tonumber(bx:GetText()) or 0) + 0.5), 0, 255)
            SetCurByKey(sm.key, v / 255)
            SliderSet(v); bx:SetText(tostring(v))
            if SelectNone then SelectNone() end
            FireSwatch()
        end
        bx:SetScript("OnEnterPressed", function(self) ApplyBox(); self:ClearFocus() end)
        bx:SetScript("OnEditFocusLost", ApplyBox)
        bx:SetScript("OnEscapePressed", function(self)
            self:SetText(tostring(floor(GetCurByKey(sm.key) * 255 + 0.5))); self:ClearFocus()
        end)

        sliderControls[sm.key] = { sl = sl, bx = bx, sliderSet = SliderSet }
    end

    -- Hex input
    local hexLabel = cpFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    hexLabel:SetPoint("TOPLEFT", 16, -224)
    hexLabel:SetText("Hex")

    local hexBox = CreateFrame("EditBox", nil, cpFrame, "InputBoxTemplate")
    hexBox:SetSize(72, 20)
    hexBox:SetPoint("LEFT", hexLabel, "RIGHT", 8, 0)
    hexBox:SetAutoFocus(false)
    hexBox:SetMaxLetters(6)
    hexBox:SetPropagateKeyboardInput(false)

    local function RGBToHex(r, g, b)
        return format("%02X%02X%02X",
            Clamp(floor(r * 255 + 0.5), 0, 255),
            Clamp(floor(g * 255 + 0.5), 0, 255),
            Clamp(floor(b * 255 + 0.5), 0, 255))
    end

    local function RefreshAll()
        updating = true
        local r255 = Clamp(floor(curR * 255 + 0.5), 0, 255)
        local g255 = Clamp(floor(curG * 255 + 0.5), 0, 255)
        local b255 = Clamp(floor(curB * 255 + 0.5), 0, 255)
        sliderControls["r"].sliderSet(r255); sliderControls["r"].bx:SetText(tostring(r255))
        sliderControls["g"].sliderSet(g255); sliderControls["g"].bx:SetText(tostring(g255))
        sliderControls["b"].sliderSet(b255); sliderControls["b"].bx:SetText(tostring(b255))
        hexBox:SetText(RGBToHex(curR, curG, curB))
        newSwatch:SetVertexColor(curR, curG, curB, 1)
        updating = false
    end

    local function ApplyHex(str)
        str = (str or ""):gsub("#", ""):upper()
        if #str ~= 6 then return end
        local r = tonumber(str:sub(1,2), 16)
        local g = tonumber(str:sub(3,4), 16)
        local b = tonumber(str:sub(5,6), 16)
        if not (r and g and b) then return end
        curR, curG, curB = r/255, g/255, b/255
        if SelectNone then SelectNone() end
        RefreshAll()
        FireSwatch()
    end
    hexBox:SetScript("OnEnterPressed", function(self) ApplyHex(self:GetText()); self:ClearFocus() end)
    hexBox:SetScript("OnEditFocusLost", function(self) ApplyHex(self:GetText()) end)
    hexBox:SetScript("OnEscapePressed", function(self)
        self:SetText(RGBToHex(curR, curG, curB)); self:ClearFocus()
    end)

    -- Divider 2 + Color Variables section (shown/hidden as a group)
    local div2 = cpFrame:CreateTexture(nil, "ARTWORK")
    div2:SetSize(308, 1)
    div2:SetPoint("TOPLEFT", 16, -252)
    div2:SetColorTexture(0.4, 0.4, 0.4, 1)

    local cvHeading = cpFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    cvHeading:SetPoint("TOPLEFT", 16, -262)
    cvHeading:SetText("Color Variables")

    local noneRadio = CreateFrame("CheckButton", nil, cpFrame, "UIRadioButtonTemplate")
    noneRadio:SetPoint("TOPLEFT", 12, -282)
    local noneText = cpFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    noneText:SetPoint("LEFT", noneRadio, "RIGHT", 4, 0)
    noneText:SetText("None")
    ExtendRadioHitToLabel(noneRadio, noneText)

    local classRadio = CreateFrame("CheckButton", nil, cpFrame, "UIRadioButtonTemplate")
    classRadio:SetPoint("LEFT", noneText, "RIGHT", 16, 0)
    local classText = cpFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    classText:SetPoint("LEFT", classRadio, "RIGHT", 4, 0)
    classText:SetText("Class Color")
    ExtendRadioHitToLabel(classRadio, classText)

    local currentClassRadio = CreateFrame("CheckButton", nil, cpFrame, "UIRadioButtonTemplate")
    currentClassRadio:SetPoint("LEFT", classText, "RIGHT", 16, 0)
    local currentClassText = cpFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    currentClassText:SetPoint("LEFT", currentClassRadio, "RIGHT", 4, 0)
    currentClassText:SetText("Current Class")
    ExtendRadioHitToLabel(currentClassRadio, currentClassText)

    local classDropdown = CreateFrame("Frame", "OMCCPClassDropdown", cpFrame, "UIDropDownMenuTemplate")
    classDropdown:SetPoint("TOPLEFT", 12, -302)
    UIDropDownMenu_SetWidth(classDropdown, 148)

    local classSwatch = cpFrame:CreateTexture(nil, "OVERLAY")
    classSwatch:SetSize(14, 14)
    classSwatch:SetTexture("Interface\\Buttons\\WHITE8X8")
    classSwatch:SetPoint("LEFT", classDropdown, "RIGHT", -2, 0)

    local varRadio = CreateFrame("CheckButton", nil, cpFrame, "UIRadioButtonTemplate")
    varRadio:SetPoint("TOPLEFT", 12, -330)
    local varText = cpFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    varText:SetPoint("LEFT", varRadio, "RIGHT", 4, 0)
    varText:SetText("Color Variable")
    ExtendRadioHitToLabel(varRadio, varText)

    local varDropdown = CreateFrame("Frame", "OMCCPVarDropdown", cpFrame, "UIDropDownMenuTemplate")
    varDropdown:SetPoint("TOPLEFT", 12, -352)
    UIDropDownMenu_SetWidth(varDropdown, 148)

    local varSwatch = cpFrame:CreateTexture(nil, "OVERLAY")
    varSwatch:SetSize(14, 14)
    varSwatch:SetTexture("Interface\\Buttons\\WHITE8X8")
    varSwatch:SetPoint("LEFT", varDropdown, "RIGHT", -2, 0)

    local useClassColor    = false
    local useCurrentClass  = false
    local selectedClassKey = nil
    local useVarColor      = false
    local selectedVarID    = nil
    local prevVarRef       = nil

    local function GetClassRGB(key)
        for _, e in ipairs(CLASS_COLORS_FLAT) do
            if e.key == key then return e.r, e.g, e.b end
        end
        return 1, 1, 1
    end

    local function GetDefaultClassKey()
        local _, playerClass = UnitClass("player")
        if playerClass then
            for _, e in ipairs(CLASS_COLORS_FLAT) do
                if e.key == playerClass then return playerClass end
            end
        end
        return CLASS_COLORS_FLAT[1] and CLASS_COLORS_FLAT[1].key or "WARRIOR"
    end

    local function SetDropdownToClass(key)
        for _, e in ipairs(CLASS_COLORS_FLAT) do
            if e.key == key then
                UIDropDownMenu_SetSelectedValue(classDropdown, key)
                UIDropDownMenu_SetText(classDropdown, e.label)
                classSwatch:SetVertexColor(e.r, e.g, e.b, 1)
                return
            end
        end
    end

    local function UpdateClassMode()
        -- If no color variables exist, the var radio must stay inactive.
        local hasVars = next(GetColorVarsDB()) ~= nil
        if not hasVars and useVarColor then
            useVarColor   = false
            selectedVarID = nil
        end

        noneRadio:SetChecked(not useClassColor and not useCurrentClass and not useVarColor)
        classRadio:SetChecked(useClassColor)
        currentClassRadio:SetChecked(useCurrentClass)
        varRadio:SetChecked(useVarColor)

        if hasVars then
            varRadio:Enable()
            varText:SetTextColor(1, 1, 1, 1)
        else
            varRadio:Disable()
            varText:SetTextColor(0.5, 0.5, 0.5, 1)
        end

        if useClassColor then
            UIDropDownMenu_EnableDropDown(classDropdown)
            classSwatch:SetAlpha(1)
        else
            UIDropDownMenu_DisableDropDown(classDropdown)
            classSwatch:SetAlpha(0.4)
        end
        if useVarColor then
            UIDropDownMenu_EnableDropDown(varDropdown)
            varSwatch:SetAlpha(1)
        else
            UIDropDownMenu_DisableDropDown(varDropdown)
            varSwatch:SetAlpha(0.4)
        end
    end

    local function ApplyClassColor()
        if not selectedClassKey then return end
        local r, g, b = GetClassRGB(selectedClassKey)
        curR, curG, curB = r, g, b
        RefreshAll()
        FireSwatch()
    end

    local function ApplyCurrentClassColor()
        local _, classFilename = UnitClass("player")
        if classFilename then
            for _, e in ipairs(CLASS_COLORS_FLAT) do
                if e.key == classFilename then
                    curR, curG, curB = e.r, e.g, e.b
                    RefreshAll()
                    FireSwatch()
                    return
                end
            end
        end
        curR, curG, curB = 1, 1, 1
        RefreshAll()
        FireSwatch()
    end

    local function SetDropdownToVar(id)
        local v = id and GetColorVarByID(id)
        if v then
            UIDropDownMenu_SetSelectedValue(varDropdown, id)
            UIDropDownMenu_SetText(varDropdown, v.name)
            varSwatch:SetVertexColor(v.r, v.g, v.b, 1)
        else
            UIDropDownMenu_SetSelectedValue(varDropdown, nil)
            UIDropDownMenu_SetText(varDropdown, "")
            varSwatch:SetVertexColor(0, 0, 0, 1)
        end
    end

    local function ApplyVarColor()
        if not selectedVarID then return end
        local v = GetColorVarByID(selectedVarID)
        if not v then return end
        curR, curG, curB = v.r, v.g, v.b
        RefreshAll()
        FireSwatch()
    end

    UIDropDownMenu_Initialize(classDropdown, function(_, level)
        for _, e in ipairs(CLASS_COLORS_FLAT) do
            local info = UIDropDownMenu_CreateInfo()
            info.text    = e.label
            info.value   = e.key
            info.func    = function()
                if not useClassColor then return end
                selectedClassKey = e.key
                UIDropDownMenu_SetSelectedValue(classDropdown, e.key)
                UIDropDownMenu_SetText(classDropdown, e.label)
                classSwatch:SetVertexColor(e.r, e.g, e.b, 1)
                ApplyClassColor()
            end
            info.checked = (selectedClassKey == e.key)
            UIDropDownMenu_AddButton(info, level)
        end
    end)

    noneRadio:SetScript("OnClick", function()
        useClassColor   = false
        useCurrentClass = false
        useVarColor     = false
        UpdateClassMode()
    end)

    classRadio:SetScript("OnClick", function()
        useClassColor   = true
        useCurrentClass = false
        useVarColor     = false
        if not selectedClassKey then selectedClassKey = GetDefaultClassKey() end
        SetDropdownToClass(selectedClassKey)
        UpdateClassMode()
        ApplyClassColor()
    end)

    currentClassRadio:SetScript("OnClick", function()
        useCurrentClass = true
        useClassColor   = false
        useVarColor     = false
        UpdateClassMode()
        ApplyCurrentClassColor()
    end)

    UIDropDownMenu_Initialize(varDropdown, function(_, level)
        local list = GetColorVarList()
        for _, v in ipairs(list) do
            local info = UIDropDownMenu_CreateInfo()
            info.text    = v.name
            info.value   = v.id
            info.func    = function()
                if not useVarColor then return end
                selectedVarID = v.id
                UIDropDownMenu_SetSelectedValue(varDropdown, v.id)
                UIDropDownMenu_SetText(varDropdown, v.name)
                varSwatch:SetVertexColor(v.r, v.g, v.b, 1)
                ApplyVarColor()
            end
            info.checked = (selectedVarID == v.id)
            UIDropDownMenu_AddButton(info, level)
        end
    end)

    varRadio:SetScript("OnClick", function()
        useVarColor     = true
        useClassColor   = false
        useCurrentClass = false
        if not selectedVarID then
            local list = GetColorVarList()
            if list[1] then selectedVarID = list[1].id end
        end
        SetDropdownToVar(selectedVarID)
        UpdateClassMode()
        ApplyVarColor()
    end)

    -- Define forward-declared functions now that noneRadio/UpdateClassMode exist.
    SelectNone = function()
        if not useClassColor and not useCurrentClass and not useVarColor then return end
        useClassColor   = false
        useCurrentClass = false
        useVarColor     = false
        UpdateClassMode()
    end

    local cvWidgets = { div2, cvHeading, noneRadio, noneText, classRadio, classText,
                        currentClassRadio, currentClassText, classDropdown, classSwatch,
                        varRadio, varText, varDropdown, varSwatch }
    local function SetCVVisible(show)
        for _, w in ipairs(cvWidgets) do
            if show then w:Show() else w:Hide() end
        end
        cpFrame:SetHeight(show and 440 or 290)
    end

    -- Accept / Cancel buttons
    local cancelBtn = CreateFrame("Button", nil, cpFrame, "UIPanelButtonTemplate")
    cancelBtn:SetSize(100, 24)
    cancelBtn:SetPoint("BOTTOMLEFT", 20, 14)
    cancelBtn:SetText("Cancel")
    cancelBtn:SetScript("OnClick", function()
        if cbCancelFunc then cbCancelFunc({ r = prevR, g = prevG, b = prevB, classRef = prevClassRef, varRef = prevVarRef, currentClassRef = prevCurrentClassRef }) end
        cpFrame:Hide()
    end)

    local acceptBtn = CreateFrame("Button", nil, cpFrame, "UIPanelButtonTemplate")
    acceptBtn:SetSize(100, 24)
    acceptBtn:SetPoint("BOTTOMRIGHT", -20, 14)
    acceptBtn:SetText("Accept")
    acceptBtn:SetScript("OnClick", function()
        FireSwatch()
        cpFrame:Hide()
    end)

    -- Default button: resets to hardcoded class color; shown only when defaults
    -- are provided AND the current color differs from them.
    local defaultBtn = CreateFrame("Button", nil, cpFrame, "UIPanelButtonTemplate")
    defaultBtn:SetSize(100, 24)
    defaultBtn:SetPoint("TOPRIGHT", cpFrame, "TOPRIGHT", -20, -220)
    defaultBtn:SetText("Default")
    defaultBtn:SetScript("OnClick", function()
        if not (defaultR and defaultG and defaultB) then return end
        curR, curG, curB = defaultR, defaultG, defaultB
        if SelectNone then SelectNone() end
        RefreshAll()
        FireSwatch()
    end)
    defaultBtn:Disable()

    local defaultBtnVisible = false

    UpdateDefaultBtn = function()
        if not defaultBtnVisible or not (defaultR and defaultG and defaultB) then
            defaultBtn:Disable()
            return
        end
        local thresh = 1 / 255
        if math.abs(curR - defaultR) > thresh or
           math.abs(curG - defaultG) > thresh or
           math.abs(curB - defaultB) > thresh then
            defaultBtn:Enable()
        else
            defaultBtn:Disable()
        end
    end

    cpFrame:SetScript("OnKeyDown", function(_, key)
        if key == "ESCAPE" then
            if cbCancelFunc then cbCancelFunc({ r = prevR, g = prevG, b = prevB, classRef = prevClassRef, varRef = prevVarRef, currentClassRef = prevCurrentClassRef }) end
            cpFrame:Hide()
        end
    end)

    -- Public API (fills the module-level OMCColorPicker table)
    OMCColorPicker.SetupAndShow = function(info)
        prevR = info.r or 1; prevG = info.g or 1; prevB = info.b or 1
        prevClassRef        = info.classRef        or nil
        prevVarRef          = info.varRef          or nil
        prevCurrentClassRef = info.currentClassRef or nil
        defaultR = info.defaultR or nil
        defaultG = info.defaultG or nil
        defaultB = info.defaultB or nil
        defaultBtnVisible = info.showDefaultBtn == true
        curR, curG, curB = prevR, prevG, prevB
        cbSwatchFunc = info.swatchFunc
        cbCancelFunc = info.cancelFunc

        if prevVarRef and GetColorVarByID(prevVarRef) then
            useVarColor      = true
            useClassColor    = false
            useCurrentClass  = false
            selectedVarID    = prevVarRef
            selectedClassKey = GetDefaultClassKey()
        elseif prevCurrentClassRef then
            useCurrentClass  = true
            useClassColor    = false
            useVarColor      = false
            selectedClassKey = GetDefaultClassKey()
        elseif prevClassRef then
            useClassColor    = true
            useCurrentClass  = false
            useVarColor      = false
            selectedClassKey = prevClassRef
        else
            useClassColor    = false
            useCurrentClass  = false
            useVarColor      = false
            selectedClassKey = GetDefaultClassKey()
        end
        SetDropdownToClass(selectedClassKey)
        SetDropdownToVar(selectedVarID)
        UpdateClassMode()

        prevSwatch:SetVertexColor(prevR, prevG, prevB, 1)
        RefreshAll()
        if defaultBtnVisible then defaultBtn:Show() else defaultBtn:Hide() end
        UpdateDefaultBtn()
        SetCVVisible(info.showColorVariables ~= false)
        cpFrame:Show()
        cpFrame:Raise()
    end

    OMCColorPicker.GetColorRGB = function()
        return curR, curG, curB
    end

    OMCColorPicker.GetClassRef = function()
        return useClassColor and selectedClassKey or nil
    end

    OMCColorPicker.GetVarRef = function()
        return useVarColor and selectedVarID or nil
    end

    OMCColorPicker.GetCurrentClassRef = function()
        return useCurrentClass and true or nil
    end
end

local function BuildPanel()
    State.built = true

    local title = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 16, -16)
    title:SetText("Old Man Cursor")

    UI.previewButton = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    UI.previewButton:SetSize(175, 24)
    UI.previewButton:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -166, -12)
    UI.previewButton:SetText("Edit & Preview")

    UI.existingProfilesDropdown = CreateFrame("Frame", nil, panel, "UIDropDownMenuTemplate")
    UI.existingProfilesDropdown:SetPoint("LEFT", UI.previewButton, "RIGHT", -10, -2)
    UIDropDownMenu_SetWidth(UI.existingProfilesDropdown, 120)

    UI.existingProfilesDropdown.RefreshList = function(self)
        local db = GetDB()
        local entries = GetSavedProfileEntries(nil)

        if not db.EXISTING_PROFILE_SELECT or db.EXISTING_PROFILE_SELECT == "" then
            db.EXISTING_PROFILE_SELECT = GetCharDB().CURRENT_PROFILE or "DEFAULT"
        end

        RefreshDropdownSelection(self, entries, db.EXISTING_PROFILE_SELECT, "")
    end

    UIDropDownMenu_Initialize(UI.existingProfilesDropdown, function(self, level)
        local db = GetDB()
        local entries = GetSavedProfileEntries(nil)

        for _, entry in ipairs(entries) do
            local info = UIDropDownMenu_CreateInfo()
            info.text = entry.text
            info.value = entry.value
            info.func = function()
                db.EXISTING_PROFILE_SELECT = entry.value
                UIDropDownMenu_SetSelectedValue(UI.existingProfilesDropdown, entry.value)
                UIDropDownMenu_SetText(UI.existingProfilesDropdown, entry.text)

                if IsPreviewActive() then
                    SaveActiveProfile()
                    SetPreview(true, entry.value)
                    RefreshReticle()
                    RefreshLineWidgets()
                    RebuildLayerList()
                end

                SetProfileStatus("PROFILES", "Profile selected for editing: " .. entry.text, false)
                UpdateEditingProfileTexts()
                UpdatePreviewButton()
                RefreshAllProfileWidgets()
            end
            info.checked = (db.EXISTING_PROFILE_SELECT == entry.value)
            UIDropDownMenu_AddButton(info, level)
        end
    end)

    UI.existingProfilesDropdown:RefreshList()

    UI.previewButton:SetScript("OnClick", function()
        State.previewActive = IsPreviewActive()

        if State.previewActive then
            SaveActiveProfile()
            SetPreview(false)
        else
            SetPreview(true, GetPreviewTargetProfileName())
        end

        State.previewActive = IsPreviewActive()
        if UI.UpdatePreviewButtonState then
            UI.UpdatePreviewButtonState()
        end
        UpdatePreviewButton()
        UpdateEditingProfileTexts()
        UpdateEditModeUI()
        RefreshAllProfileWidgets()
        RebuildLayerList()
    end)

    local previewTooltipOverlay = CreateFrame("Frame", nil, UI.previewButton)
    previewTooltipOverlay:SetAllPoints(UI.previewButton)
    previewTooltipOverlay:EnableMouse(false)
    previewTooltipOverlay:SetFrameLevel(UI.previewButton:GetFrameLevel() + 10)

    previewTooltipOverlay:SetScript("OnEnter", function()
    end)

    previewTooltipOverlay:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    UI.previewButton.tooltipOverlay = previewTooltipOverlay

    UpdatePreviewButton()
    if UI.UpdatePreviewButtonState then
        UI.UpdatePreviewButtonState()
    end

    local nav = CreateFrame("Frame", nil, panel, "BackdropTemplate")
    nav:SetPoint("TOPLEFT", 16, -48)
    nav:SetPoint("BOTTOMLEFT", 16, 16)
    nav:SetWidth(190)
    nav:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true,
        tileSize = 16,
        edgeSize = 14,
        insets = { left = 4, right = 4, top = 4, bottom = 4 },
    })
    nav:SetBackdropColor(0, 0, 0, 0.35)

    local content = CreateFrame("Frame", nil, panel, "BackdropTemplate")
    content:SetPoint("TOPLEFT", nav, "TOPRIGHT", 14, 0)
    content:SetPoint("BOTTOMRIGHT", -20, 16)
    content:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true,
        tileSize = 16,
        edgeSize = 14,
        insets = { left = 6, right = 10, top = 6, bottom = 6 },
    })
    content:SetBackdropColor(0, 0, 0, 0.25)

    local tabOrder = {
        { key = "GENERAL",       text = "General" },
        { key = "LINE",          text = "Line Settings" },
        { key = "LAYERS",        text = "Layers" },
        { key = "COLORS",        text = "Colors" },
        { key = "IMPORT_EXPORT", text = "Import & Export" },
        { key = "PROFILES",      text = "Profiles" },
    }

    local btnY = -8
    for i = 1, #tabOrder do
        local info = tabOrder[i]

        local button = CreateFrame("Button", nil, nav, "UIPanelButtonTemplate")
        button:SetSize(170, 22)
        button:SetPoint("TOPLEFT", 10, btnY)
        button:SetText(info.text)
        btnY = btnY - 26

        local frame = CreateFrame("Frame", nil, content)
        frame:SetAllPoints(content)
        frame:Hide()

        button:SetScript("OnClick", function()
            SelectTab(info.key)
        end)

        Lists.tabs[info.key] = {
            button = button,
            frame = frame,
        }
    end

    local versionLabel = nav:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
    versionLabel:SetPoint("BOTTOM", nav, "BOTTOM", 0, 10)
    versionLabel:SetText("v1.2.8")

    do
        local tf = Lists.tabs.GENERAL.frame

        local heading = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        heading:SetPoint("TOPLEFT", 16, -16)
        heading:SetText("General")

        local helper = tf:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        helper:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -10)
        helper:SetJustifyH("LEFT")
        helper:SetJustifyV("TOP")
        helper:SetWidth(420)
        helper:SetText("Choose which profiles are used by each visibility mode. Use Profiles to manage profiles, and Edit & Preview to edit the selected profile.")

        UI.generalVisibilityWidgets = CreateGeneralVisibilityControls(tf, 12, -72)

        local resetDefaultsButton = CreateFrame("Button", nil, tf, "UIPanelButtonTemplate")
        resetDefaultsButton:SetSize(130, 24)
        resetDefaultsButton:SetPoint("BOTTOMLEFT", tf, "BOTTOMLEFT", 16, 16)
        resetDefaultsButton:SetText("Reset to Defaults")
        resetDefaultsButton:SetScript("OnClick", function()
            local profileName = GetEditingProfileName()
            OMCConfirm(
                format("Reset profile \"%s\" to default settings?", profileName),
                "Reset",
                function()
                    -- Stop preview before resetting so the cursor reflects the
                    -- default settings immediately and the edit-lock UI is restored.
                    if IsPreviewActive() then
                        SaveActiveProfile()
                        SetPreview(false)
                    end
                    if _G.OldManCursor_ResetProfile then
                        local ok, message = _G.OldManCursor_ResetProfile(profileName)
                        if ok then
                            SetProfileStatus("PROFILES", message or "Current profile reset.", false)
                        else
                            SetProfileStatus("PROFILES", message or "Could not reset profile.", true)
                        end
                    end
                    State.previewActive = IsPreviewActive()
                    if UI.UpdatePreviewButtonState then UI.UpdatePreviewButtonState() end
                    UpdatePreviewButton()
                    UpdateEditingProfileTexts()
                    UpdateEditModeUI()
                    RefreshAllProfileWidgets()
                    RebuildLayerList()
                end
            )
        end)

        local resetInfoText = tf:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        resetInfoText:SetPoint("LEFT", resetDefaultsButton, "RIGHT", 10, 0)
        resetInfoText:SetJustifyH("LEFT")
        resetInfoText:SetText("Resets the profile currently being edited.")
    end

    do
        local tf = Lists.tabs.LINE.frame

        UI.lineHeadingText = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        UI.lineHeadingText:SetPoint("TOPLEFT", 16, -16)
        UI.lineHeadingText:SetText("Line Settings")

        local helper = tf:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        helper:SetPoint("TOPLEFT", UI.lineHeadingText, "BOTTOMLEFT", 0, -10)
        helper:SetJustifyH("LEFT")
        helper:SetText("These settings affect the cursor guide lines behind the layered textures.")

        local linesEnabledCheck = CreateFrame("CheckButton", nil, tf, "UICheckButtonTemplate")
        linesEnabledCheck:SetPoint("TOPLEFT", helper, "BOTTOMLEFT", 0, -8)
        linesEnabledCheck:SetChecked(GetProfileDB().LINES_ENABLED ~= false)
        linesEnabledCheck:SetScript("OnClick", function(self)
            GetProfileDB().LINES_ENABLED = self:GetChecked() and true or false
            OldManCursor_ApplySettings()
        end)

        local linesEnabledText = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        linesEnabledText:SetPoint("LEFT", linesEnabledCheck, "RIGHT", 4, 0)
        linesEnabledText:SetText("Enable Lines")

        UI.linesEnabledCheck = linesEnabledCheck

        UI.lineThicknessWidgets = CreateLineThicknessControl(tf, 16, -106)
        UI.lineLengthModeWidgets = CreateLineLengthModeControls(tf, 16, -166)
        UI.lineGapWidgets = CreateLineGapControl(tf, 16, -334)
        UI.lineOpacityWidgets = CreateLineOpacityControl(tf, 16, -384)
        UI.lineBlendDropdown = CreateValueDropdown(tf, "LINE_BLEND", "Blend Mode", availableBlendModes, 16, -434, GetProfileDB)
        UI.lineColorWidgets = CreateLineColorControl(tf, 250, -434)

        UI.lineLockOverlay = CreateEditLockOverlay(tf, -56)
    end

    do
        local tf = Lists.tabs.LAYERS.frame

        UI.layersHeadingText = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        UI.layersHeadingText:SetPoint("TOPLEFT", 16, -16)
        UI.layersHeadingText:SetText("Layers (0)")

        local border = CreateFrame("Frame", nil, tf, "InsetFrameTemplate")
        border:SetPoint("TOPLEFT", UI.layersHeadingText, "BOTTOMLEFT", 4, -8)
        border:SetSize(390, 450)

        UI.layerScrollFrame = CreateFrame("ScrollFrame", ADDON_NAME .. "LayerScrollFrame", border, "UIPanelScrollFrameTemplate")
        UI.layerScrollFrame:SetPoint("TOPLEFT", 4, -4)
        UI.layerScrollFrame:SetPoint("BOTTOMRIGHT", -28, 4)

        UI.layerContent = CreateFrame("Frame", nil, UI.layerScrollFrame)
        UI.layerContent:SetSize(290, 290)
        UI.layerScrollFrame:SetScrollChild(UI.layerContent)

        local addLayerButton = CreateFrame("Button", nil, tf, "UIPanelButtonTemplate")
        addLayerButton:SetSize(90, 24)
        addLayerButton:SetPoint("TOPLEFT", border, "BOTTOMLEFT", 0, -10)
        addLayerButton:SetText("Add Layer")
        addLayerButton:SetScript("OnClick", function()
            AddLayer()
        end)


        UI.layersLockOverlay = CreateEditLockOverlay(tf)
    end

    do
        local tf = Lists.tabs.COLORS.frame

        local heading = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        heading:SetPoint("TOPLEFT", 16, -16)
        heading:SetText("Colors")

        local helper = tf:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        helper:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -10)
        helper:SetJustifyH("LEFT")
        helper:SetWidth(420)
        helper:SetText("Changes to Class Colors and Class Variables apply to all elements using them.")

        -- Class Colors block --------------------------------------------------
        local classColorsBox = CreateFrame("Frame", nil, tf, "InsetFrameTemplate")
        classColorsBox:SetPoint("TOPLEFT", helper, "BOTTOMLEFT", 0, -12)
        classColorsBox:SetSize(400, 168)

        local classColorsHeading = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        classColorsHeading:SetPoint("TOPLEFT", classColorsBox, "TOPLEFT", 8, -10)
        classColorsHeading:SetText("Class Colors (CC)")

        -- Col 1 start=10, col 2 start=128 (col1 118px wide), col 3 start=191 (col2 63px), col 4 start=254 (col3 63px)
        local colOffsets = { 16, 128, 224, 320 }
        local rowHeight  = 28

        LoadClassColorsFromDB()

        for rowIdx, ccRow in ipairs(CLASS_COLORS_ROWS) do
            for colIdx, entry in ipairs(ccRow) do
                local xPos = colOffsets[colIdx]
                local yPos = -36 - (rowIdx - 1) * rowHeight

                local btn = CreateFrame("Button", nil, classColorsBox)
                btn:SetSize(16, 16)
                btn:SetPoint("TOPLEFT", classColorsBox, "TOPLEFT", xPos, yPos)

                local preview = btn:CreateTexture(nil, "ARTWORK")
                preview:SetAllPoints(btn)
                preview:SetTexture("Interface\\Buttons\\WHITE8X8")
                preview:SetVertexColor(entry.r, entry.g, entry.b, 1)

                local function UpdatePreview()
                    preview:SetVertexColor(entry.r, entry.g, entry.b, 1)
                end

                btn:SetScript("OnClick", function()
                    local hc = CLASS_COLORS_HARDCODED[entry.key]
                    OMCColorPicker.SetupAndShow({
                        r = entry.r, g = entry.g, b = entry.b,
                        defaultR = hc and hc.r, defaultG = hc and hc.g, defaultB = hc and hc.b,
                        showDefaultBtn = true,
                        showColorVariables = false,
                        swatchFunc = function()
                            local r, g, b = OMCColorPicker.GetColorRGB()
                            SaveClassColorToDB(entry.key, r, g, b)
                            UpdatePreview()
                            RefreshReticle()
                        end,
                        cancelFunc = function(previousValues)
                            if previousValues then
                                SaveClassColorToDB(entry.key, previousValues.r, previousValues.g, previousValues.b)
                                UpdatePreview()
                                RefreshReticle()
                            end
                        end,
                    })
                end)

                local label = classColorsBox:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                label:SetPoint("LEFT", btn, "RIGHT", 4, 0)
                label:SetText(entry.label)
            end
        end

        -- Color Variables block ------------------------------------------------
        local colorVarsBox = CreateFrame("Frame", nil, tf, "InsetFrameTemplate")
        colorVarsBox:SetPoint("TOPLEFT", classColorsBox, "BOTTOMLEFT", 0, -14)
        colorVarsBox:SetSize(400, 220)

        local colorVarsHeading = colorVarsBox:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        colorVarsHeading:SetPoint("TOPLEFT", colorVarsBox, "TOPLEFT", 8, -10)
        colorVarsHeading:SetText("Color Variables (CV)")

        -- Local state for this block
        local createColorR, createColorG, createColorB = 1, 1, 1
        local changeColorR, changeColorG, changeColorB = 0, 0, 0
        local selectedChangeVarID = nil
        local selectedDeleteVarID = nil
        local changeVarDropdown, deleteVarDropdown
        local changeNameBox, changeSwatchTex, confirmBtn

        local function RefreshColorVarDropdowns()
            selectedChangeVarID = nil
            UIDropDownMenu_SetText(changeVarDropdown, "")
            changeNameBox:SetText("")
            changeColorR, changeColorG, changeColorB = 0, 0, 0
            changeSwatchTex:SetVertexColor(0, 0, 0, 1)
            confirmBtn:Disable()
            selectedDeleteVarID = nil
            UIDropDownMenu_SetText(deleteVarDropdown, "")
        end
        UI.RefreshColorVarDropdowns = RefreshColorVarDropdowns

        -- ── Create section ──────────────────────────────────────────────────
        local createLabel = colorVarsBox:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        createLabel:SetPoint("TOPLEFT", colorVarsBox, "TOPLEFT", 17, -30)
        createLabel:SetText("Create New Color Variable")

        local createNameBox = CreateFrame("EditBox", nil, colorVarsBox, "InputBoxTemplate")
        createNameBox:SetSize(190, 20)
        createNameBox:SetPoint("TOPLEFT", colorVarsBox, "TOPLEFT", 29, -50)
        createNameBox:SetAutoFocus(false)
        createNameBox:SetMaxLetters(40)
        createNameBox:SetPropagateKeyboardInput(false)

        local createColorBtn = CreateFrame("Button", nil, colorVarsBox)
        createColorBtn:SetSize(20, 20)
        createColorBtn:SetPoint("TOPLEFT", colorVarsBox, "TOPLEFT", 227, -50)
        local createSwatchTex = createColorBtn:CreateTexture(nil, "ARTWORK")
        createSwatchTex:SetAllPoints(createColorBtn)
        createSwatchTex:SetTexture("Interface\\Buttons\\WHITE8X8")
        createSwatchTex:SetVertexColor(1, 1, 1, 1)
        createColorBtn:SetScript("OnClick", function()
            OMCColorPicker.SetupAndShow({
                r = createColorR, g = createColorG, b = createColorB,
                showColorVariables = false,
                swatchFunc = function()
                    createColorR, createColorG, createColorB = OMCColorPicker.GetColorRGB()
                    createSwatchTex:SetVertexColor(createColorR, createColorG, createColorB, 1)
                end,
                cancelFunc = function(prev)
                    if prev then
                        createColorR, createColorG, createColorB = prev.r, prev.g, prev.b
                        createSwatchTex:SetVertexColor(prev.r, prev.g, prev.b, 1)
                    end
                end,
            })
        end)

        local createBtn = CreateFrame("Button", nil, colorVarsBox, "UIPanelButtonTemplate")
        createBtn:SetSize(80, 24)
        createBtn:SetPoint("TOPLEFT", colorVarsBox, "TOPLEFT", 255, -48)
        createBtn:SetText("Create")
        createBtn:SetScript("OnClick", function()
            local name = Trim(createNameBox:GetText() or "")
            if name == "" then return end
            CreateColorVar(name, createColorR, createColorG, createColorB)
            createNameBox:SetText("")
            createColorR, createColorG, createColorB = 1, 1, 1
            createSwatchTex:SetVertexColor(1, 1, 1, 1)
            RefreshColorVarDropdowns()
        end)

        -- ── Change section ───────────────────────────────────────────────────
        local changeLabel = colorVarsBox:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        changeLabel:SetPoint("TOPLEFT", colorVarsBox, "TOPLEFT", 17, -82)
        changeLabel:SetText("Change:")

        changeVarDropdown = CreateFrame("Frame", nil, colorVarsBox, "UIDropDownMenuTemplate")
        changeVarDropdown:SetPoint("TOPLEFT", colorVarsBox, "TOPLEFT", 5, -100)
        UIDropDownMenu_SetWidth(changeVarDropdown, 148)

        UIDropDownMenu_Initialize(changeVarDropdown, function(_, level)
            local list = GetColorVarList()
            for _, v in ipairs(list) do
                local info = UIDropDownMenu_CreateInfo()
                info.text  = v.name
                info.value = v.id
                info.func  = function()
                    selectedChangeVarID = v.id
                    UIDropDownMenu_SetSelectedValue(changeVarDropdown, v.id)
                    UIDropDownMenu_SetText(changeVarDropdown, v.name)
                    changeNameBox:SetText(v.name)
                    changeColorR, changeColorG, changeColorB = v.r, v.g, v.b
                    changeSwatchTex:SetVertexColor(v.r, v.g, v.b, 1)
                    confirmBtn:Enable()
                end
                info.checked = (selectedChangeVarID == v.id)
                UIDropDownMenu_AddButton(info, level)
            end
        end)

        changeNameBox = CreateFrame("EditBox", nil, colorVarsBox, "InputBoxTemplate")
        changeNameBox:SetSize(190, 20)
        changeNameBox:SetPoint("TOPLEFT", colorVarsBox, "TOPLEFT", 29, -128)
        changeNameBox:SetAutoFocus(false)
        changeNameBox:SetMaxLetters(40)
        changeNameBox:SetPropagateKeyboardInput(false)

        local changeColorBtn = CreateFrame("Button", nil, colorVarsBox)
        changeColorBtn:SetSize(20, 20)
        changeColorBtn:SetPoint("TOPLEFT", colorVarsBox, "TOPLEFT", 227, -128)
        changeSwatchTex = changeColorBtn:CreateTexture(nil, "ARTWORK")
        changeSwatchTex:SetAllPoints(changeColorBtn)
        changeSwatchTex:SetTexture("Interface\\Buttons\\WHITE8X8")
        changeSwatchTex:SetVertexColor(0, 0, 0, 1)
        changeColorBtn:SetScript("OnClick", function()
            OMCColorPicker.SetupAndShow({
                r = changeColorR, g = changeColorG, b = changeColorB,
                showColorVariables = false,
                swatchFunc = function()
                    changeColorR, changeColorG, changeColorB = OMCColorPicker.GetColorRGB()
                    changeSwatchTex:SetVertexColor(changeColorR, changeColorG, changeColorB, 1)
                end,
                cancelFunc = function(prev)
                    if prev then
                        changeColorR, changeColorG, changeColorB = prev.r, prev.g, prev.b
                        changeSwatchTex:SetVertexColor(prev.r, prev.g, prev.b, 1)
                    end
                end,
            })
        end)

        confirmBtn = CreateFrame("Button", nil, colorVarsBox, "UIPanelButtonTemplate")
        confirmBtn:SetSize(80, 24)
        confirmBtn:SetPoint("TOPLEFT", colorVarsBox, "TOPLEFT", 255, -126)
        confirmBtn:SetText("Confirm")
        confirmBtn:Disable()
        confirmBtn:SetScript("OnClick", function()
            if not selectedChangeVarID then return end
            local name = Trim(changeNameBox:GetText() or "")
            if name == "" then return end
            UpdateColorVar(selectedChangeVarID, name, changeColorR, changeColorG, changeColorB)
            RefreshColorVarDropdowns()
            RefreshReticle()
        end)

        -- ── Delete section ───────────────────────────────────────────────────
        local deleteLabel = colorVarsBox:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        deleteLabel:SetPoint("TOPLEFT", colorVarsBox, "TOPLEFT", 17, -160)
        deleteLabel:SetText("Delete:")

        deleteVarDropdown = CreateFrame("Frame", nil, colorVarsBox, "UIDropDownMenuTemplate")
        deleteVarDropdown:SetPoint("TOPLEFT", colorVarsBox, "TOPLEFT", 5, -178)
        UIDropDownMenu_SetWidth(deleteVarDropdown, 148)

        UIDropDownMenu_Initialize(deleteVarDropdown, function(_, level)
            local list = GetColorVarList()
            for _, v in ipairs(list) do
                local info = UIDropDownMenu_CreateInfo()
                info.text  = v.name
                info.value = v.id
                info.func  = function()
                    UIDropDownMenu_SetSelectedValue(deleteVarDropdown, v.id)
                    UIDropDownMenu_SetText(deleteVarDropdown, v.name)
                    OMCConfirm(
                        "Delete color variable \"" .. v.name .. "\"?",
                        "Delete",
                        function()
                            DeleteColorVar(v.id)
                            RefreshColorVarDropdowns()
                        end
                    )
                end
                info.checked = false
                UIDropDownMenu_AddButton(info, level)
            end
        end)
    end

    do
        local tf = Lists.tabs.IMPORT_EXPORT.frame

        local heading = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        heading:SetPoint("TOPLEFT", 16, -16)
        heading:SetText("Import & Export")

        local helper = tf:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        helper:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -10)
        helper:SetJustifyH("LEFT")
        helper:SetText("Export your current cursor settings to a single string, or import them later.")

        local exportLabel = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        exportLabel:SetPoint("TOPLEFT", helper, "BOTTOMLEFT", 0, -18)
        exportLabel:SetText("Export")

        local exportScroll
        exportScroll, UI.exportEditBox = CreateMultiLineBox(tf, ADDON_NAME .. "ExportEditBox", imexportBoxWidth, 90)
        exportScroll:SetPoint("TOPLEFT", exportLabel, "BOTTOMLEFT", 0, -8)

        local exportButton = CreateFrame("Button", nil, tf, "UIPanelButtonTemplate")
        exportButton:SetSize(140, 24)
        exportButton:SetPoint("TOPLEFT", exportScroll, "BOTTOMLEFT", 0, -10)
        exportButton:SetText("Export Current")
        exportButton:SetScript("OnClick", function()
            local exportString = GenerateProfileString()
            UI.exportEditBox:SetText(exportString)
            UI.exportEditBox:SetCursorPosition(0)
            if exportScroll and exportScroll.scrollFrame then
                exportScroll.scrollFrame:SetVerticalScroll(0)
            end
            UI.exportEditBox:HighlightText()
            SetProfileStatus("EXPORT", "Current settings exported.", false)
            SetProfileStatus("IMPORT", "", false)
        end)

        local importLabel = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        importLabel:SetPoint("TOPLEFT", exportButton, "BOTTOMLEFT", 0, -22)
        importLabel:SetText("Import")

        local importScroll
        importScroll, UI.importEditBox = CreateMultiLineBox(tf, ADDON_NAME .. "ImportEditBox", imexportBoxWidth, 90)
        importScroll:SetPoint("TOPLEFT", importLabel, "BOTTOMLEFT", 0, -8)

        local importButton = CreateFrame("Button", nil, tf, "UIPanelButtonTemplate")
        importButton:SetSize(140, 24)
        importButton:SetPoint("TOPLEFT", importScroll, "BOTTOMLEFT", 0, -10)
        importButton:SetText("Import String")
        importButton:SetScript("OnClick", function()
            local ok, message = ApplyProfileString(UI.importEditBox:GetText() or "")

            if importScroll and importScroll.scrollFrame then
                importScroll.scrollFrame:SetVerticalScroll(0)
            end
            UI.importEditBox:SetCursorPosition(0)

            if ok then
                RefreshReticle()
                SaveActiveProfile()
                RefreshLineWidgets()
                RebuildLayerList()
                SetProfileStatus("IMPORT", message, false)
                SetProfileStatus("EXPORT", "", false)
            else
                SetProfileStatus("IMPORT", message, true)
                SetProfileStatus("EXPORT", "", false)
            end
        end)

        UI.exportStatusText = tf:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        UI.exportStatusText:SetPoint("LEFT", exportButton, "RIGHT", 14, 0)
        UI.exportStatusText:SetJustifyH("LEFT")
        UI.exportStatusText:SetText("")

        UI.importStatusText = tf:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        UI.importStatusText:SetPoint("LEFT", importButton, "RIGHT", 14, 0)
        UI.importStatusText:SetJustifyH("LEFT")
        UI.importStatusText:SetText("")
    end

    do
        local tf = Lists.tabs.PROFILES.frame
        local db = GetDB()

        local heading = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        heading:SetPoint("TOPLEFT", 16, -16)
        heading:SetText("Profiles")

        local helper = tf:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        helper:SetPoint("TOPLEFT", heading, "BOTTOMLEFT", 0, -10)
        helper:SetJustifyH("LEFT")
        helper:SetWidth(420)
        helper:SetText("Manage profiles here. The profile on the top right is used for Edit & Preview.")

        local newLegend = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        newLegend:SetPoint("TOPLEFT", helper, "BOTTOMLEFT", 0, -18)
        newLegend:SetText("Create New Profile")

        UI.newProfileEditBox = CreateFrame("EditBox", ADDON_NAME .. "NewProfileEditBox", tf, "InputBoxTemplate")
        UI.newProfileEditBox:SetSize(180, 20)
        UI.newProfileEditBox:SetPoint("TOPLEFT", newLegend, "BOTTOMLEFT", 10, -8)
        UI.newProfileEditBox:SetAutoFocus(false)
        UI.newProfileEditBox:SetMaxLetters(40)
        UI.newProfileEditBox:SetText(db.NEW_PROFILE_NAME or "")
        UI.newProfileEditBox:SetScript("OnTextChanged", function(self)
            db.NEW_PROFILE_NAME = self:GetText() or ""
        end)

        local createProfileButton = CreateFrame("Button", nil, tf, "UIPanelButtonTemplate")
        createProfileButton:SetSize(110, 22)
        createProfileButton:SetPoint("LEFT", UI.newProfileEditBox, "RIGHT", 10, 0)
        createProfileButton:SetText("Create")
        createProfileButton:SetScript("OnClick", function()
            local name = Trim(UI.newProfileEditBox:GetText() or "")
            if _G.OldManCursor_CreateProfile then
                local ok, message = _G.OldManCursor_CreateProfile(name)
                if ok then
                    db.NEW_PROFILE_NAME = ""
                    UI.newProfileEditBox:SetText("")
                    if not db.EXISTING_PROFILE_SELECT or db.EXISTING_PROFILE_SELECT == "" then
                        db.EXISTING_PROFILE_SELECT = name
                    end
                    SetProfileStatus("PROFILES", message or "Profile created.", false)
                    RefreshAllProfileWidgets()
                else
                    SetProfileStatus("PROFILES", message or "Could not create profile.", true)
                end
            end
        end)

        local copyHelper = tf:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        copyHelper:SetPoint("TOPLEFT", UI.newProfileEditBox, "BOTTOMLEFT", -10, -24)
        copyHelper:SetJustifyH("LEFT")
        copyHelper:SetText("Copy settings from an existing profile into the selected profile to edit.")

        local copyLabel = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        copyLabel:SetPoint("TOPLEFT", copyHelper, "BOTTOMLEFT", 0, -14)
        copyLabel:SetText("Copy from:")

        local copyDropdown = CreateActionProfileDropdown(
            tf,
            180,
            copyLabel,
            "LEFT",
            "RIGHT",
            -10,
            -2,
            false,
            function(value) return value == "DEFAULT" and #GetSavedProfileEntries(nil) <= 1 end,
            function(profileValue, profileText)
                local targetProfile = GetDB().EXISTING_PROFILE_SELECT or "DEFAULT"

                if profileValue == targetProfile then
                    SetProfileStatus("PROFILES", "Source and target are the same profile.", true)
                    return
                end

                local capturedSource = profileValue
                local capturedTarget = targetProfile
                OMCConfirm(
                    format("Selected profile to edit: \"%s\"\nOverwrite it with \"%s\"?", targetProfile, profileText),
                    "Yes",
                    function()
                        if _G.OldManCursor_CopyProfileIntoProfile then
                            local ok, message = _G.OldManCursor_CopyProfileIntoProfile(capturedSource, capturedTarget)
                            if ok then
                                SetProfileStatus("PROFILES", message or "Profile overwritten.", false)
                                RefreshAllProfileWidgets()
                                RefreshReticle()
                                RebuildLayerList()
                            else
                                SetProfileStatus("PROFILES", message or "Could not overwrite profile.", true)
                            end
                        end
                    end
                )
            end
        )

        local deleteHelper = tf:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        deleteHelper:SetPoint("TOPLEFT", copyLabel, "BOTTOMLEFT", 0, -20)
        deleteHelper:SetJustifyH("LEFT")
        deleteHelper:SetText("Delete an existing profile.")

        local deleteLabel = tf:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        deleteLabel:SetPoint("TOPLEFT", deleteHelper, "BOTTOMLEFT", 0, -14)
        deleteLabel:SetText("Delete:")

        local deleteDropdown = CreateActionProfileDropdown(
            tf,
            180,
            deleteLabel,
            "LEFT",
            "RIGHT",
            -10,
            -2,
            false,
            "DEFAULT",
            function(profileValue, profileText)
                local capturedProfile = profileValue
                if IsPreviewActive() then
                    SaveActiveProfile()
                    SetPreview(false)
                    if UI.UpdatePreviewButtonState then UI.UpdatePreviewButtonState() end
                    UpdatePreviewButton()
                    UpdateEditingProfileTexts()
                    UpdateEditModeUI()
                    RefreshAllProfileWidgets()
                    RebuildLayerList()
                end
                OMCConfirm(
                    format("Delete profile \"%s\"?", profileText),
                    "Delete",
                    function()
                        if _G.OldManCursor_DeleteProfile then
                            local ok, message = _G.OldManCursor_DeleteProfile(capturedProfile)
                            if ok then
                                local db = GetDB()
                                if db.EXISTING_PROFILE_SELECT == capturedProfile then
                                    db.EXISTING_PROFILE_SELECT = GetCharDB().CURRENT_PROFILE or "DEFAULT"
                                end
                                SetProfileStatus("PROFILES", message or "Profile deleted.", false)
                                RefreshAllProfileWidgets()
                                RefreshReticle()
                                RebuildLayerList()
                            else
                                SetProfileStatus("PROFILES", message or "Could not delete profile.", true)
                            end
                        end
                    end
                )
            end
        )

        UI.profilesStatusText = tf:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
        UI.profilesStatusText:SetPoint("TOPLEFT", deleteLabel, "BOTTOMLEFT", 0, -40)
        UI.profilesStatusText:SetJustifyH("LEFT")
        UI.profilesStatusText:SetText("")

        UI.copyDropdown = copyDropdown
        UI.copyLabel = copyLabel
        UI.deleteDropdown = deleteDropdown
        UI.deleteLabel = deleteLabel

        copyDropdown:RefreshList()
        deleteDropdown:RefreshList()
    end

    SelectTab(State.currentTabKey or "GENERAL")
    RebuildLayerList()
    RefreshAllProfileWidgets()
    UpdateEditingProfileTexts()
    UpdatePreviewButton()
    UpdateEditModeUI()
end

_G.OldManCursor_ProfileChanged = function()
    if not State.built then
        return
    end

    State.previewActive = IsPreviewActive()
    RefreshAllProfileWidgets()

    if UI.generalVisibilityWidgets and UI.generalVisibilityWidgets.UpdateModeUI then
        UI.generalVisibilityWidgets.UpdateModeUI()
    end

    RebuildLayerList()
    UpdateEditingProfileTexts()
    UpdatePreviewButton()
    UpdateEditModeUI()
end

do
    local initFrame = CreateFrame("Frame")
    initFrame:RegisterEvent("ADDON_LOADED")
    initFrame:SetScript("OnEvent", function(self, event, addonName)
        if addonName == ADDON_NAME then
            BuildPanel()
            self:UnregisterEvent("ADDON_LOADED")
        end
    end)
end

panel:SetScript("OnShow", function()
    State.previewActive = IsPreviewActive()
    UpdatePreviewButton()
    if UI.UpdatePreviewButtonState then
        UI.UpdatePreviewButtonState()
    end
    SelectTab(State.currentTabKey or "GENERAL")
    RebuildLayerList()
    RefreshAllProfileWidgets()
    UpdateEditingProfileTexts()
    UpdatePreviewButton()
    UpdateEditModeUI()
end)

panel:SetScript("OnHide", function()
    SaveActiveProfile()

    State.previewActive = false
    SetPreview(false)
    UpdatePreviewButton()
    UpdateEditModeUI()
    GameTooltip:Hide()
    ResetProfilesTabSelections()
end)

local category = Settings.RegisterCanvasLayoutCategory(panel, ADDON_NAME)
Settings.RegisterAddOnCategory(category)

SLASH_OLDMANCURSOR1 = "/omc"
SlashCmdList["OLDMANCURSOR"] = function()
    Settings.OpenToCategory(category:GetID())
end