-- Portal Authority — core/defaults_db.lua
-- Default values, DB normalization, migrations, and font/version helpers.
--
-- Depends on: PortalAuthority; PortalAuthority.CoreInternal gate helpers
-- Exposes:    EnsureDB, NormalizePersistedDockSortMode, font/version helpers, PortalAuthority.CoreInternal scalar utilities/constants
-- DB keys:    PortalAuthorityDB*
-- Events:     none
-- Search key: none

local ADDON_NAME = ...

PortalAuthority = PortalAuthority or {}
PortalAuthority.CoreInternal = PortalAuthority.CoreInternal or {}
local CoreInternal = PortalAuthority.CoreInternal

local PA_IsUiSurfaceGateEnabled = CoreInternal.PA_IsUiSurfaceGateEnabled
local PA_IsStaticBaselineGateEnabled = CoreInternal.PA_IsStaticBaselineGateEnabled
local PA_IsSettingsBaselineGateEnabled = CoreInternal.PA_IsSettingsBaselineGateEnabled
local PA_IsSettingsEagerBuildGateEnabled = CoreInternal.PA_IsSettingsEagerBuildGateEnabled
local PA_IsSettingsDirectBuildGateEnabled = CoreInternal.PA_IsSettingsDirectBuildGateEnabled
local PA_IsSettingsWindowHostEnabled = CoreInternal.PA_IsSettingsWindowHostEnabled
local PA_IsSettingsWindowDevGateEnabled = CoreInternal.PA_IsSettingsWindowDevGateEnabled
local PA_IsSettingsWindowPublicGateEnabled = CoreInternal.PA_IsSettingsWindowPublicGateEnabled
local PA_IsOnboardingD1DevGateEnabled = CoreInternal.PA_IsOnboardingD1DevGateEnabled
local PA_IsOnboardingD3DevGateEnabled = CoreInternal.PA_IsOnboardingD3DevGateEnabled
local PA_IsOnboardingD4DevGateEnabled = CoreInternal.PA_IsOnboardingD4DevGateEnabled
local PA_IsOnboardingD3PublicGateEnabled = CoreInternal.PA_IsOnboardingD3PublicGateEnabled
local PA_IsOnboardingD4PublicGateEnabled = CoreInternal.PA_IsOnboardingD4PublicGateEnabled
local PA_IsOnboardingQualifyingUpgradePublicGateEnabled = CoreInternal.PA_IsOnboardingQualifyingUpgradePublicGateEnabled
local PA_IsOnboardingD3Enabled = CoreInternal.PA_IsOnboardingD3Enabled
local PA_IsOnboardingD4Enabled = CoreInternal.PA_IsOnboardingD4Enabled
local trim = CoreInternal.trim
local PA_CompareSemver = CoreInternal.PA_CompareSemver
local PA_IsSemverStringValid = CoreInternal.PA_IsSemverStringValid
local PA_GetCurrentAddonVersionState = CoreInternal.PA_GetCurrentAddonVersionState
local VALID_OUTLINES = CoreInternal.VALID_OUTLINES
local VALID_SOUND_CHANNELS = CoreInternal.VALID_SOUND_CHANNELS
local BUILTIN_FONT_FALLBACK = CoreInternal.BUILTIN_FONT_FALLBACK
local PLAYER_LOGIN_MESSAGE = CoreInternal.PLAYER_LOGIN_MESSAGE
local DOCK_ENABLE_DEFAULTS_VERSION = CoreInternal.DOCK_ENABLE_DEFAULTS_VERSION
local PA_PrintPortalAuthorityMessage = CoreInternal.PA_PrintPortalAuthorityMessage
local PA_MaybeShowMPlusNotice = CoreInternal.PA_MaybeShowMPlusNotice
local ResetMPlusNoticeShown = CoreInternal.ResetMPlusNoticeShown
local PA_IsSecretValue = CoreInternal.PA_IsSecretValue
local PA_NormalizeSafeString = CoreInternal.PA_NormalizeSafeString
local PA_SafeStringsEqual = CoreInternal.PA_SafeStringsEqual
local PA_SafeUnitGUID = CoreInternal.PA_SafeUnitGUID

PortalAuthority.defaults = {
    announceEnabled = true,
    announcementText = "Porting to [destination] - click my portal!",
    portalsEnabled = true,
    portalsText = "Porting to [destination] - click my portal!",
    teleportsEnabled = true,
    teleportsText = "Teleporting to [destination].",
    mplusEnabled = true,
    mplusText = "Taking M+ teleport to [destination].",
    warlockSummoningEnabled = true,
    warlockSummoningText = "Summoning stone is up - please click.",
    summonRequestsEnabled = true,
    summonRequestSoundChannel = "Master",
    summonRequestSound = "Overclock: Pause",
    summonStoneRequestsEnabled = true,
    summonStoneRequestSoundChannel = "Master",
    summonStoneRequestSound = "Overclock: OOT Press Start",
    keystoneHelperEnabled = true,
    keystoneAutoSlotEnabled = false,
    keystoneAutoStartEnabled = false,
    pullCommandEnabled = true,
    pullTimerMethod = 1,
    pullShortSeconds = 5,
    pullLongSeconds = 10,
    releaseGateEnabled = false,
    releaseGateModifier = "SHIFT",
    releaseGateHoldSeconds = 1.0,
    keystoneTooltipDungeonNameMode = "NO_CHANGES",
    keystoneTooltipRemoveKeystonePrefix = false,
    keystoneTooltipLevelMode = "NO_CHANGES",
    keystoneTooltipLevelAddToName = false,
    keystoneTooltipResilientMode = "NO_CHANGES",
    keystoneTooltipSoulboundMode = "NO_CHANGES",
    keystoneTooltipUniqueMode = "NO_CHANGES",
    keystoneTooltipAffixesMode = "NO_CHANGES",
    keystoneTooltipAffixesColor = { r = 1, g = 0.82, b = 0, a = 1 },
    keystoneTooltipDurationMode = "NO_CHANGES",
    keystoneTooltipRPQuoteMode = "NO_CHANGES",
    deathAlertTankEnabled = true,
    deathAlertTankSound = "Overclock: MGS",
    deathAlertTankOnScreen = true,
    deathAlertHealerEnabled = false,
    deathAlertHealerSound = "",
    deathAlertHealerOnScreen = false,
    deathAlertDpsEnabled = false,
    deathAlertDpsSound = "",
    deathAlertDpsOnScreen = false,
    deathAlertSelfEnabled = false,
    deathAlertSelfSound = "",
    deathAlertSelfOnScreen = false,
    deathAlertMessageTemplate = "[role] Died: [name]",
    deathAlertLocked = true,
    deathAlertX = 0,
    deathAlertY = 0,
    deathAlertFontPath = "",
    deathAlertFontSize = 36,
    deathAlertSoundChannel = "Master",
    deathAlertShowRoleIcon = true,
    deathAlertAntiSpamWipeEnabled = true,
    combatAlertsEnabled = true,

    dockEnabled = true,
    dockOrientation = "VERTICAL",
    dockX = 0,
    dockY = 0,
    dockLocked = true,
    dockHideInMajorCity = false,
    dockHideInDungeon = false,
    dockHideInCombat = false,
    dockGizmoMode = false,
    dockIconWidth = 32,
    dockIconHeight = 32,
    dockHideDungeonName = false,
    dockUseShortNames = true,
    dockVerticalTextSide = "LEFT",
    dockHorizontalTextPosition = "ABOVE",
    dockTextIconSpacing = 8,
    dockFontSize = 12,
    dockFontPath = "",
    dockFontColor = { r = 1, g = 0.82, b = 0, a = 1 },
    dockTextOutline = "OUTLINE",
    dockTextShadow = true,
    dockShadowOffsetX = 1,
    dockShadowOffsetY = -1,
    dockRowSpacing = 3,
    dockLayoutMode = "ADAPTIVE_GRID",
    dockWrapAfter = 4,
    dockIconSize = 36,
    dockSpacingX = 6,
    dockSpacingY = 6,
    dockPadding = 6,
    dockGrowthX = "RIGHT",
    dockGrowthY = "DOWN",
    dockAnchorPoint = "CENTER",
    dockCenterAlignment = true,
    dockPreferAxis = "AUTO",
    dockCompactMode = false,
    dockTestMode = false,
    dockSortMode = "ROW_ORDER",
    dockAnimateReflow = false,
    dockAnimateDuration = 0.15,
    dockBackdropEnabled = false,
    dockInactiveAlpha = 0.5,
    dockHoverGlow = true,
    dockSimpleLayoutMode = "GRID",
    dockIconsPerLine = 4,
    dockIconSpacing = 6,
    dockDensity = 50,
    dockTextDirection = "BOTTOM",
    dockTextAlign = "CENTER",
    dockLabelAlignMode = "CENTER",
    dockLabelMode = "OUTSIDE",
    dockLabelModePersist = "OUTSIDE",
    dockLabelSide = "BOTTOM",
    dockLabelSidePersist = "BOTTOM",
    dockLabelSideOutsidePersist = "BOTTOM",
    dockTextOffset = 2,
    dockHoverGlowAlpha = 0.2,
    dockHoverGlowSize = 0,

    modules = {
        brezTimer = {
            enabled = true,
            x = 0,
            y = 0,
            textFormat = "{mm}:{ss}",
            timeFormatMode = "mmss",
            font = "",
            fontSize = 14,
            color = { r = 1, g = 1, b = 1, a = 1 },
            label1Text = "Battle Rez",
            label2Text = "Next In",
            label1Font = "",
            label2Font = "",
            valueFont = "",
            label1FontSize = 14,
            label2FontSize = 14,
            valueFontSize = 14,
            label1Color = { r = 1, g = 1, b = 1, a = 1 },
            label2Color = { r = 1, g = 1, b = 1, a = 1 },
            valueColor = { r = 1, g = 1, b = 1, a = 1 },
            value1Color = { r = 1, g = 1, b = 1, a = 1 },
            value2Color = { r = 1, g = 1, b = 1, a = 1 },
        },
    },
}
local VALID_OUTLINES = {
    [""] = true,
    ["OUTLINE"] = true,
    ["THICKOUTLINE"] = true,
}

local VALID_SOUND_CHANNELS = {
    Master = true,
    SFX = true,
    Music = true,
    Ambience = true,
    Dialog = true,
}

local BUILTIN_FONT_FALLBACK = "Fonts\\FRIZQT__.TTF"
local PLAYER_LOGIN_MESSAGE = "— type /pa to open settings."
local DOCK_ENABLE_DEFAULTS_VERSION = "2.0.0"

local function PA_TryGetAddonVersionString()
    if C_AddOns and C_AddOns.GetAddOnMetadata then
        local v = C_AddOns.GetAddOnMetadata(ADDON_NAME, "Version")
        if type(v) == "string" and v ~= "" then return v end
    end
    if GetAddOnMetadata then
        local v = GetAddOnMetadata(ADDON_NAME, "Version")
        if type(v) == "string" and v ~= "" then return v end
    end
    return nil
end

local function PA_GetAddonVersionString()
    local version = PA_TryGetAddonVersionString()
    if type(version) == "string" and version ~= "" then
        return version
    end
    return "0.0.0"
end

function PortalAuthority:GetAnnouncementReleaseVersionString()
    return PortalAuthority._buildConfig.release.settingsWindowAnnouncementVersion
end

local function PA_ParseSemver(v)
    if type(v) ~= "string" then
        return 0, 0, 0
    end
    local major, minor, patch = v:match("^%s*v?(%d+)%.(%d+)%.(%d+)")
    if not major then
        major, minor = v:match("^%s*v?(%d+)%.(%d+)")
        patch = "0"
    end
    if not major then
        major = v:match("^%s*v?(%d+)")
        minor = "0"
        patch = "0"
    end
    return tonumber(major) or 0, tonumber(minor) or 0, tonumber(patch) or 0
end

local function PA_CompareSemver(leftVersion, rightVersion)
    local lMaj, lMin, lPatch = PA_ParseSemver(leftVersion)
    local rMaj, rMin, rPatch = PA_ParseSemver(rightVersion)
    if lMaj ~= rMaj then
        return lMaj < rMaj and -1 or 1
    end
    if lMin ~= rMin then
        return lMin < rMin and -1 or 1
    end
    if lPatch ~= rPatch then
        return lPatch < rPatch and -1 or 1
    end
    return 0
end

local function trim(text)
    if type(hasanysecretvalues) == "function" then
        local ok, hasSecret = pcall(hasanysecretvalues, text)
        if ok and hasSecret then
            return ""
        end
    end
    if type(text) ~= "string" then
        return ""
    end
    return text:gsub("^%s+", ""):gsub("%s+$", "")
end

local function PA_IsSemverStringValid(version)
    if type(version) ~= "string" then
        return false
    end
    local normalized = trim(version)
    if normalized == "" then
        return false
    end
    return normalized:match("^[vV]?%d+%.%d+%.%d+$") ~= nil
        or normalized:match("^[vV]?%d+%.%d+$") ~= nil
        or normalized:match("^[vV]?%d+$") ~= nil
end

local function PA_GetCurrentAddonVersionState()
    local rawVersion = PA_TryGetAddonVersionString()
    local normalized = nil
    if type(rawVersion) == "string" then
        normalized = trim(rawVersion)
        if normalized == "" then
            normalized = nil
        end
    end
    return normalized, PA_IsSemverStringValid(normalized)
end

local function clampNumber(value, fallback, min, max)
    value = tonumber(value) or fallback
    if min and value < min then
        value = min
    end
    if max and value > max then
        value = max
    end
    return value
end

local function PA_Clamp(value, fallback, min, max)
    local n = tonumber(value)
    if n == nil then
        local ok, s = pcall(tostring, value)
        if ok then
            n = tonumber(s)
        end
    end
    value = n or fallback
    if min and value < min then value = min end
    if max and value > max then value = max end
    return value
end

local function PA_ToNumber(v, fallback)
    if type(v) == "number" then
        return v
    end
    local ok, s = pcall(tostring, v)
    if ok and s ~= nil then
        local n = tonumber(s)
        if n ~= nil then
            return n
        end
    end
    return fallback
end

local function PA_SafeBool(v)
    local ok, s = pcall(tostring, v)
    if not ok or s == nil then return false end
    local okb, b = pcall(string.byte, s, 1)
    if not okb or b == nil then return false end
    local nb = tonumber(tostring(b)) or 0
    return nb == 116
end

local function sanitizeColor(color, fallback)
    if type(color) ~= "table" then
        return { r = fallback.r, g = fallback.g, b = fallback.b, a = fallback.a }
    end

    return {
        r = clampNumber(color.r, fallback.r, 0, 1),
        g = clampNumber(color.g, fallback.g, 0, 1),
        b = clampNumber(color.b, fallback.b, 0, 1),
        a = clampNumber(color.a, fallback.a, 0, 1),
    }
end

local function migrateTemplateTokens(text)
    if type(text) ~= "string" then
        return text
    end
    return text:gsub("{dest}", "[destination]")
end

function PortalAuthority:NormalizePersistedDockSortMode(db)
    if type(db) ~= "table" then
        self._dockUnexpectedPersistedSortMode = nil
        self._dockUnexpectedPersistedSortModeSource = nil
        return false, nil
    end

    local raw = db.dockSortMode
    local changed = false
    local auditValue = nil
    if raw == nil or raw == "TYPE_ID" then
        db.dockSortMode = "ROW_ORDER"
        changed = true
    elseif raw ~= "ROW_ORDER" then
        auditValue = tostring(raw)
    end

    self._dockUnexpectedPersistedSortMode = auditValue
    self._dockUnexpectedPersistedSortModeSource = auditValue and "PortalAuthorityDB.dockSortMode" or nil

    return changed, auditValue
end

function PortalAuthority:EnsureDB()
    if self.InitializeProfiles and not self._profilesInitialized then
        self:InitializeProfiles()
    end

    local dbWasNil = (PortalAuthorityDB == nil)
    PortalAuthorityDB = PortalAuthorityDB or {}

    local db = PortalAuthorityDB
    local defaults = self.defaults
    local dockV200MigrationTriggered = false
    local dockSortModeChanged = false
    local meta = (self.GetOperationalMeta and self:GetOperationalMeta()) or db

    if meta.dockEnableDefaultsVersion == nil then
        meta.dockEnableDefaultsVersion = DOCK_ENABLE_DEFAULTS_VERSION
        meta.dockEnableDefaultsMigrated = true
        if self._profilesLegacyMigratedThisLoad then
            meta.dockV200NoticePending = true
            dockV200MigrationTriggered = true
        else
            meta.dockV200NoticePending = false
        end
    else
        local storedDockVersion = trim(tostring(meta.dockEnableDefaultsVersion or ""))
        if storedDockVersion == "" or PA_CompareSemver(storedDockVersion, DOCK_ENABLE_DEFAULTS_VERSION) < 0 then
            meta.dockEnableDefaultsVersion = DOCK_ENABLE_DEFAULTS_VERSION
            meta.dockEnableDefaultsMigrated = true
            meta.dockV200NoticePending = true
            dockV200MigrationTriggered = true
        else
            if meta.dockEnableDefaultsMigrated == nil then
                meta.dockEnableDefaultsMigrated = true
            end
            if meta.dockV200NoticePending == nil then
                meta.dockV200NoticePending = false
            end
        end
    end

    if db.announceEnabled == nil then
        db.announceEnabled = defaults.announceEnabled
    end
    if trim(db.announcementText) == "" then
        db.announcementText = defaults.announcementText
    end
    db.announcementText = migrateTemplateTokens(db.announcementText)

    if db.portalsEnabled == nil then
        db.portalsEnabled = db.announceEnabled
    end
    if trim(db.portalsText) == "" then
        db.portalsText = trim(db.announcementText) ~= "" and db.announcementText or defaults.portalsText
    end
    db.portalsText = migrateTemplateTokens(db.portalsText)

    if db.teleportsEnabled == nil then
        db.teleportsEnabled = defaults.teleportsEnabled
    end
    if trim(db.teleportsText) == "" then
        db.teleportsText = defaults.teleportsText
    end
    db.teleportsText = migrateTemplateTokens(db.teleportsText)

    if db.mplusEnabled == nil then
        db.mplusEnabled = defaults.mplusEnabled
    end
    if trim(db.mplusText) == "" then
        db.mplusText = defaults.mplusText
    end
    db.mplusText = migrateTemplateTokens(db.mplusText)

    if db.warlockSummoningEnabled == nil then
        db.warlockSummoningEnabled = defaults.warlockSummoningEnabled
    end
    if trim(db.warlockSummoningText) == "" then
        db.warlockSummoningText = defaults.warlockSummoningText
    end
    db.warlockSummoningText = migrateTemplateTokens(db.warlockSummoningText)

    db.sayWhenSoloEnabled = nil

    if db.summonRequestsEnabled == nil then
        db.summonRequestsEnabled = defaults.summonRequestsEnabled
    end
    if trim(db.summonRequestSoundChannel) == "" then
        db.summonRequestSoundChannel = defaults.summonRequestSoundChannel
    end
    if db.summonRequestSound == nil or trim(db.summonRequestSound) == "" then
        db.summonRequestSound = defaults.summonRequestSound
    end

    if db.summonStoneRequestsEnabled == nil then
        db.summonStoneRequestsEnabled = defaults.summonStoneRequestsEnabled
    end
    if trim(db.summonStoneRequestSoundChannel) == "" then
        db.summonStoneRequestSoundChannel = defaults.summonStoneRequestSoundChannel
    end
    if db.summonStoneRequestSound == nil or trim(db.summonStoneRequestSound) == "" then
        db.summonStoneRequestSound = defaults.summonStoneRequestSound
    end

    if db.keystoneHelperEnabled == nil then
        db.keystoneHelperEnabled = defaults.keystoneHelperEnabled
    end
    if db.keystoneAutoSlotEnabled == nil then
        db.keystoneAutoSlotEnabled = defaults.keystoneAutoSlotEnabled
    end
    if db.keystoneAutoStartEnabled == nil then
        db.keystoneAutoStartEnabled = defaults.keystoneAutoStartEnabled
    end
    if db.pullCommandEnabled == nil then
        db.pullCommandEnabled = defaults.pullCommandEnabled
    end
    if db.pullTimerMethod == nil then
        db.pullTimerMethod = defaults.pullTimerMethod
    end
    if db.pullShortSeconds == nil then
        db.pullShortSeconds = defaults.pullShortSeconds
    end
    if db.pullLongSeconds == nil then
        db.pullLongSeconds = defaults.pullLongSeconds
    end
    if db.releaseGateEnabled == nil then
        db.releaseGateEnabled = defaults.releaseGateEnabled
    end
    if trim(db.releaseGateModifier) == "" then
        db.releaseGateModifier = defaults.releaseGateModifier
    end
    if db.releaseGateHoldSeconds == nil then
        db.releaseGateHoldSeconds = defaults.releaseGateHoldSeconds
    end
    if db.keystoneTooltipDungeonNameMode == nil then
        db.keystoneTooltipDungeonNameMode = defaults.keystoneTooltipDungeonNameMode
    end
    if db.keystoneTooltipRemoveKeystonePrefix == nil then
        db.keystoneTooltipRemoveKeystonePrefix = defaults.keystoneTooltipRemoveKeystonePrefix
    end
    if db.keystoneTooltipLevelMode == nil then
        db.keystoneTooltipLevelMode = defaults.keystoneTooltipLevelMode
    end
    if db.keystoneTooltipLevelAddToName == nil then
        db.keystoneTooltipLevelAddToName = defaults.keystoneTooltipLevelAddToName
    end
    if db.keystoneTooltipResilientMode == nil then
        db.keystoneTooltipResilientMode = defaults.keystoneTooltipResilientMode
    end
    if db.keystoneTooltipSoulboundMode == nil then
        db.keystoneTooltipSoulboundMode = defaults.keystoneTooltipSoulboundMode
    end
    if db.keystoneTooltipUniqueMode == nil then
        db.keystoneTooltipUniqueMode = defaults.keystoneTooltipUniqueMode
    end
    if db.keystoneTooltipAffixesMode == nil then
        db.keystoneTooltipAffixesMode = defaults.keystoneTooltipAffixesMode
    end
    if db.keystoneTooltipAffixesColor == nil then
        db.keystoneTooltipAffixesColor = defaults.keystoneTooltipAffixesColor
    end
    if db.keystoneTooltipDurationMode == nil then
        db.keystoneTooltipDurationMode = defaults.keystoneTooltipDurationMode
    end
    if db.keystoneTooltipRPQuoteMode == nil then
        db.keystoneTooltipRPQuoteMode = defaults.keystoneTooltipRPQuoteMode
    end
    if db.deathAlertTankEnabled == nil then
        db.deathAlertTankEnabled = defaults.deathAlertTankEnabled
    end
    if db.deathAlertTankSound == nil then
        db.deathAlertTankSound = defaults.deathAlertTankSound
    end
    if db.deathAlertTankOnScreen == nil then
        db.deathAlertTankOnScreen = defaults.deathAlertTankOnScreen
    end
    if db.deathAlertHealerEnabled == nil then
        db.deathAlertHealerEnabled = defaults.deathAlertHealerEnabled
    end
    if db.deathAlertHealerSound == nil then
        db.deathAlertHealerSound = defaults.deathAlertHealerSound
    end
    if db.deathAlertHealerOnScreen == nil then
        db.deathAlertHealerOnScreen = defaults.deathAlertHealerOnScreen
    end
    if db.deathAlertDpsEnabled == nil then
        db.deathAlertDpsEnabled = defaults.deathAlertDpsEnabled
    end
    if db.deathAlertDpsSound == nil then
        db.deathAlertDpsSound = defaults.deathAlertDpsSound
    end
    if db.deathAlertDpsOnScreen == nil then
        db.deathAlertDpsOnScreen = defaults.deathAlertDpsOnScreen
    end
    if db.deathAlertSelfEnabled == nil then
        db.deathAlertSelfEnabled = defaults.deathAlertSelfEnabled
    end
    if db.deathAlertSelfSound == nil then
        db.deathAlertSelfSound = defaults.deathAlertSelfSound
    end
    if db.deathAlertSelfOnScreen == nil then
        db.deathAlertSelfOnScreen = defaults.deathAlertSelfOnScreen
    end
    if db.deathAlertMessageTemplate == nil then
        db.deathAlertMessageTemplate = defaults.deathAlertMessageTemplate
    end
    if db.deathAlertLocked == nil then
        db.deathAlertLocked = defaults.deathAlertLocked
    end
    if db.deathAlertX == nil then
        db.deathAlertX = defaults.deathAlertX
    end
    if db.deathAlertY == nil then
        db.deathAlertY = defaults.deathAlertY
    end
    if db.deathAlertFontPath == nil then
        db.deathAlertFontPath = defaults.deathAlertFontPath
    end
    if db.deathAlertFontSize == nil then
        db.deathAlertFontSize = defaults.deathAlertFontSize
    end
    if db.deathAlertSoundChannel == nil then
        db.deathAlertSoundChannel = defaults.deathAlertSoundChannel
    end
    if db.deathAlertShowRoleIcon == nil then
        db.deathAlertShowRoleIcon = defaults.deathAlertShowRoleIcon
    end
    if db.deathAlertAntiSpamWipeEnabled == nil then
        db.deathAlertAntiSpamWipeEnabled = defaults.deathAlertAntiSpamWipeEnabled
    end

    db.keystoneHelperEnabled = PA_SafeBool(db.keystoneHelperEnabled)
    db.keystoneAutoSlotEnabled = PA_SafeBool(db.keystoneAutoSlotEnabled)
    db.keystoneAutoStartEnabled = PA_SafeBool(db.keystoneAutoStartEnabled)
    db.pullCommandEnabled = not not db.pullCommandEnabled
    db.pullTimerMethod = math.floor(PA_Clamp(db.pullTimerMethod, defaults.pullTimerMethod, 1, 3))
    db.pullShortSeconds = math.floor(PA_Clamp(db.pullShortSeconds, defaults.pullShortSeconds, 1, 60))
    db.pullLongSeconds = math.floor(PA_Clamp(db.pullLongSeconds, defaults.pullLongSeconds, 1, 60))
    db.releaseGateEnabled = PA_SafeBool(db.releaseGateEnabled)
    if db.releaseGateModifier ~= "SHIFT" and db.releaseGateModifier ~= "CTRL" then
        db.releaseGateModifier = defaults.releaseGateModifier
    end
    local hold = PA_ToNumber(db.releaseGateHoldSeconds, defaults.releaseGateHoldSeconds)
    db.releaseGateHoldSeconds = PA_Clamp(hold, defaults.releaseGateHoldSeconds, 0.0, 5.0)
    db.keystoneTooltipRemoveKeystonePrefix = PA_SafeBool(db.keystoneTooltipRemoveKeystonePrefix)
    db.keystoneTooltipLevelAddToName = PA_SafeBool(db.keystoneTooltipLevelAddToName)
    if db.keystoneTooltipDungeonNameMode ~= "FULL"
        and db.keystoneTooltipDungeonNameMode ~= "SHORT"
        and db.keystoneTooltipDungeonNameMode ~= "NICKNAME"
        and db.keystoneTooltipDungeonNameMode ~= "NICKNAME_FULL"
        and db.keystoneTooltipDungeonNameMode ~= "NO_CHANGES"
    then
        db.keystoneTooltipDungeonNameMode = defaults.keystoneTooltipDungeonNameMode
    end
    if db.keystoneTooltipLevelMode ~= "PLUS_N"
        and db.keystoneTooltipLevelMode ~= "N"
        and db.keystoneTooltipLevelMode ~= "M_N_PLUS"
        and db.keystoneTooltipLevelMode ~= "HIDE"
        and db.keystoneTooltipLevelMode ~= "NO_CHANGES"
    then
        db.keystoneTooltipLevelMode = defaults.keystoneTooltipLevelMode
    end
    if db.keystoneTooltipResilientMode ~= "HIDE" and db.keystoneTooltipResilientMode ~= "NO_CHANGES" then
        db.keystoneTooltipResilientMode = defaults.keystoneTooltipResilientMode
    end
    if db.keystoneTooltipSoulboundMode ~= "HIDE" and db.keystoneTooltipSoulboundMode ~= "NO_CHANGES" then
        db.keystoneTooltipSoulboundMode = defaults.keystoneTooltipSoulboundMode
    end
    if db.keystoneTooltipUniqueMode ~= "HIDE" and db.keystoneTooltipUniqueMode ~= "NO_CHANGES" then
        db.keystoneTooltipUniqueMode = defaults.keystoneTooltipUniqueMode
    end
    if db.keystoneTooltipAffixesMode ~= "RENAME_AFFIXES"
        and db.keystoneTooltipAffixesMode ~= "HIDE"
        and db.keystoneTooltipAffixesMode ~= "NO_CHANGES"
    then
        db.keystoneTooltipAffixesMode = defaults.keystoneTooltipAffixesMode
    end
    if db.keystoneTooltipDurationMode ~= "HIDE" and db.keystoneTooltipDurationMode ~= "NO_CHANGES" then
        db.keystoneTooltipDurationMode = defaults.keystoneTooltipDurationMode
    end
    if db.keystoneTooltipRPQuoteMode ~= "HIDE" and db.keystoneTooltipRPQuoteMode ~= "NO_CHANGES" then
        db.keystoneTooltipRPQuoteMode = defaults.keystoneTooltipRPQuoteMode
    end
    db.keystoneTooltipAffixesColor = sanitizeColor(db.keystoneTooltipAffixesColor, defaults.keystoneTooltipAffixesColor)
    db.deathAlertTankEnabled = PA_SafeBool(db.deathAlertTankEnabled)
    db.deathAlertTankOnScreen = PA_SafeBool(db.deathAlertTankOnScreen)
    db.deathAlertHealerEnabled = PA_SafeBool(db.deathAlertHealerEnabled)
    db.deathAlertHealerOnScreen = PA_SafeBool(db.deathAlertHealerOnScreen)
    db.deathAlertDpsEnabled = PA_SafeBool(db.deathAlertDpsEnabled)
    db.deathAlertDpsOnScreen = PA_SafeBool(db.deathAlertDpsOnScreen)
    db.deathAlertSelfEnabled = PA_SafeBool(db.deathAlertSelfEnabled)
    db.deathAlertSelfOnScreen = PA_SafeBool(db.deathAlertSelfOnScreen)
    db.deathAlertLocked = PA_SafeBool(db.deathAlertLocked)
    db.deathAlertShowRoleIcon = PA_SafeBool(db.deathAlertShowRoleIcon)
    db.deathAlertAntiSpamWipeEnabled = PA_SafeBool(db.deathAlertAntiSpamWipeEnabled)
    if type(db.deathAlertTankSound) ~= "string" then
        db.deathAlertTankSound = tostring(db.deathAlertTankSound or defaults.deathAlertTankSound)
    end
    if type(db.deathAlertHealerSound) ~= "string" then
        db.deathAlertHealerSound = tostring(db.deathAlertHealerSound or defaults.deathAlertHealerSound)
    end
    if type(db.deathAlertDpsSound) ~= "string" then
        db.deathAlertDpsSound = tostring(db.deathAlertDpsSound or defaults.deathAlertDpsSound)
    end
    if type(db.deathAlertSelfSound) ~= "string" then
        db.deathAlertSelfSound = tostring(db.deathAlertSelfSound or defaults.deathAlertSelfSound)
    end
    db.deathAlertX = math.floor(PA_Clamp(db.deathAlertX, defaults.deathAlertX, -10000, 10000))
    db.deathAlertY = math.floor(PA_Clamp(db.deathAlertY, defaults.deathAlertY, -10000, 10000))
    db.deathAlertFontSize = math.floor(PA_Clamp(db.deathAlertFontSize, defaults.deathAlertFontSize, 8, 72))
    if trim(db.deathAlertFontPath) == "" then
        db.deathAlertFontPath = defaults.deathAlertFontPath
    end
    if not VALID_SOUND_CHANNELS[db.deathAlertSoundChannel] then
        db.deathAlertSoundChannel = defaults.deathAlertSoundChannel
    end
    if trim(db.deathAlertMessageTemplate) == "" then
        db.deathAlertMessageTemplate = defaults.deathAlertMessageTemplate
    end

    if db.dockEnabled == nil then
        db.dockEnabled = defaults.dockEnabled
    end
    if dbWasNil or dockV200MigrationTriggered then
        db.dockEnabled = true
    end

    if db.dockOrientation ~= "VERTICAL" and db.dockOrientation ~= "HORIZONTAL" then
        db.dockOrientation = defaults.dockOrientation
    end
    db.dockX = clampNumber(db.dockX, defaults.dockX)
    db.dockY = clampNumber(db.dockY, defaults.dockY)

    if db.dockLocked == nil then
        db.dockLocked = defaults.dockLocked
    end
    if db.dockHideInMajorCity == nil then
        if db.dockShowInMajorCity ~= nil then
            db.dockHideInMajorCity = not db.dockShowInMajorCity
        else
            db.dockHideInMajorCity = defaults.dockHideInMajorCity
        end
    end
    db.dockShowInMajorCity = nil

    if db.dockHideInDungeon == nil then
        db.dockHideInDungeon = defaults.dockHideInDungeon
    end

    if db.dockHideInCombat == nil then
        db.dockHideInCombat = defaults.dockHideInCombat
    end


    if db.dockGizmoMode == nil then
        db.dockGizmoMode = defaults.dockGizmoMode
    end
    db.dockIconWidth = math.floor(clampNumber(db.dockIconWidth, defaults.dockIconWidth, 16, 128))
    db.dockIconHeight = math.floor(clampNumber(db.dockIconHeight, defaults.dockIconHeight, 16, 128))
    db.dockTextIconSpacing = math.floor(clampNumber(db.dockTextIconSpacing, defaults.dockTextIconSpacing, 0, 40))
    db.dockRowSpacing = math.floor(clampNumber(db.dockRowSpacing, defaults.dockRowSpacing, 0, 40))

    if db.dockHideDungeonName == nil then
        if db.dockShowAcronyms ~= nil then
            db.dockHideDungeonName = not db.dockShowAcronyms
        else
            db.dockHideDungeonName = defaults.dockHideDungeonName
        end
    end
    db.dockShowAcronyms = nil

    if db.dockUseShortNames == nil then
        db.dockUseShortNames = defaults.dockUseShortNames
    end

    if db.dockVerticalTextSide == nil then
        if db.dockTextPosition == "LEFT" or db.dockTextPosition == "RIGHT" then
            db.dockVerticalTextSide = db.dockTextPosition
        else
            db.dockVerticalTextSide = defaults.dockVerticalTextSide
        end
    end
    db.dockTextPosition = nil

    if db.dockVerticalTextSide ~= "LEFT" and db.dockVerticalTextSide ~= "RIGHT" then
        db.dockVerticalTextSide = defaults.dockVerticalTextSide
    end

    if db.dockHorizontalTextPosition ~= "ABOVE" and db.dockHorizontalTextPosition ~= "BELOW" then
        db.dockHorizontalTextPosition = defaults.dockHorizontalTextPosition
    end

    if db.dockOrientation == "HORIZONTAL" then
        db.dockUseShortNames = true
        if db.dockHorizontalTextPosition ~= "ABOVE" and db.dockHorizontalTextPosition ~= "BELOW" then
            db.dockHorizontalTextPosition = defaults.dockHorizontalTextPosition
        end
    else
        if db.dockVerticalTextSide ~= "LEFT" and db.dockVerticalTextSide ~= "RIGHT" then
            db.dockVerticalTextSide = defaults.dockVerticalTextSide
        end
    end

    db.dockFontSize = math.floor(clampNumber(db.dockFontSize, defaults.dockFontSize, 8, 48))
    if db.dockFontPath == nil then
        db.dockFontPath = defaults.dockFontPath
    end
    db.dockFontColor = sanitizeColor(db.dockFontColor, defaults.dockFontColor)

    if not VALID_OUTLINES[db.dockTextOutline] then
        db.dockTextOutline = defaults.dockTextOutline
    end

    if db.dockTextShadow == nil then
        db.dockTextShadow = defaults.dockTextShadow
    end

    db.dockShadowOffsetX = clampNumber(db.dockShadowOffsetX, defaults.dockShadowOffsetX, -10, 10)
    db.dockShadowOffsetY = clampNumber(db.dockShadowOffsetY, defaults.dockShadowOffsetY, -10, 10)

    if db.dockLayoutMode == nil then
        db.dockLayoutMode = defaults.dockLayoutMode
    end
    if db.dockWrapAfter == nil then
        db.dockWrapAfter = defaults.dockWrapAfter
    end
    if db.dockIconSize == nil then
        db.dockIconSize = defaults.dockIconSize
    end
    if db.dockSpacingX == nil then
        db.dockSpacingX = defaults.dockSpacingX
    end
    if db.dockSpacingY == nil then
        db.dockSpacingY = defaults.dockSpacingY
    end
    if db.dockPadding == nil then
        db.dockPadding = defaults.dockPadding
    end
    if db.dockGrowthX == nil then
        db.dockGrowthX = defaults.dockGrowthX
    end
    if db.dockGrowthY == nil then
        db.dockGrowthY = defaults.dockGrowthY
    end
    if db.dockAnchorPoint == nil then
        db.dockAnchorPoint = defaults.dockAnchorPoint
    end
    if db.dockCenterAlignment == nil then
        db.dockCenterAlignment = defaults.dockCenterAlignment
    end
    if db.dockPreferAxis == nil then
        db.dockPreferAxis = defaults.dockPreferAxis
    end
    if db.dockCompactMode == nil then
        db.dockCompactMode = defaults.dockCompactMode
    end
    if db.dockTestMode == nil then
        db.dockTestMode = defaults.dockTestMode
    end
    dockSortModeChanged = select(1, self:NormalizePersistedDockSortMode(db)) or false
    if db.dockAnimateReflow == nil then
        db.dockAnimateReflow = defaults.dockAnimateReflow
    end
    if db.dockAnimateDuration == nil then
        db.dockAnimateDuration = defaults.dockAnimateDuration
    end
    if db.dockBackdropEnabled == nil then
        db.dockBackdropEnabled = defaults.dockBackdropEnabled
    end
    if db.dockInactiveAlpha == nil then
        db.dockInactiveAlpha = defaults.dockInactiveAlpha
    end
    if db.dockHoverGlow == nil then
        db.dockHoverGlow = defaults.dockHoverGlow
    end
    if db.dockSimpleLayoutMode == nil then
        db.dockSimpleLayoutMode = defaults.dockSimpleLayoutMode
    end
    if db.dockIconsPerLine == nil then
        db.dockIconsPerLine = defaults.dockIconsPerLine
    end
    if db.dockIconSpacing == nil then
        db.dockIconSpacing = defaults.dockIconSpacing
    end
    if db.dockDensity == nil then
        db.dockDensity = defaults.dockDensity
    end
    if db.dockTextDirection == nil then
        db.dockTextDirection = defaults.dockTextDirection
    end
    if db.dockTextAlign == nil then
        db.dockTextAlign = defaults.dockTextAlign
    end
    if db.dockLabelAlignMode == nil then
        local direction = string.upper(tostring(db.dockTextDirection or defaults.dockTextDirection or "BOTTOM"))
        if direction ~= "LEFT"
            and direction ~= "TOP"
            and direction ~= "RIGHT"
            and direction ~= "BOTTOM"
            and direction ~= "INNER_TOP"
            and direction ~= "INNER_BOTTOM"
            and direction ~= "CENTER"
        then
            direction = "BOTTOM"
        end
        local align = string.upper(tostring(db.dockTextAlign or defaults.dockTextAlign or "CENTER"))
        if align ~= "LEFT" and align ~= "CENTER" and align ~= "RIGHT" then
            align = "CENTER"
        end
        local alignMode = "CENTER"
        if direction == "LEFT" then
            if align == "RIGHT" then
                alignMode = "HUG"
            elseif align == "LEFT" then
                alignMode = "FLUSH"
            end
        elseif direction == "RIGHT" then
            if align == "LEFT" then
                alignMode = "HUG"
            elseif align == "RIGHT" then
                alignMode = "FLUSH"
            end
        end
        db.dockLabelAlignMode = alignMode
    end
    if db.dockLabelMode == nil then
        if PA_SafeBool(db.dockHideDungeonName) then
            db.dockLabelMode = "OFF"
        else
            db.dockLabelMode = defaults.dockLabelMode or "OUTSIDE"
        end
    end
    if db.dockLabelSide == nil then
        local legacyDirection = string.upper(tostring(db.dockTextDirection or defaults.dockTextDirection or "BOTTOM"))
        if legacyDirection == "TOP" or legacyDirection == "INNER_TOP" then
            db.dockLabelSide = "TOP"
        elseif legacyDirection == "LEFT" then
            db.dockLabelSide = "LEFT"
        elseif legacyDirection == "RIGHT" then
            db.dockLabelSide = "RIGHT"
        else
            db.dockLabelSide = defaults.dockLabelSide or "BOTTOM"
        end
    end
    if db.dockLabelModePersist == nil then
        db.dockLabelModePersist = db.dockLabelMode or defaults.dockLabelMode or "OUTSIDE"
    end
    if db.dockLabelSidePersist == nil then
        db.dockLabelSidePersist = db.dockLabelSide or defaults.dockLabelSide or "BOTTOM"
    end
    if db.dockLabelSideOutsidePersist == nil then
        if db.dockLabelSide == "BOTTOM" or db.dockLabelSide == "TOP" or db.dockLabelSide == "LEFT" or db.dockLabelSide == "RIGHT" then
            db.dockLabelSideOutsidePersist = db.dockLabelSide
        elseif db.dockLabelSidePersist == "BOTTOM" or db.dockLabelSidePersist == "TOP" or db.dockLabelSidePersist == "LEFT" or db.dockLabelSidePersist == "RIGHT" then
            db.dockLabelSideOutsidePersist = db.dockLabelSidePersist
        else
            db.dockLabelSideOutsidePersist = defaults.dockLabelSideOutsidePersist or defaults.dockLabelSide or "BOTTOM"
        end
    end
    if db.dockTextOffset == nil then
        db.dockTextOffset = defaults.dockTextOffset
    end
    if db.dockHoverGlowAlpha == nil then
        db.dockHoverGlowAlpha = defaults.dockHoverGlowAlpha
    end
    if db.dockHoverGlowSize == nil then
        db.dockHoverGlowSize = defaults.dockHoverGlowSize
    end

    if db.dockLayoutMode ~= "MANUAL_GRID" and db.dockLayoutMode ~= "ADAPTIVE_GRID" then
        db.dockLayoutMode = defaults.dockLayoutMode
    end
    if db.dockPreferAxis ~= "AUTO" and db.dockPreferAxis ~= "HORIZONTAL" and db.dockPreferAxis ~= "VERTICAL" then
        db.dockPreferAxis = defaults.dockPreferAxis
    end
    if db.dockSimpleLayoutMode ~= "HORIZONTAL_ROW"
        and db.dockSimpleLayoutMode ~= "VERTICAL_COLUMN"
        and db.dockSimpleLayoutMode ~= "GRID"
    then
        db.dockSimpleLayoutMode = defaults.dockSimpleLayoutMode
    end
    if db.dockTextDirection ~= "LEFT"
        and db.dockTextDirection ~= "TOP"
        and db.dockTextDirection ~= "RIGHT"
        and db.dockTextDirection ~= "BOTTOM"
        and db.dockTextDirection ~= "INNER_TOP"
        and db.dockTextDirection ~= "INNER_BOTTOM"
        and db.dockTextDirection ~= "CENTER"
    then
        db.dockTextDirection = defaults.dockTextDirection
    end
    if db.dockTextAlign ~= "LEFT" and db.dockTextAlign ~= "CENTER" and db.dockTextAlign ~= "RIGHT" then
        db.dockTextAlign = defaults.dockTextAlign
    end
    if db.dockLabelAlignMode ~= "HUG" and db.dockLabelAlignMode ~= "CENTER" and db.dockLabelAlignMode ~= "FLUSH" then
        db.dockLabelAlignMode = defaults.dockLabelAlignMode or "CENTER"
    end
    if db.dockLabelMode == false then
        db.dockLabelMode = "OFF"
    elseif db.dockLabelMode == true then
        db.dockLabelMode = "OUTSIDE"
    end
    if db.dockLabelModePersist == false then
        db.dockLabelModePersist = "OFF"
    elseif db.dockLabelModePersist == true then
        db.dockLabelModePersist = "OUTSIDE"
    end
    if db.dockLabelMode ~= "OFF" and db.dockLabelMode ~= "OUTSIDE" and db.dockLabelMode ~= "INSIDE" then
        db.dockLabelMode = defaults.dockLabelMode or "OUTSIDE"
    end
    -- If either label mode key is OFF, treat portal names as hidden to avoid reload drift.
    if db.dockLabelMode == "OFF" or db.dockLabelModePersist == "OFF" then
        db.dockHideDungeonName = true
    end
    if db.dockLabelModePersist ~= "OFF" and db.dockLabelModePersist ~= "OUTSIDE" and db.dockLabelModePersist ~= "INSIDE" then
        db.dockLabelModePersist = db.dockLabelMode or defaults.dockLabelMode or "OUTSIDE"
    end
    if db.dockLabelSide ~= "BOTTOM" and db.dockLabelSide ~= "TOP" and db.dockLabelSide ~= "LEFT" and db.dockLabelSide ~= "RIGHT" and db.dockLabelSide ~= "CENTER" then
        db.dockLabelSide = defaults.dockLabelSide or "BOTTOM"
    end
    -- Label mode OFF is equivalent to hiding portal names; keep these in sync for deterministic reload behavior.
    if PA_SafeBool(db.dockHideDungeonName) then
        db.dockLabelMode = "OFF"
        db.dockLabelModePersist = "OFF"
    end
    if db.dockLabelSidePersist ~= "BOTTOM" and db.dockLabelSidePersist ~= "TOP" and db.dockLabelSidePersist ~= "LEFT" and db.dockLabelSidePersist ~= "RIGHT" and db.dockLabelSidePersist ~= "CENTER" then
        db.dockLabelSidePersist = db.dockLabelSide or defaults.dockLabelSide or "BOTTOM"
    end
    if db.dockLabelSideOutsidePersist ~= "BOTTOM"
        and db.dockLabelSideOutsidePersist ~= "TOP"
        and db.dockLabelSideOutsidePersist ~= "LEFT"
        and db.dockLabelSideOutsidePersist ~= "RIGHT"
    then
        if db.dockLabelSide == "BOTTOM" or db.dockLabelSide == "TOP" or db.dockLabelSide == "LEFT" or db.dockLabelSide == "RIGHT" then
            db.dockLabelSideOutsidePersist = db.dockLabelSide
        elseif db.dockLabelSidePersist == "BOTTOM" or db.dockLabelSidePersist == "TOP" or db.dockLabelSidePersist == "LEFT" or db.dockLabelSidePersist == "RIGHT" then
            db.dockLabelSideOutsidePersist = db.dockLabelSidePersist
        else
            db.dockLabelSideOutsidePersist = defaults.dockLabelSideOutsidePersist or defaults.dockLabelSide or "BOTTOM"
        end
    end
    -- Keep live and persisted label controls in lockstep so settings visuals cannot drift on reload.
    -- If either key is OFF, OFF wins so user intent cannot be lost by stale mismatch.
    if db.dockLabelMode == "OFF" or db.dockLabelModePersist == "OFF" then
        db.dockLabelMode = "OFF"
        db.dockLabelModePersist = "OFF"
    else
        db.dockLabelModePersist = db.dockLabelMode
    end
    db.dockLabelSidePersist = db.dockLabelSide
    if db.dockGrowthX ~= "RIGHT" and db.dockGrowthX ~= "LEFT" then
        db.dockGrowthX = defaults.dockGrowthX
    end
    if db.dockGrowthY ~= "DOWN" and db.dockGrowthY ~= "UP" then
        db.dockGrowthY = defaults.dockGrowthY
    end
    if db.dockAnchorPoint ~= "TOPLEFT"
        and db.dockAnchorPoint ~= "TOP"
        and db.dockAnchorPoint ~= "TOPRIGHT"
        and db.dockAnchorPoint ~= "LEFT"
        and db.dockAnchorPoint ~= "CENTER"
        and db.dockAnchorPoint ~= "RIGHT"
        and db.dockAnchorPoint ~= "BOTTOMLEFT"
        and db.dockAnchorPoint ~= "BOTTOM"
        and db.dockAnchorPoint ~= "BOTTOMRIGHT"
    then
        db.dockAnchorPoint = defaults.dockAnchorPoint
    end

    db.dockWrapAfter = math.floor(PA_Clamp(db.dockWrapAfter, defaults.dockWrapAfter, 1, 10))
    db.dockIconSize = math.floor(PA_Clamp(db.dockIconSize, defaults.dockIconSize, 16, 64))
    db.dockSpacingX = math.floor(PA_Clamp(db.dockSpacingX, defaults.dockSpacingX, -600, 160))
    db.dockSpacingY = math.floor(PA_Clamp(db.dockSpacingY, defaults.dockSpacingY, -200, 200))
    db.dockPadding = math.floor(PA_Clamp(db.dockPadding, defaults.dockPadding, 0, 40))
    db.dockIconsPerLine = math.floor(PA_Clamp(db.dockIconsPerLine, defaults.dockIconsPerLine, 1, 10))
    db.dockIconSpacing = math.floor(PA_Clamp(db.dockIconSpacing, defaults.dockIconSpacing, 0, 40))
    db.dockDensity = math.floor(PA_Clamp(db.dockDensity, defaults.dockDensity, 0, 100))
    db.dockTextOffset = math.floor(PA_Clamp(db.dockTextOffset, defaults.dockTextOffset, -30, 30))
    db.dockInactiveAlpha = PA_Clamp(db.dockInactiveAlpha, defaults.dockInactiveAlpha, 0.1, 1.0)
    db.dockAnimateDuration = PA_Clamp(db.dockAnimateDuration, defaults.dockAnimateDuration, 0.05, 0.5)
    db.dockHoverGlowAlpha = PA_Clamp(db.dockHoverGlowAlpha, defaults.dockHoverGlowAlpha, 0.0, 1.0)
    db.dockHoverGlowSize = math.floor(PA_Clamp(db.dockHoverGlowSize, defaults.dockHoverGlowSize, 0, 20))
    db.dockCenterAlignment = PA_SafeBool(db.dockCenterAlignment)
    db.dockCompactMode = PA_SafeBool(db.dockCompactMode)
    db.dockTestMode = PA_SafeBool(db.dockTestMode)
    db.dockAnimateReflow = PA_SafeBool(db.dockAnimateReflow)
    db.dockBackdropEnabled = PA_SafeBool(db.dockBackdropEnabled)
    db.dockHoverGlow = PA_SafeBool(db.dockHoverGlow)
    db.dockGizmoMode = PA_SafeBool(db.dockGizmoMode)

    -- Keep legacy fields initialized, but preserve explicit X/Y spacing when set.
    db.dockWrapAfter = db.dockIconsPerLine
    if db.dockSpacingX == nil then
        db.dockSpacingX = db.dockIconSpacing
    end
    if db.dockSpacingY == nil then
        db.dockSpacingY = db.dockIconSpacing
    end

    local function buildDockSeedSlots()
        local slots = {}
        local dungeons = (self.GetDefaultMPlusDockPresetDungeons and self:GetDefaultMPlusDockPresetDungeons()) or {}
        for i = 1, 10 do
            local dungeon = dungeons[i]
            local spellID = dungeon and math.floor(tonumber(dungeon.spellID) or 0) or 0
            if spellID > 0 then
                slots[i] = {
                    enabled = true,
                    selection = tostring(spellID),
                    spellID = spellID,
                    name = tostring(dungeon.dest or ""),
                }
            else
                slots[i] = {
                    enabled = false,
                    selection = "CUSTOM",
                    spellID = 0,
                    name = "",
                }
            end
        end
        return slots
    end

    if type(db.dockSlots) ~= "table" or #db.dockSlots == 0 then
        db.dockSlots = buildDockSeedSlots()
    end
    for i = 1, 10 do
        local slot = db.dockSlots[i]
        if type(slot) ~= "table" then
            slot = {}
            db.dockSlots[i] = slot
        end
        if slot.enabled == nil then
            slot.enabled = false
        end
        if slot.selection == nil or slot.selection == "" then
            if tonumber(slot.spellID) and tonumber(slot.spellID) > 0 then
                slot.selection = tostring(math.floor(tonumber(slot.spellID) or 0))
            else
                slot.selection = "CUSTOM"
            end
        end
        if slot.spellID == nil then
            slot.spellID = 0
        end
        if slot.name == nil then
            slot.name = ""
        end

        slot.enabled = PA_SafeBool(slot.enabled)
        slot.selection = tostring(slot.selection or "CUSTOM")
        slot.spellID = math.floor(tonumber(slot.spellID) or 0)
        if slot.spellID < 0 then
            slot.spellID = 0
        end
        slot.name = tostring(slot.name or "")
    end
    for i = #db.dockSlots, 11, -1 do
        db.dockSlots[i] = nil
    end

    db.modules = db.modules or {}
    db.modules.brezTimer = db.modules.brezTimer or {}

    local brezDefaults = defaults.modules and defaults.modules.brezTimer or {}
    local brezDB = db.modules.brezTimer
    if brezDB.enabled == nil then
        brezDB.enabled = brezDefaults.enabled ~= false
    end
    brezDB.x = math.floor(clampNumber(brezDB.x, brezDefaults.x or 0))
    brezDB.y = math.floor(clampNumber(brezDB.y, brezDefaults.y or 0))

    if trim(brezDB.textFormat) == "" then
        brezDB.textFormat = brezDefaults.textFormat or "{mm}:{ss}"
    end

    if brezDB.timeFormatMode ~= "mmss" and brezDB.timeFormatMode ~= "mmsscc" then
        brezDB.timeFormatMode = brezDefaults.timeFormatMode or "mmss"
    end

    if brezDB.font == nil then
        brezDB.font = brezDefaults.font or ""
    end

    brezDB.fontSize = math.floor(clampNumber(brezDB.fontSize, brezDefaults.fontSize or 14, 8, 48))
    brezDB.color = sanitizeColor(brezDB.color, brezDefaults.color or { r = 1, g = 1, b = 1, a = 1 })

    if trim(brezDB.label1Text) == "" then
        brezDB.label1Text = brezDefaults.label1Text or "Battle Rez"
    end
    if trim(brezDB.label2Text) == "" then
        brezDB.label2Text = brezDefaults.label2Text or "Next In"
    end

    brezDB.label1Font = trim(brezDB.label1Font)
    brezDB.label2Font = trim(brezDB.label2Font)
    brezDB.valueFont = trim(brezDB.valueFont)
    if brezDB.valueFont == "" then
        brezDB.valueFont = brezDB.font or brezDefaults.valueFont or brezDefaults.font or ""
    end

    local fallbackFontSize = brezDB.fontSize or brezDefaults.fontSize or 14
    brezDB.label1FontSize = math.floor(clampNumber(brezDB.label1FontSize, fallbackFontSize, 8, 48))
    brezDB.label2FontSize = math.floor(clampNumber(brezDB.label2FontSize, fallbackFontSize, 8, 48))
    brezDB.valueFontSize = math.floor(clampNumber(brezDB.valueFontSize, fallbackFontSize, 8, 48))

    local legacyValue1 = sanitizeColor(brezDB.value1Color, brezDefaults.value1Color or brezDB.color or { r = 1, g = 1, b = 1, a = 1 })
    local legacyValue2 = sanitizeColor(brezDB.value2Color, brezDefaults.value2Color or brezDB.color or { r = 1, g = 1, b = 1, a = 1 })

    brezDB.label1Color = sanitizeColor(brezDB.label1Color, brezDB.color or brezDefaults.label1Color or { r = 1, g = 1, b = 1, a = 1 })
    brezDB.label2Color = sanitizeColor(brezDB.label2Color, brezDB.color or brezDefaults.label2Color or { r = 1, g = 1, b = 1, a = 1 })
    brezDB.valueColor = sanitizeColor(brezDB.valueColor, legacyValue1 or legacyValue2 or brezDB.color or brezDefaults.valueColor or { r = 1, g = 1, b = 1, a = 1 })

    brezDB.value1Color = sanitizeColor(legacyValue1, brezDB.valueColor)
    brezDB.value2Color = sanitizeColor(legacyValue2, brezDB.valueColor)

    if dockSortModeChanged and self.dockFrame and self.RebuildDock then
        if InCombatLockdown and InCombatLockdown() then
            if self.QueueDockUpdate then
                self:QueueDockUpdate()
            end
        else
            self:RebuildDock(false)
        end
    end

end

function PortalAuthority:GetGlobalFontPath()
    local _, _, fontFlags = GameFontNormal:GetFont()
    local fontPath = trim(STANDARD_TEXT_FONT or "")
    if fontPath == "" then
        fontPath = select(1, GameFontNormal:GetFont())
    end
    if trim(fontPath) == "" then
        fontPath = BUILTIN_FONT_FALLBACK
    end
    return fontPath, fontFlags or ""
end

function PortalAuthority:GetPremiumAuthoredFontPath()
    return PortalAuthority._buildConfig.release.premiumAuthoredFontPath
end

function PortalAuthority:GetPremiumAuthoredFontName()
    return PortalAuthority._buildConfig.release.premiumAuthoredFontName
end

function PortalAuthority:ApplyPremiumAuthoredFont(object, size, flags)
    if not object or not object.SetFont then
        return
    end

    local fontPath = self:GetPremiumAuthoredFontPath()
    local currentSize = select(2, object:GetFont())
    object:SetFont(fontPath, size or currentSize or 12, flags or "")
end

function PortalAuthority:GetMageClassColorCode()
    local color = RAID_CLASS_COLORS and RAID_CLASS_COLORS.MAGE
    if color and color.colorStr then
        return "|c" .. color.colorStr
    end
    return "|cff3fc7eb"
end

function PortalAuthority:ShowLoginMessage()
    local chatFrame = DEFAULT_CHAT_FRAME or ChatFrame1
    if not chatFrame or not chatFrame.AddMessage then
        return
    end

    local addonName = "|cffE88BC7Portal Authority|r"
    chatFrame:AddMessage(string.format("%s %s", addonName, PLAYER_LOGIN_MESSAGE))
end

function PortalAuthority:ShowDockV200MigrationNotice()
    local meta = (self.GetOperationalMeta and self:GetOperationalMeta()) or PortalAuthorityDB
    if not meta or not PA_SafeBool(meta.dockV200NoticePending) then
        return
    end

    meta.dockV200NoticePending = false

    local chatFrame = DEFAULT_CHAT_FRAME or ChatFrame1
    local msg = "|cffE88BC7Portal Authority:|r Mythic+ Dock has been enabled by default in v2.0.0."
    if chatFrame and chatFrame.AddMessage then
        chatFrame:AddMessage(msg)
    else
        print(msg)
    end
end

CoreInternal.trim = trim
CoreInternal.clampNumber = clampNumber
CoreInternal.PA_Clamp = PA_Clamp
CoreInternal.PA_ToNumber = PA_ToNumber
CoreInternal.PA_SafeBool = PA_SafeBool
CoreInternal.PA_CompareSemver = PA_CompareSemver
CoreInternal.PA_IsSemverStringValid = PA_IsSemverStringValid
CoreInternal.PA_GetCurrentAddonVersionState = PA_GetCurrentAddonVersionState
CoreInternal.VALID_OUTLINES = VALID_OUTLINES
CoreInternal.VALID_SOUND_CHANNELS = VALID_SOUND_CHANNELS
CoreInternal.BUILTIN_FONT_FALLBACK = BUILTIN_FONT_FALLBACK
CoreInternal.PLAYER_LOGIN_MESSAGE = PLAYER_LOGIN_MESSAGE
CoreInternal.DOCK_ENABLE_DEFAULTS_VERSION = DOCK_ENABLE_DEFAULTS_VERSION
