-- On-screen profile picker for Dynamic Profiles.
-- Movable/resizable via Blizzard Edit Mode (LibEditMode).
-- Supports Free / Minimap / Chat / hover-picked custom frame anchors.

local addonName, DP = ...
DP = DP or _G.DynamicProfiles or {}

local LibEditMode = LibStub and LibStub("LibEditMode", true)

local pickerButton
local pickerMenu
local menuButtons = {}
local defaultPosition = {
    point = "CENTER",
    x = 0,
    y = -200,
    width = 110,
    height = 28,
    anchorTarget = "NONE",
    anchorSide = "BOTTOM",
    matchWidth = false,
    offsetX = 0,
    offsetY = -8,
    customFrameName = nil,
}
local editingLocked = false
local applyingLayout = false
local customRetryTicker = nil
local customRetryAttempts = 0
local MAX_CUSTOM_RETRIES = 20 -- ~10s at 0.5s

-- Hover-pick state
local pickCapture
local pickHighlight
local pickInstruction
local pickActive = false
local currentCandidate = nil -- { frame = Frame, name = string }

local SIDE_POINTS = {
    LEFT = { self = "RIGHT", other = "LEFT", dx = -8, dy = 0 },
    RIGHT = { self = "LEFT", other = "RIGHT", dx = 8, dy = 0 },
    TOP = { self = "BOTTOM", other = "TOP", dx = 0, dy = 8 },
    BOTTOM = { self = "TOP", other = "BOTTOM", dx = 0, dy = -8 },
}

local EXCLUDED_NAMES = {
    UIParent = true,
    WorldFrame = true,
    DynamicProfilesPickerButton = true,
    DynamicProfilesPickerMenu = true,
    DynamicProfilesPickCapture = true,
    DynamicProfilesPickHighlight = true,
    DynamicProfilesPickInstruction = true,
}

local function Clamp(v, minV, maxV)
    if v < minV then return minV end
    if v > maxV then return maxV end
    return v
end

local function GetPickerDB()
    local db = DP.GetDB and DP.GetDB()
    if not db then
        return {
            enabled = true,
            point = defaultPosition.point,
            x = defaultPosition.x,
            y = defaultPosition.y,
            width = defaultPosition.width,
            height = defaultPosition.height,
            anchorTarget = defaultPosition.anchorTarget,
            anchorSide = defaultPosition.anchorSide,
            matchWidth = defaultPosition.matchWidth,
            offsetX = defaultPosition.offsetX,
            offsetY = defaultPosition.offsetY,
            customFrameName = nil,
        }
    end
    db.picker = db.picker or {
        enabled = true,
        point = defaultPosition.point,
        x = defaultPosition.x,
        y = defaultPosition.y,
        width = defaultPosition.width,
        height = defaultPosition.height,
        anchorTarget = defaultPosition.anchorTarget,
        anchorSide = defaultPosition.anchorSide,
        matchWidth = defaultPosition.matchWidth,
        offsetX = defaultPosition.offsetX,
        offsetY = defaultPosition.offsetY,
        customFrameName = nil,
    }
    local pos = db.picker
    pos.width = pos.width or defaultPosition.width
    pos.height = pos.height or defaultPosition.height
    pos.anchorTarget = pos.anchorTarget or defaultPosition.anchorTarget
    pos.anchorSide = pos.anchorSide or defaultPosition.anchorSide
    if pos.matchWidth == nil then pos.matchWidth = defaultPosition.matchWidth end
    pos.offsetX = pos.offsetX or defaultPosition.offsetX
    pos.offsetY = pos.offsetY or defaultPosition.offsetY
    return pos
end

local function StopCustomRetry()
    if customRetryTicker then
        customRetryTicker:Cancel()
        customRetryTicker = nil
    end
    customRetryAttempts = 0
end

local function ScheduleCustomRetry()
    if customRetryTicker then return end
    customRetryAttempts = 0
    customRetryTicker = C_Timer.NewTicker(0.5, function()
        customRetryAttempts = customRetryAttempts + 1
        local pos = GetPickerDB()
        if pos.anchorTarget ~= "CUSTOM" or not pos.customFrameName then
            StopCustomRetry()
            return
        end
        local frame = _G[pos.customFrameName]
        if frame and frame.GetObjectType then
            StopCustomRetry()
            if DP.UpdatePickerButton then DP.UpdatePickerButton() end
            return
        end
        if customRetryAttempts >= MAX_CUSTOM_RETRIES then
            StopCustomRetry()
            if DP.Debug then
                DP.Debug("Custom anchor '" .. tostring(pos.customFrameName) .. "' not found after retries; keeping free position.")
            end
        end
    end)
end

local function ResolveAnchorFrame(target, customName)
    if target == "MINIMAP" then
        return _G.MinimapCluster or _G.Minimap
    elseif target == "CHAT" then
        local chat = SELECTED_CHAT_FRAME or DEFAULT_CHAT_FRAME or _G.ChatFrame1
        if chat and chat.GetName then
            local bg = _G[chat:GetName() .. "Background"]
            if bg then return bg end
        end
        return chat
    elseif target == "CUSTOM" then
        local name = customName or GetPickerDB().customFrameName
        if type(name) == "string" and name ~= "" then
            local frame = _G[name]
            if frame and frame.GetObjectType then
                return frame
            end
        end
    end
    return nil
end

local function ApplyPickerSize(anchor)
    if not pickerButton then return end
    local pos = GetPickerDB()
    local width = Clamp(tonumber(pos.width) or defaultPosition.width, 70, 260)
    local height = Clamp(tonumber(pos.height) or defaultPosition.height, 20, 48)

    if pos.matchWidth and anchor and anchor.GetWidth then
        local aw = anchor:GetWidth()
        if aw and aw > 0 then
            width = Clamp(aw, 70, 400)
        end
    end

    pos.width = width
    pos.height = height
    pickerButton:SetSize(width, height)

    if pickerButton.label then
        if height <= 24 then
            pickerButton.label:SetFontObject(GameFontHighlightSmall)
        elseif height <= 32 then
            pickerButton.label:SetFontObject(GameFontNormal)
        else
            pickerButton.label:SetFontObject(GameFontNormalLarge)
        end
    end
end

local function ApplySavedPosition()
    if not pickerButton or applyingLayout then return end
    applyingLayout = true

    local pos = GetPickerDB()
    local anchor = ResolveAnchorFrame(pos.anchorTarget, pos.customFrameName)
    ApplyPickerSize(anchor)

    pickerButton:ClearAllPoints()

    if pos.anchorTarget ~= "NONE" and pos.anchorTarget ~= nil then
        if anchor then
            local side = SIDE_POINTS[pos.anchorSide or "BOTTOM"] or SIDE_POINTS.BOTTOM
            local ox = tonumber(pos.offsetX)
            local oy = tonumber(pos.offsetY)
            if ox == nil then ox = side.dx; pos.offsetX = side.dx end
            if oy == nil then oy = side.dy; pos.offsetY = side.dy end
            pickerButton:SetPoint(side.self, anchor, side.other, ox, oy)
            StopCustomRetry()
        else
            -- Custom/preset anchor not ready yet: fall back to free position and retry.
            pickerButton:SetPoint(pos.point or "CENTER", UIParent, pos.point or "CENTER", pos.x or 0, pos.y or 0)
            if pos.anchorTarget == "CUSTOM" and pos.customFrameName then
                ScheduleCustomRetry()
            end
        end
    else
        pos.anchorTarget = "NONE"
        pickerButton:SetPoint(pos.point or "CENTER", UIParent, pos.point or "CENTER", pos.x or 0, pos.y or 0)
        StopCustomRetry()
    end

    applyingLayout = false
end

local function HideMenu()
    if pickerMenu then pickerMenu:Hide() end
end

local function RefreshButtonLabel()
    if not pickerButton then return end
    local label = DP.GetActiveProfileLabel and DP.GetActiveProfileLabel() or "Auto"
    local reloadIndicator = DP.GetReloadIndicator and DP.GetReloadIndicator() or ""
    pickerButton.label:SetText(label .. reloadIndicator)
end

local function BuildMenu()
    if not pickerMenu then
        pickerMenu = CreateFrame("Frame", "DynamicProfilesPickerMenu", UIParent, "BackdropTemplate")
        pickerMenu:SetSize(220, 40)
        pickerMenu:SetBackdrop({
            bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            tile = true,
            tileSize = 16,
            edgeSize = 12,
            insets = { left = 3, right = 3, top = 3, bottom = 3 },
        })
        pickerMenu:SetBackdropColor(0, 0, 0, 0.9)
        pickerMenu:SetFrameStrata("DIALOG")
        pickerMenu:SetClampedToScreen(true)
        pickerMenu:Hide()
        pickerMenu:EnableMouse(true)

        pickerMenu:SetScript("OnLeave", function(self)
            if not self:IsMouseOver() and not (pickerButton and pickerButton:IsMouseOver()) then
                C_Timer.After(0.15, function()
                    if pickerMenu and pickerMenu:IsShown()
                        and not pickerMenu:IsMouseOver()
                        and not (pickerButton and pickerButton:IsMouseOver()) then
                        HideMenu()
                    end
                end)
            end
        end)
    end

    for _, btn in ipairs(menuButtons) do
        btn:Hide()
    end

    local entries = {}
    table.insert(entries, { id = nil, label = "Auto (by resolution)", isAuto = true })
    for _, profile in ipairs(DP.GetSortedProfiles() or {}) do
        table.insert(entries, {
            id = profile.id,
            label = profile.name or profile.id,
            isAuto = false,
        })
    end

    local activeID = DP.GetActiveProfileID and DP.GetActiveProfileID()
    local y = -8
    local ROW = 28
    local menuWidth = math.max(160, (pickerButton and pickerButton:GetWidth() or 160))
    pickerMenu:SetWidth(menuWidth + 20)

    for i, entry in ipairs(entries) do
        local btn = menuButtons[i]
        if not btn then
            btn = CreateFrame("Button", nil, pickerMenu, "BackdropTemplate")
            btn:SetBackdrop({
                bgFile = "Interface\\Buttons\\WHITE8X8",
                edgeFile = "Interface\\Buttons\\WHITE8X8",
                edgeSize = 1,
            })
            btn:SetBackdropBorderColor(0.2, 0.2, 0.2, 1)
            btn.label = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
            btn.label:SetPoint("LEFT", 8, 0)
            btn.label:SetPoint("RIGHT", -8, 0)
            btn.label:SetJustifyH("LEFT")
            btn:SetScript("OnEnter", function(self)
                self:SetBackdropColor(0.2, 0.35, 0.6, 0.9)
            end)
            btn:SetScript("OnLeave", function(self)
                if self.selected then
                    self:SetBackdropColor(0.15, 0.45, 0.25, 0.9)
                else
                    self:SetBackdropColor(0.08, 0.08, 0.08, 0.9)
                end
            end)
            menuButtons[i] = btn
        end

        local selected = (entry.isAuto and activeID == nil) or (not entry.isAuto and activeID == entry.id)
        btn.selected = selected
        if selected then
            btn:SetBackdropColor(0.15, 0.45, 0.25, 0.9)
        else
            btn:SetBackdropColor(0.08, 0.08, 0.08, 0.9)
        end
        btn:SetSize(menuWidth, 26)
        btn.label:SetText(entry.label)
        btn:ClearAllPoints()
        btn:SetPoint("TOP", pickerMenu, "TOP", 0, y)
        btn:SetScript("OnClick", function()
            HideMenu()
            if entry.isAuto then
                DP.SelectAutoMode(true)
            else
                DP.SelectProfile(entry.id, true)
            end
        end)
        btn:Show()
        y = y - ROW
    end

    pickerMenu:SetHeight(math.max(40, (#entries * ROW) + 16))
end

local function ShowMenu()
    if editingLocked or pickActive then return end
    if pickerMenu and pickerMenu:IsShown() then
        HideMenu()
        return
    end
    BuildMenu()
    pickerMenu:ClearAllPoints()
    pickerMenu:SetPoint("TOP", pickerButton, "BOTTOM", 0, -4)
    pickerMenu:Show()
end

-------------------------------------------------------------------------------
-- Hover-to-select frame picker
-------------------------------------------------------------------------------

local function IsOwnPickFrame(region)
    if not region then return true end
    if region == pickCapture or region == pickHighlight or region == pickInstruction then
        return true
    end
    if region == pickerButton or region == pickerMenu then
        return true
    end
    -- Climb parents for highlight/instruction children
    local p = region
    for _ = 1, 8 do
        if not p then break end
        if p == pickCapture or p == pickHighlight or p == pickInstruction then
            return true
        end
        if p.GetParent then
            p = p:GetParent()
        else
            break
        end
    end
    return false
end

local function ClimbToNamedFrame(region)
    local r = region
    for _ = 1, 20 do
        if not r then return nil end
        if IsOwnPickFrame(r) then return nil end

        local name = r.GetName and r:GetName() or nil
        if type(name) == "string" and name ~= "" and not EXCLUDED_NAMES[name] then
            -- Prefer Frame/Button/etc over Texture/FontString with names (rare).
            local objType = r.GetObjectType and r:GetObjectType() or ""
            if objType ~= "Texture" and objType ~= "FontString" and objType ~= "Line" then
                if r.IsShown and not r:IsShown() then
                    -- keep climbing; shown parent may still be valid
                else
                    local w = r.GetWidth and r:GetWidth() or 0
                    local h = r.GetHeight and r:GetHeight() or 0
                    if w > 1 and h > 1 then
                        return r, name
                    end
                end
            end
        end

        if r.GetParent then
            r = r:GetParent()
        else
            return nil
        end
    end
    return nil
end

local function FindCandidateUnderCursor()
    local foci = (GetMouseFoci and GetMouseFoci()) or (GetMouseFocus and { GetMouseFocus() }) or {}
    for _, region in ipairs(foci) do
        if not IsOwnPickFrame(region) then
            local frame, name = ClimbToNamedFrame(region)
            if frame and name then
                return frame, name
            end
        end
    end
    return nil, nil
end

local function UpdatePickHighlight(frame, name)
    if not pickHighlight then return end
    if not frame then
        pickHighlight:Hide()
        if pickInstruction and pickInstruction.label then
            pickInstruction.label:SetText("Hover a UI element, then Left-Click to anchor.\nRight-Click or Escape to cancel.")
        end
        return
    end
    pickHighlight:ClearAllPoints()
    pickHighlight:SetPoint("TOPLEFT", frame, "TOPLEFT", -3, 3)
    pickHighlight:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 3, -3)
    pickHighlight:Show()
    if pickInstruction and pickInstruction.label then
        pickInstruction.label:SetText("Anchor to: |cffffffff" .. name .. "|r\nLeft-Click to confirm  |  Right-Click / Esc to cancel")
    end
end

local function StopPickMode()
    pickActive = false
    currentCandidate = nil
    if pickCapture then
        pickCapture:SetScript("OnUpdate", nil)
        pickCapture:Hide()
        pickCapture:EnableKeyboard(false)
        pickCapture:SetPropagateKeyboardInput(true)
    end
    if pickHighlight then pickHighlight:Hide() end
    if pickInstruction then pickInstruction:Hide() end
end

local function ConfirmPick()
    if not currentCandidate or not currentCandidate.name then
        return
    end
    local chosenName = currentCandidate.name
    local pos = GetPickerDB()
    pos.anchorTarget = "CUSTOM"
    pos.customFrameName = chosenName
    local side = SIDE_POINTS[pos.anchorSide or "BOTTOM"] or SIDE_POINTS.BOTTOM
    if pos.offsetX == nil then pos.offsetX = side.dx end
    if pos.offsetY == nil then pos.offsetY = side.dy end
    StopPickMode()
    ApplySavedPosition()
    if LibEditMode and LibEditMode.RefreshFrameSettings and pickerButton then
        LibEditMode:RefreshFrameSettings(pickerButton)
    end
    if DP.Print then
        DP.Print("Anchored to '" .. chosenName .. "'.")
    end
end

local function EnsurePickFrames()
    if pickCapture then return end

    pickCapture = CreateFrame("Button", "DynamicProfilesPickCapture", UIParent)
    pickCapture:SetAllPoints(UIParent)
    pickCapture:SetFrameStrata("TOOLTIP")
    pickCapture:SetFrameLevel(10000)
    pickCapture:EnableMouse(true)
    pickCapture:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    pickCapture:Hide()
    -- Propagate motion so GetMouseFoci() includes frames beneath this capture layer.
    -- Clicks stay on the capture so confirm/cancel do not activate the UI underneath.
    if pickCapture.SetPropagateMouseMotion then
        pickCapture:SetPropagateMouseMotion(true)
    end
    if pickCapture.SetPropagateMouseClicks then
        pickCapture:SetPropagateMouseClicks(false)
    end

    local dim = pickCapture:CreateTexture(nil, "BACKGROUND")
    dim:SetAllPoints()
    dim:SetColorTexture(0, 0, 0, 0.15)

    pickHighlight = CreateFrame("Frame", "DynamicProfilesPickHighlight", UIParent, "BackdropTemplate")
    pickHighlight:SetFrameStrata("TOOLTIP")
    pickHighlight:SetFrameLevel(10001)
    pickHighlight:SetBackdrop({
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 16,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    pickHighlight:SetBackdropBorderColor(0.2, 0.85, 1, 1)
    pickHighlight:EnableMouse(false)
    pickHighlight:Hide()

    local fill = pickHighlight:CreateTexture(nil, "BACKGROUND")
    fill:SetAllPoints()
    fill:SetColorTexture(0.15, 0.55, 0.85, 0.18)

    pickInstruction = CreateFrame("Frame", "DynamicProfilesPickInstruction", UIParent, "BackdropTemplate")
    pickInstruction:SetSize(420, 54)
    pickInstruction:SetPoint("TOP", UIParent, "TOP", 0, -80)
    pickInstruction:SetFrameStrata("TOOLTIP")
    pickInstruction:SetFrameLevel(10002)
    pickInstruction:SetBackdrop({
        bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true,
        tileSize = 16,
        edgeSize = 12,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    pickInstruction:SetBackdropColor(0, 0, 0, 0.9)
    pickInstruction:EnableMouse(false)
    pickInstruction.label = pickInstruction:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    pickInstruction.label:SetPoint("CENTER", 0, 0)
    pickInstruction.label:SetJustifyH("CENTER")
    pickInstruction:Hide()

    pickCapture:SetScript("OnClick", function(_, button)
        if button == "LeftButton" then
            if currentCandidate then
                ConfirmPick()
            end
        elseif button == "RightButton" then
            StopPickMode()
            if DP.Print then DP.Print("Anchor pick cancelled.") end
        end
    end)

    pickCapture:SetScript("OnKeyDown", function(self, key)
        if key == "ESCAPE" then
            StopPickMode()
            if DP.Print then DP.Print("Anchor pick cancelled.") end
            if self.SetPropagateKeyboardInput then
                self:SetPropagateKeyboardInput(false)
            end
        else
            if self.SetPropagateKeyboardInput then
                self:SetPropagateKeyboardInput(true)
            end
        end
    end)
end

local function StartPickMode()
    EnsurePickFrames()
    HideMenu()
    pickActive = true
    currentCandidate = nil

    pickCapture:Show()
    pickCapture:EnableKeyboard(true)
    if pickCapture.SetPropagateKeyboardInput then
        pickCapture:SetPropagateKeyboardInput(false)
    end
    pickCapture:SetFrameStrata("TOOLTIP")
    pickInstruction:Show()
    UpdatePickHighlight(nil, nil)

    pickCapture:SetScript("OnUpdate", function()
        if not pickActive then return end

        -- GetMouseFoci includes the capture first when motion is propagated;
        -- FindCandidateUnderCursor skips our own frames and climbs to a named parent.
        local frame, name = FindCandidateUnderCursor()
        if frame and name then
            if not currentCandidate or currentCandidate.name ~= name then
                currentCandidate = { frame = frame, name = name }
                UpdatePickHighlight(frame, name)
            end
        else
            if currentCandidate then
                currentCandidate = nil
                UpdatePickHighlight(nil, nil)
            end
        end
    end)
end

local function CreatePickerButton()
    if pickerButton then return pickerButton end

    pickerButton = CreateFrame("Button", "DynamicProfilesPickerButton", UIParent, "BackdropTemplate")
    pickerButton:SetSize(defaultPosition.width, defaultPosition.height)
    pickerButton:SetFrameStrata("MEDIUM")
    pickerButton:SetClampedToScreen(true)
    pickerButton:EnableMouse(true)
    pickerButton:RegisterForClicks("LeftButtonUp", "RightButtonUp", "MiddleButtonUp")
    pickerButton:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true,
        tileSize = 8,
        edgeSize = 12,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    pickerButton:SetBackdropColor(0.05, 0.08, 0.14, 0.85)
    pickerButton:SetBackdropBorderColor(0.35, 0.55, 0.85, 1)

    pickerButton.label = pickerButton:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    pickerButton.label:SetPoint("CENTER")
    pickerButton.label:SetText("Auto")

    pickerButton:SetScript("OnEnter", function(self)
        if editingLocked or pickActive then return end
        self:SetBackdropBorderColor(0.55, 0.75, 1, 1)
        GameTooltip:SetOwner(self, "ANCHOR_TOP")
        GameTooltip:AddLine("Dynamic Profiles", 1, 1, 1)
        if DP.AddPendingReloadTooltip then
            DP.AddPendingReloadTooltip(GameTooltip)
        end
        GameTooltip:AddLine("Left-click to choose a profile.", 0.8, 0.8, 0.8, true)
        GameTooltip:AddLine("Right-click to open Profile settings.", 0.8, 0.8, 0.8, true)
        GameTooltip:AddLine("Middle-click to reload UI.", 0.8, 0.8, 0.8, true)
        GameTooltip:AddLine("Use Edit Mode to move, resize, or anchor this button.", 0.7, 0.7, 0.7, true)
        GameTooltip:Show()
    end)
    pickerButton:SetScript("OnLeave", function(self)
        self:SetBackdropBorderColor(0.35, 0.55, 0.85, 1)
        GameTooltip:Hide()
    end)
    pickerButton:SetScript("OnClick", function(_, button)
        if editingLocked or pickActive then return end
        if button == "RightButton" then
            HideMenu()
            if DP.OpenProfilesCategory then
                DP.OpenProfilesCategory()
            end
        elseif button == "MiddleButton" then
            HideMenu()
            if DP.ExecuteReload then
                DP.ExecuteReload()
            end
        else
            ShowMenu()
        end
    end)

    ApplySavedPosition()
    return pickerButton
end

local function onPositionChanged(frame, _layoutName, point, x, y)
    local pos = GetPickerDB()
    -- Manual drag always switches back to free positioning.
    pos.anchorTarget = "NONE"
    pos.customFrameName = nil
    pos.point = point
    pos.x = x
    pos.y = y
    StopCustomRetry()
    if not applyingLayout then
        frame:ClearAllPoints()
        frame:SetPoint(point, UIParent, point, x, y)
    end
end

local function IsAnchored()
    local t = GetPickerDB().anchorTarget or "NONE"
    return t ~= "NONE"
end

local function BuildAnchorValues()
    local values = {
        { text = "Free (drag)", value = "NONE", isRadio = true },
        { text = "Minimap", value = "MINIMAP", isRadio = true },
        { text = "Chat Frame", value = "CHAT", isRadio = true },
    }
    local pos = GetPickerDB()
    if pos.customFrameName and pos.customFrameName ~= "" then
        table.insert(values, {
            text = "Custom: " .. pos.customFrameName,
            value = "CUSTOM",
            isRadio = true,
        })
    end
    return values
end

local function RegisterEditModeSettings()
    if not LibEditMode or not pickerButton then return end

    LibEditMode:AddFrameSettings(pickerButton, {
        {
            name = "Width",
            desc = "Button width. Disabled while Match Width is on.",
            kind = LibEditMode.SettingType.Slider,
            default = defaultPosition.width,
            minValue = 70,
            maxValue = 260,
            valueStep = 1,
            get = function()
                return GetPickerDB().width or defaultPosition.width
            end,
            set = function(_, value)
                local pos = GetPickerDB()
                pos.width = Clamp(tonumber(value) or defaultPosition.width, 70, 260)
                ApplySavedPosition()
            end,
            hidden = function()
                return GetPickerDB().matchWidth == true
            end,
        },
        {
            name = "Height",
            desc = "Button height.",
            kind = LibEditMode.SettingType.Slider,
            default = defaultPosition.height,
            minValue = 20,
            maxValue = 48,
            valueStep = 1,
            get = function()
                return GetPickerDB().height or defaultPosition.height
            end,
            set = function(_, value)
                local pos = GetPickerDB()
                pos.height = Clamp(tonumber(value) or defaultPosition.height, 20, 48)
                ApplySavedPosition()
            end,
        },
        {
            name = "Anchor To",
            desc = "Free, Minimap, Chat Frame, or a custom frame picked by hovering.",
            kind = LibEditMode.SettingType.Dropdown,
            default = "NONE",
            height = 160,
            values = function()
                return BuildAnchorValues()
            end,
            get = function()
                return GetPickerDB().anchorTarget or "NONE"
            end,
            set = function(_, value)
                local pos = GetPickerDB()
                if value == "CUSTOM" and (not pos.customFrameName or pos.customFrameName == "") then
                    value = "NONE"
                end
                pos.anchorTarget = value or "NONE"
                if pos.anchorTarget == "NONE" then
                    -- Keep customFrameName so it remains available in the list.
                end
                local side = SIDE_POINTS[pos.anchorSide or "BOTTOM"] or SIDE_POINTS.BOTTOM
                pos.offsetX = side.dx
                pos.offsetY = side.dy
                ApplySavedPosition()
            end,
        },
        {
            name = "Anchor Side",
            desc = "Which side of the chosen element to attach to.",
            kind = LibEditMode.SettingType.Dropdown,
            default = "BOTTOM",
            height = 140,
            values = {
                { text = "Left", value = "LEFT", isRadio = true },
                { text = "Right", value = "RIGHT", isRadio = true },
                { text = "Top", value = "TOP", isRadio = true },
                { text = "Bottom", value = "BOTTOM", isRadio = true },
            },
            get = function()
                return GetPickerDB().anchorSide or "BOTTOM"
            end,
            set = function(_, value)
                local pos = GetPickerDB()
                pos.anchorSide = value or "BOTTOM"
                local side = SIDE_POINTS[pos.anchorSide] or SIDE_POINTS.BOTTOM
                pos.offsetX = side.dx
                pos.offsetY = side.dy
                ApplySavedPosition()
            end,
            hidden = function()
                return not IsAnchored()
            end,
        },
        {
            name = "Match Width",
            desc = "Match the width of the anchored frame.",
            kind = LibEditMode.SettingType.Checkbox,
            default = false,
            get = function()
                return GetPickerDB().matchWidth == true
            end,
            set = function(_, value)
                GetPickerDB().matchWidth = not not value
                ApplySavedPosition()
            end,
            hidden = function()
                return not IsAnchored()
            end,
        },
        {
            name = "Offset X",
            desc = "Horizontal offset from the anchored element.",
            kind = LibEditMode.SettingType.Slider,
            default = 0,
            minValue = -100,
            maxValue = 100,
            valueStep = 1,
            get = function()
                return GetPickerDB().offsetX or 0
            end,
            set = function(_, value)
                GetPickerDB().offsetX = tonumber(value) or 0
                ApplySavedPosition()
            end,
            hidden = function()
                return not IsAnchored()
            end,
        },
        {
            name = "Offset Y",
            desc = "Vertical offset from the anchored element.",
            kind = LibEditMode.SettingType.Slider,
            default = -8,
            minValue = -100,
            maxValue = 100,
            valueStep = 1,
            get = function()
                return GetPickerDB().offsetY or 0
            end,
            set = function(_, value)
                GetPickerDB().offsetY = tonumber(value) or 0
                ApplySavedPosition()
            end,
            hidden = function()
                return not IsAnchored()
            end,
        },
    })

    LibEditMode:AddFrameSettingsButtons(pickerButton, {
        {
            text = "Pick Anchor Frame",
            desc = "Hover any named UI frame and left-click to anchor to it.",
            click = function()
                StartPickMode()
            end,
        },
        {
            text = "Clear Custom Anchor",
            desc = "Remove the saved custom frame and return to Free.",
            click = function()
                local pos = GetPickerDB()
                pos.customFrameName = nil
                if pos.anchorTarget == "CUSTOM" then
                    pos.anchorTarget = "NONE"
                end
                StopCustomRetry()
                ApplySavedPosition()
                if LibEditMode.RefreshFrameSettings then
                    LibEditMode:RefreshFrameSettings(pickerButton)
                end
            end,
        },
    })
end

function DP.UpdatePickerButton()
    -- Broker refresh is funneled here so login/select/options stay in sync.
    if DP.UpdateBroker then DP.UpdateBroker() end
    if not pickerButton then return end
    local pos = GetPickerDB()
    if pos.enabled == false then
        pickerButton:Hide()
        HideMenu()
        return
    end
    ApplySavedPosition()
    pickerButton:Show()
    RefreshButtonLabel()
end

function DP.SetPickerEnabled(enabled)
    GetPickerDB().enabled = not not enabled
    DP.UpdatePickerButton()
end

function DP.InitPicker()
    CreatePickerButton()

    if LibEditMode then
        LibEditMode:AddFrame(
            pickerButton,
            onPositionChanged,
            {
                point = defaultPosition.point,
                x = defaultPosition.x,
                y = defaultPosition.y,
            },
            "Dynamic Profiles"
        )
        RegisterEditModeSettings()

        LibEditMode:RegisterCallback("layout", function()
            ApplySavedPosition()
        end)
        LibEditMode:RegisterCallback("enter", function()
            editingLocked = true
            HideMenu()
            ApplySavedPosition()
            if pickerButton and GetPickerDB().enabled ~= false then
                pickerButton:Show()
            end
        end)
        LibEditMode:RegisterCallback("exit", function()
            editingLocked = false
            if pickActive then StopPickMode() end
            DP.UpdatePickerButton()
        end)
    else
        if DP.Print then
            DP.Print("LibEditMode not found; picker is usable but not Edit Mode–anchorable.")
        end
    end

    ApplySavedPosition()
    DP.UpdatePickerButton()
end
