-- Modules/Database.lua
-- Database module for managing SavedVariables

local ADDON_NAME, BFL = ...
local DB = BFL:RegisterModule("DB", {})

-- Optimization: Cache CustomNames Lib usage
local CustomNamesLib = nil
local ThemePalette = BFL:GetModule("ThemePalette")

-- Built-in groups are derived from friend state and must never be persisted as
-- custom friend-group assignments. Keep this fallback local so the invariant
-- also holds while the Groups module is still initializing.
local NON_PERSISTED_FRIEND_GROUPS = {
	favorites = true,
	ingame = true,
	recentlyadded = true,
	nogroup = true,
}

-- Default values
local defaults = {
	groupStates = {},
	customGroups = {},
	friendGroups = {},
	showBlizzardOption = false, -- Show "Show Blizzard's Friendlist" in menu
	showWelcomeMessage = true, -- Show the welcome message on login
	groupOrder = nil, -- nil = use default order (favorites, custom alphabetically, nogroup)
	groupColors = {}, -- {groupId: {r, g, b}} - custom colors for group headers
	groupCountColors = {}, -- {groupId: {r, g, b}} - custom colors for group counts (overrides global)
	groupArrowColors = {}, -- {groupId: {r, g, b}} - custom colors for group arrows (overrides global)
	-- Raid Shortcuts
	raidShortcuts = {
		mainTank = { modifier = "SHIFT", button = "RightButton" },
		mainAssist = { modifier = "CTRL", button = "RightButton" },
		lead = { modifier = "ALT", button = "LeftButton" },
		promote = { modifier = "ALT", button = "RightButton" },
	},
	-- Individual Shortcut Toggles
	raidShortcutEnabled_mainTank = true,
	raidShortcutEnabled_mainAssist = true,
	raidShortcutEnabled_lead = true,
	raidShortcutEnabled_promote = true,
	-- Raid Tools Settings
	raidToolsSortMode = "tmrh", -- Sort mode: "tmrh" (Tanks>Melee>Ranged>Healers) or "thmr" (Tanks>Healers>Melee>Ranged)
	raidToolsPreserveGroups = {}, -- Table of preserved group numbers: {[1]=true, [3]=true} = groups 1 and 3 are preserved
	raidToolsResumeAfterCombat = true, -- Auto-resume sorting after leaving combat
	raidToolsBalanceDps = false, -- Balance DPS across groups during split
	enableReadyCheckButton = false, -- Show compact ready check button in the raid header

	nicknames = {}, -- {friendUID: "Nickname"} - custom nicknames for friends
	lastInvitedAccounts = {}, -- {friendUID: gameAccountID} - remembers last invited game account per friend
	preferredGameAccounts = {}, -- {friendUID: gameAccountID} - user-selected preferred game account per friend
	-- Visual Settings
	compactMode = false, -- Use compact button layout
	theme = "blizzard", -- UI theme: "blizzard", "dark", "custom", "elvui"
	darkThemeSettings = ThemePalette and ThemePalette:GetDefaultDarkSettings() or {},
	customThemeSettings = ThemePalette and ThemePalette:GetDefaultCustomSettings() or {},
	customTheme = {},
	enableElvUISkin = false, -- Legacy ElvUI skin toggle shown outside beta theme settings
	fontSize = "normal", -- "small", "normal", "large"
	-- Font Customization (LibSharedMedia)
	fontFriendName = "Friz Quadrata TT",
	fontSizeFriendName = 12,
	fontOutlineFriendName = "SLUG",
	fontShadowFriendName = false,
	fontColorFriendName = { r = 0.510, g = 0.773, b = 1.0, a = 1 }, -- Blizzard BNet Blue {r=0.51, g=0.773, b=1.0}
	fontFriendInfo = "Friz Quadrata TT",
	fontSizeFriendInfo = 10,
	fontOutlineFriendInfo = "SLUG",
	fontShadowFriendInfo = false,
	fontColorFriendInfo = { r = 0.510, g = 0.510, b = 0.510, a = 1 }, -- Blizzard Gray {r=0.51, g=0.51, b=0.51}
	-- Tab Font Customization
	fontTabText = "Friz Quadrata TT",
	fontSizeTabText = 12,
	fontOutlineTabText = "SLUG",
	fontShadowTabText = false,
	fontColorTabText = { r = 1.0, g = 0.82, b = 0.0, a = 1 }, -- Normal/Highlight yellow {r=1.0, g=0.82, b=0}
	-- Raid Name Font Customization
	fontRaidName = "Friz Quadrata TT",
	fontSizeRaidName = 12,
	fontOutlineRaidName = "SLUG",
	fontShadowRaidName = false,
	fontColorRaidName = { r = 1.0, g = 0.82, b = 0.0, a = 1 }, -- Class color override usually applies, but default valid
	-- Group Header Font Customization
	fontGroupHeader = "Friz Quadrata TT",
	fontSizeGroupHeader = 12,
	fontOutlineGroupHeader = "SLUG",
	fontShadowGroupHeader = false,
	colorGroupCount = nil, -- Default: nil (Inherit from Group Color)
	colorGroupArrow = nil, -- Default: nil (Inherit from Group Color)

	colorClassNames = true, -- Color character names by class (default: ON)
	showGameIcon = true, -- Show game icon next to friend name (default: ON)
	simpleMode = false, -- Use simple/compact mode (default: OFF)
	simpleModeShowSearch = true, -- Show search bar in simple mode (default: ON)
	hideEmptyGroups = false, -- Hide groups with no online friends (default: OFF)
	headerCountFormat = "visible", -- Group header count format: "visible", "online", "both" (default: visible)
	groupHeaderAlign = "LEFT", -- Alignment of group header text: "LEFT", "CENTER", "RIGHT" (default: LEFT)
	showGroupArrow = true, -- Show the collapse/expand arrow on group headers (default: ON)
	groupArrowAlign = "LEFT", -- Alignment of group header arrow: "LEFT", "CENTER", "RIGHT" (default: LEFT)
	showFactionIcons = false, -- Show faction icons next to character names (default: OFF)
	showRealmName = false, -- Show realm name for cross-realm friends (default: OFF)
	grayOtherFaction = false, -- Gray out friends from other faction (default: OFF)
	showMobileAsAFK = false, -- Show mobile friends with AFK status icon (default: OFF)
	treatMobileAsOffline = false, -- Treat mobile friends as offline (display in Offline group) (default: OFF)
	nameDisplayFormat = "%name%", -- LEGACY: Migrated to nameFormatPreset/nameFormatCustom
	nameFormatPreset = "default", -- Preset: "default", "battletag", "nickname", "name_nickname", "name_note", "name_battletag", "custom"
	nameFormatCustom = "%name%", -- Custom format string (used when nameFormatPreset == "custom")
	infoFormatPreset = "default", -- Preset: "default", "zone", "level", "class_zone", "level_class_zone", "game", "disabled", "custom"
	infoFormatCustom = "%level%, %zone%", -- Custom info format string (used when infoFormatPreset == "custom")
	enableInGameGroup = false, -- Enable dynamic "In-Game" group (default: OFF)
	inGameGroupMode = "same_game", -- "same_game" (WoW matching project) or "any_game" (Any BNet game)
	windowScale = 1.0, -- Window scale factor: 0.5 = 50%, 1.0 = 100%, 2.0 = 200% (default: 100%)
	hideMaxLevel = false, -- Hide level display for max level characters (default: OFF)
	colorLevelByDifficulty = true, -- Color level text by difficulty (grey/green/yellow/orange/red) (default: ON)
	showNoteIcon = false, -- Show a note icon if the friend has a note (default: OFF)
	accordionGroups = false, -- Only allow one group to be open at a time (default: OFF)
	showFavoritesGroup = true, -- Show the Favorites group (default: ON)
	enableFavoriteIcon = true, -- Show the Favorite icon on the friend button (default: ON)
	favoriteIconStyle = "bfl", -- Favorite icon style: "bfl" or "blizzard"
	showFactionBg = false, -- Show faction color as background (default: OFF)
	showMultiAccountBadge = true, -- Show badge on friends with multiple game accounts online (default: ON)
	showMultiAccountInfo = true, -- Append multi-account details to the info line (default: ON)
	-- Sort Settings
	primarySort = "status", -- Primary sort method: status, name, level, zone (default: status)
	secondarySort = "name", -- Secondary sort method: none, name, level, zone (default: name)
	-- Filter Settings
	quickFilter = "all", -- Quick filter mode: all, online, offline, wowonline, wow, bnet, hideafk, retail, ingame (default: all)
	customQuickFilters = {}, -- User-created QuickFilter definitions (account-wide)
	quickFilterVisibility = {}, -- {filterId: boolean}; nil means visible
	quickFilterOrder = {}, -- Ordered list of built-in and custom QuickFilter IDs
	nextCustomQuickFilterId = 1,
	customSorters = {}, -- User-created sorter definitions (account-wide)
	sorterVisibility = {}, -- {sorterId: boolean}; nil means visible
	sorterOrder = {}, -- Ordered list of built-in and custom sorter IDs
	nextCustomSorterId = 1,
	-- Streamer Mode
	streamerModeActive = false, -- Enable Streamer Mode (default: OFF)
	showStreamerModeButton = true, -- Show Streamer Mode button (default: ON)
	streamerModeHeaderText = "Streamer Mode", -- Text to replace BattleTag with
	streamerModeNameFormat = "battletag", -- Name format in streamer mode: "battletag", "character", "custom" (default: battletag)
	-- Debug Settings
	debugPrintEnabled = false, -- Toggle debug prints with /bfl debug print
	-- Beta Features
	enableBetaFeatures = false, -- Enable experimental Beta features (default: OFF)
	enableSettingsCenterBeta = false, -- Use the LibSettingsDesigner Settings Center beta (default: OFF)
	externalMenuBridgeEnabled = false, -- Add supported addon actions to supported BFL context menus (default: OFF)
	contactMemory = {
		version = 1,
		enabled = false,
		contacts = {},
		tags = {},
		settings = {
			showTooltipSection = true,
			hideInStreamerMode = true,
			nextTagId = 1,
		},
	},
	friendTagsSchemaVersion = 1,
	friendTagSettings = {
		enabled = true,
		showRowChips = true,
		showTooltipChips = true,
		showBrokerChips = true,
		showMenuTagCounts = true,
		showTagsInStreamerMode = false,
		rowMode = "chip_line",
		compactRowMode = "icon_only",
		maxRowChips = 3,
		maxTooltipChips = 8,
		enableDynamicTagGroups = false,
		includeCustomTagsInSearch = true,
		includeBlizzardTagsInSearch = true,
	},
	friendTagProfiles = {},
	customFriendTags = {},
	friendCustomTags = {},
	friendBlizzardTags = {},
	friendTagsLegacyContactMemoryTagMap = {},
	friendTagsLegacyContactMemoryMigrated = false,
	nextCustomFriendTagID = 1,
	autoRaidAssist = {
		version = 1,
		enabled = false,
		targets = {},
	},
	-- Global Sync Settings
	enableGlobalSync = false, -- Enable Global Friend Sync (default: OFF)
	enableGlobalSyncDeletion = false, -- Enable deletion of friends during sync (default: OFF)

	-- Main Frame Edit Mode (Phase EditMode)
	mainFrameSize = {}, -- {[layoutName] = {width, height}} - Main frame size per layout
	mainFramePosition = {}, -- {[layoutName] = {point, x, y}} - Main frame position per layout
	settingsCenterWindow = { width = 1080, height = 700, locked = false, density = "compact" }, -- LibSettingsDesigner window state
	settingsCenterSeenNewTags = {}, -- Host-owned seen state for LibSettingsDesigner new badges
	mainFramePositionMigrated = false, -- Track if old position has been migrated (one-time)
	lockWindow = false, -- Lock the frame to prevent movement
	defaultFrameWidth = 415, -- User-customizable default width (350-800)
	defaultFrameHeight = 570, -- User-customizable default height (400-1200)

	-- Data Broker Settings
	brokerEnabled = false, -- Enable Data Broker integration (default: OFF)
	brokerShowIcon = true, -- Show icon on display addons (default: ON)
	brokerShowLabel = true, -- Show label text (default: ON)
	brokerShowTotal = true, -- Show total count (default: ON)
	brokerShowGroups = false, -- Split counts by WoW/BNet (default: OFF, shows combined)
	brokerTooltipMode = "advanced", -- Tooltip detail level: "basic" or "advanced" (default: advanced)
	brokerClickAction = "toggle", -- Left click action: "toggle", "friends", "settings" (default: toggle)
	brokerShowHints = true, -- Show hint text in LDB tooltip footer (default: ON)
	brokerFont = "Friz Quadrata TT", -- Broker tooltip font face
	brokerFontSize = 12, -- Broker tooltip font size
	brokerFontFlags = "SLUG", -- Broker tooltip font rendering flags
	brokerUseCustomFontForNonLatin = false, -- Use selected broker tooltip font for non-latin alphabets (default: OFF)
	brokerSeparatorColor = { r = 0.3, g = 0.3, b = 0.3, a = 0.5 }, -- Shared Friends/Guild Broker group and footer separator color
	brokerTooltipThemeSettings = {
		blizzard = {},
		dark = {},
		custom = {},
		elvui = {},
	}, -- Per-theme broker tooltip background overrides; nil values inherit theme defaults
	brokerShowColStatus = false, -- Column: Status icon (default: OFF)
	brokerGroupHeaderAlign = "LEFT", -- Friends Broker group header alignment: LEFT/CENTER/RIGHT
	-- Guild Data Broker Settings
	guildBrokerEnabled = false, -- Enable Guild Data Broker plugin (default: OFF)
	guildBrokerShowIcon = true, -- Show guild icon on display addons (default: ON)
	guildBrokerShowLabel = true, -- Show label text (default: ON)
	guildBrokerShowTotal = true, -- Show total member count (default: ON)
	guildBrokerTooltipMode = "advanced", -- Tooltip detail level: "basic" or "advanced" (default: advanced)
	guildBrokerClickAction = "guild_tab", -- Left click action: "guild_tab", "settings" (default: guild_tab)
	guildBrokerShowHints = true, -- Show hint text in tooltip footer (default: ON)
	guildBrokerShowApplicants = true, -- Show pending Guild Finder applicant count when available
	guildBrokerGroupMode = "none", -- Group mode: "none", "by_rank", "by_class" (default: none)
	guildBrokerFilter = "online", -- Default filter: "all", "online" (default: online)
	guildBrokerMaxRows = 100, -- Max rows in tooltip (performance cap, default: 100)
	guildBrokerHideLevelAtMax = false, -- Hide level column for max-level characters (default: OFF)
	guildBrokerShowColNickname = false, -- Column: Nickname (default: OFF)
	guildBrokerShowColName = true, -- Column: Name (default: ON)
	guildBrokerShowColLevel = true, -- Column: Level (default: ON)
	guildBrokerShowColClass = true, -- Column: Class (default: ON)
	guildBrokerShowColProfessions = false, -- Column: Professions (default: OFF)
	guildBrokerShowColRank = true, -- Column: Rank (default: ON)
	guildBrokerShowColZone = true, -- Column: Zone (default: ON)
	guildBrokerShowColNote = false, -- Column: Public Note (default: OFF)
	guildBrokerShowColOfficerNote = false, -- Column: Officer Note (default: OFF, privacy-sensitive)
	guildBrokerShowColLastOnline = true, -- Column: Last Online (default: ON)
	guildBrokerColumnOrder = {}, -- Column order persistence
	guildBrokerCollapsedGroups = {}, -- Collapsed rank/class groups
	guildBrokerSortMode = "name", -- Sort mode: "name", "rank", "level", "class", "zone", "nickname" (default: name)
	guildBrokerExcludeSelf = false, -- Exclude the player from guild list and online count (default: OFF)
	guildBrokerShowClassIcons = false, -- Show class icons in tooltip name column (default: OFF)
	guildBrokerNicknameColor = nil, -- Custom nickname color {r, g, b} (nil = class color)
	guildBrokerRankColor = nil, -- Custom rank column color {r, g, b} (nil = default white)
	guildBrokerZoneColor = nil, -- Custom zone column color {r, g, b} (nil = default white)
	guildBrokerNoteColor = nil, -- Custom note column color {r, g, b} (nil = default white)
	guildBrokerOfficerNoteColor = nil, -- Custom officer note column color {r, g, b} (nil = default white)
	guildNicknames = {}, -- Guild member nicknames {"Name-Realm": "Nickname"}
	-- Guild Tab Settings
	enableGuildTab = false, -- Enable the Guild tab in BetterFriendlist (default: OFF, opt-in)
	guildTabFilterMode = "online", -- Default filter for Guild Tab (default: online)
	guildTabSortMode = "rank", -- Default sort for Guild Tab (default: rank)
	guildTabShowClassIcons = true, -- Display class icons in Guild Tab rows
	guildTabUseNicknames = true, -- Use BetterFriendlist-local guild nicknames in Guild Tab rows
	guildTabShowTabard = false, -- Deprecated/hidden: not part of the Retail-first Guild Tab beta
	guildTabShowILvlColumn = false, -- Deprecated/hidden: not part of the Retail-first Guild Tab beta
	-- Classic Guild Tab Settings (legacy, kept for compat)
	hideGuildTab = false, -- Hide the Guild tab completely (Classic only, default: OFF)
	-- UI Panel System
	useUIPanelSystem = false, -- Use ShowUIPanel/HideUIPanel for automatic repositioning (default: OFF)
	friendListClickWhisperEnabled = false, -- Allow friend row left-click actions to start whispers (default: OFF)
	friendListClickWhisperMode = "double", -- Friend row whisper trigger: "double" or "single" (default: double)
	-- Note Sync
	syncGroupsToNote = false, -- Sync group assignments to friend notes (default: OFF)

	-- Taint-Free Whisper
	taintFreeWhisper = false, -- Use custom EditBox for whispers to avoid tainting Blizzard chat (default: OFF)

	-- Recently Added Group
	enableRecentlyAddedGroup = false, -- Enable "Recently Added" builtin group (default: OFF)
	recentlyAddedDurationUnit = "days", -- Duration unit: "days", "hours", "minutes" (default: days)
	recentlyAddedDurationValue = 7, -- Duration value (default: 7)
	recentlyAddedTimestamps = {}, -- {friendUID: unixTimestamp} - when each friend was first seen
	knownFriendUIDs = {}, -- {friendUID: true} - snapshot of all known friends for diffing

	-- WHO Frame Settings
	whoClassColorNames = true, -- Color WHO result names by class color (default: ON)
	whoShowClassIcons = true, -- Show class icon in WHO result rows (default: ON)
	whoLevelColors = true, -- Color level text by difficulty (default: ON)
	whoZebraStripes = true, -- Alternating row backgrounds in WHO (default: ON)
	whoSearchHistoryEnabled = true, -- Enable WHO search history (default: ON)
	whoSearchHistoryMax = 8, -- Maximum search history entries (default: 8)
	whoDoubleClickAction = "whisper", -- Double-click action: whisper, invite, inspect (default: whisper)
	whoSearchHistory = {}, -- Stored search history entries
	whoSearchBuilderDocked = false, -- Search Builder docked mode (default: OFF, flyout overlay)

	-- Migration tracking
	friendGroupsMigrated = false, -- Track if FriendGroups migration has been completed
	socialKeybindMigrated = false, -- Track if TOGGLESOCIAL was moved to BetterFriendlist's native binding on secret-value clients
	socialKeybindMigrationVersion = nil, -- Last social keybind migration version
	socialKeybindMigratedKeys = {}, -- Keys moved from TOGGLESOCIAL to BETTERFRIENDLIST_TOGGLE
	themeSettingsIndependentDefaultsVersion = 1, -- Tracks independent Theme tab default derivation
	lastChangelogVersion = "0.0.0", -- Last version the user saw the changelog for
	version = BFL.Version,
}

function DB:InternalDeepCopy(orig)
	local orig_type = type(orig)
	local copy
	if orig_type == "table" then
		copy = {}
		for orig_key, orig_value in next, orig, nil do
			copy[self:InternalDeepCopy(orig_key)] = self:InternalDeepCopy(orig_value)
		end
		setmetatable(copy, self:InternalDeepCopy(getmetatable(orig)))
	else -- number, string, boolean, etc
		copy = orig
	end
	return copy
end

-- ========================================
-- Name/Info Format Preset System (Phase 22)
-- ========================================
local NAME_PRESET_FORMATS = {
	["default"] = "%name% (%character%)",
	["battletag"] = "%battletag% (%character%)",
	["battletag_only"] = "%battletag%",
	["nickname"] = "%nickname% (%character%)",
	["character"] = "%character%",
	["name_only"] = "%name%",
}

local INFO_PRESET_FORMATS = {
	-- "default" is handled specially (hardcoded level/zone logic in FriendsList)
	["zone"] = "%zone%",
	["level"] = "%level%",
	["class_zone"] = "%class%, %zone%",
	["level_class_zone"] = "%level% %class%, %zone%",
	["game"] = "%game%",
	-- "disabled" = no info line
	-- "custom" = user-defined
}

local VALID_THEMES = {
	blizzard = true,
	dark = true,
	custom = true,
	elvui = true,
}

local THEME_SETTINGS_INDEPENDENT_DEFAULTS_VERSION = 1

local function GetColorComponent(color, namedKey, indexedKey)
	if type(color) ~= "table" then
		return nil
	end
	return tonumber(color[namedKey] ~= nil and color[namedKey] or color[indexedKey])
end

local function ColorsEqual(left, right)
	local leftR = GetColorComponent(left, "r", 1)
	local leftG = GetColorComponent(left, "g", 2)
	local leftB = GetColorComponent(left, "b", 3)
	local rightR = GetColorComponent(right, "r", 1)
	local rightG = GetColorComponent(right, "g", 2)
	local rightB = GetColorComponent(right, "b", 3)

	return leftR ~= nil
		and leftG ~= nil
		and leftB ~= nil
		and rightR ~= nil
		and rightG ~= nil
		and rightB ~= nil
		and math.abs(leftR - rightR) < 0.0001
		and math.abs(leftG - rightG) < 0.0001
		and math.abs(leftB - rightB) < 0.0001
end

-- Resolve the name format preset to a format string
function DB:GetNameFormatString()
	local preset = self:Get("nameFormatPreset", "default")
	if preset == "custom" then
		return self:Get("nameFormatCustom", "%name%")
	end
	return NAME_PRESET_FORMATS[preset] or "%name%"
end

-- Resolve the info format preset to a format string (or nil for "default"/"disabled")
-- Returns: formatString, isDisabled, isDefault
function DB:GetInfoFormatString()
	local preset = self:Get("infoFormatPreset", "default")
	if preset == "disabled" then
		return nil, true, false
	end
	if preset == "default" then
		return nil, false, true
	end
	if preset == "custom" then
		return self:Get("infoFormatCustom", "%level%, %zone%"), false, false
	end
	return INFO_PRESET_FORMATS[preset] or nil, false, (INFO_PRESET_FORMATS[preset] == nil)
end

-- Check if info line is disabled
function DB:IsInfoDisabled()
	return self:Get("infoFormatPreset", "default") == "disabled"
end

function DB:NormalizeThemeSetting()
	if not BetterFriendlistDB then
		return
	end

	if BetterFriendlistDB.theme == nil or (BetterFriendlistDB.theme == "blizzard" and BetterFriendlistDB.enableElvUISkin == true) then
		BetterFriendlistDB.theme = BetterFriendlistDB.enableElvUISkin == true and "elvui" or "blizzard"
	end

	if not VALID_THEMES[BetterFriendlistDB.theme] then
		BetterFriendlistDB.theme = "blizzard"
	end

	-- Dark and Custom themes are standard features now.
	-- Keep stored selections intact regardless of enableBetaFeatures.
	-- Legacy ElvUI preference migration is handled above.
	-- Do not normalize non-ElvUI themes based on Beta Features.
	-- Runtime fallback still handles unavailable addon-backed themes.
	-- Per-theme palette data is normalized separately below.
	if BetterFriendlistDB.theme == "elvui" then
		BetterFriendlistDB.enableElvUISkin = true
	elseif BetterFriendlistDB.theme == "dark" or BetterFriendlistDB.theme == "custom" then
		BetterFriendlistDB.enableElvUISkin = false
	elseif BetterFriendlistDB.enableElvUISkin == nil then
		BetterFriendlistDB.enableElvUISkin = false
	end
end

function DB:NormalizeThemeSettings()
	local Palette = BFL:GetModule("ThemePalette")
	if Palette and Palette.NormalizeSavedSettings then
		Palette:NormalizeSavedSettings(BetterFriendlistDB)
		return
	end

	if type(BetterFriendlistDB.darkThemeSettings) ~= "table" then
		BetterFriendlistDB.darkThemeSettings = {}
	end
	if type(BetterFriendlistDB.customThemeSettings) ~= "table" then
		BetterFriendlistDB.customThemeSettings = {}
	end
	BetterFriendlistDB.blizzardThemeSettings = nil
	if type(BetterFriendlistDB.customTheme) ~= "table" then
		BetterFriendlistDB.customTheme = {}
	end
	if type(BetterFriendlistDB.brokerSeparatorColor) ~= "table" then
		BetterFriendlistDB.brokerSeparatorColor = self:InternalDeepCopy(defaults.brokerSeparatorColor)
	end
	if type(BetterFriendlistDB.brokerTooltipThemeSettings) ~= "table" then
		BetterFriendlistDB.brokerTooltipThemeSettings = self:InternalDeepCopy(defaults.brokerTooltipThemeSettings)
	else
		for _, theme in ipairs({ "blizzard", "dark", "custom", "elvui" }) do
			if type(BetterFriendlistDB.brokerTooltipThemeSettings[theme]) ~= "table" then
				BetterFriendlistDB.brokerTooltipThemeSettings[theme] = {}
			end
		end
		for theme in pairs(BetterFriendlistDB.brokerTooltipThemeSettings) do
			if theme ~= "blizzard" and theme ~= "dark" and theme ~= "custom" and theme ~= "elvui" then
				BetterFriendlistDB.brokerTooltipThemeSettings[theme] = nil
			end
		end
	end
end

function DB:Initialize()
	-- Initialize Settings Version Counter (Optimization)
	if not BFL.SettingsVersion then
		BFL.SettingsVersion = 1
	end

	-- Initialize Nickname Cache Version (Phase 6 Optimization)
	if not BFL.NicknameCacheVersion then
		BFL.NicknameCacheVersion = 1
	end

	-- Initialize SavedVariables
	if not BetterFriendlistDB then
		BetterFriendlistDB = {}
	end

	-- The short-lived Appear Offline Broker action moved into the existing
	-- Battle.net status dropdown. Keep old profiles from retaining a dead click action.
	if BetterFriendlistDB.brokerClickAction == "appear_offline" then
		BetterFriendlistDB.brokerClickAction = "toggle"
	end

	self:NormalizeThemeSetting()

	-- MIGRATION: Move the temporary global Slug toggle into per-font flag settings.
	-- Also enables Slug by default for existing profiles while preserving any outline flags.
	if not BetterFriendlistDB.fontFlagsMigratedToPerFont then
		local fontFlagKeys = {
			"fontOutlineFriendName",
			"fontOutlineFriendInfo",
			"fontOutlineTabText",
			"fontOutlineRaidName",
			"fontOutlineGroupHeader",
			"brokerFontFlags",
		}
		local useSlug = BetterFriendlistDB.fontUseSlugRendering
		if useSlug == nil then
			useSlug = true
		end

		for _, key in ipairs(fontFlagKeys) do
			local flags = BetterFriendlistDB[key]
			if useSlug then
				flags = BFL.FontManager and BFL.FontManager.AddFontFlag and BFL.FontManager:AddFontFlag(flags, "SLUG")
					or "SLUG"
			elseif BFL.FontManager and BFL.FontManager.RemoveFontFlag then
				flags = BFL.FontManager:RemoveFontFlag(flags, "SLUG")
			end
			if flags ~= nil then
				BetterFriendlistDB[key] = flags
			end
		end

		BetterFriendlistDB.fontUseSlugRendering = nil
		BetterFriendlistDB.fontFlagsMigratedToPerFont = true
	end

	-- MIGRATION (BEFORE defaults): Migrate nameDisplayFormat to preset system
	-- Runs whenever nameFormatPreset is missing but old nameDisplayFormat exists.
	-- NOT a one-time flag - if the user somehow loses nameFormatPreset, it re-derives from the old value.
	if not BetterFriendlistDB.nameFormatPreset and BetterFriendlistDB.nameDisplayFormat then
		local oldFormat = BetterFriendlistDB.nameDisplayFormat
		local oldLower = oldFormat:lower()

		-- Map known format strings to presets (case-insensitive)
		local presetMap = {
			["%name%"] = "default",
			["%battletag%"] = "battletag",
			["%nickname%"] = "nickname",
			["%name% (%character%)"] = "default",
			["%character%"] = "character",
		}

		local matchedPreset = presetMap[oldLower]
		if matchedPreset then
			BetterFriendlistDB.nameFormatPreset = matchedPreset
			BetterFriendlistDB.nameFormatCustom = oldFormat
		else
			-- Non-standard format -> Custom preset preserving user's input
			-- Append " (%character%)" because pre-2.3.6 the character name was always appended automatically
			BetterFriendlistDB.nameFormatPreset = "custom"
			BetterFriendlistDB.nameFormatCustom = oldFormat .. " (%character%)"
		end
	end

	-- MIGRATION: Fix removed preset keys from earlier versions -> map to "custom" or "default"
	local currentPreset = BetterFriendlistDB.nameFormatPreset
	if currentPreset == "name_nickname" then
		BetterFriendlistDB.nameFormatPreset = "custom"
		BetterFriendlistDB.nameFormatCustom = "%name% (%nickname%)"
	elseif currentPreset == "name_note" then
		BetterFriendlistDB.nameFormatPreset = "custom"
		BetterFriendlistDB.nameFormatCustom = "%name% (%note%)"
	elseif currentPreset == "name_battletag" then
		BetterFriendlistDB.nameFormatPreset = "custom"
		BetterFriendlistDB.nameFormatCustom = "%name% (%battletag%)"
	elseif currentPreset == "name_character" then
		BetterFriendlistDB.nameFormatPreset = "default"
	end

	-- MIGRATION: Custom theme controls are independent, but inspired by Dark.
	-- If a pre-release build copied Dark accent 1:1, replace only that copied accent.
	local themeDefaultMigrationVersion = BetterFriendlistDB.themeSettingsIndependentDefaultsVersion or 0
	local darkSettings = type(BetterFriendlistDB.darkThemeSettings) == "table" and BetterFriendlistDB.darkThemeSettings or nil
	local Palette = BFL:GetModule("ThemePalette") or ThemePalette
	local inspiredCustom = Palette
		and Palette.GetCustomSettingsInspiredByDark
		and Palette:GetCustomSettingsInspiredByDark(darkSettings)

	if darkSettings and inspiredCustom then
		if BetterFriendlistDB.customThemeSettings == nil then
			BetterFriendlistDB.customThemeSettings = self:InternalDeepCopy(inspiredCustom)
		elseif
			themeDefaultMigrationVersion < THEME_SETTINGS_INDEPENDENT_DEFAULTS_VERSION
			and ColorsEqual(BetterFriendlistDB.customThemeSettings.accentColor, darkSettings.accentColor)
		then
			BetterFriendlistDB.customThemeSettings.accentColor = self:InternalDeepCopy(inspiredCustom.accentColor)
		end
	end
	BetterFriendlistDB.blizzardThemeSettings = nil
	if themeDefaultMigrationVersion < THEME_SETTINGS_INDEPENDENT_DEFAULTS_VERSION then
		BetterFriendlistDB.themeSettingsIndependentDefaultsVersion = THEME_SETTINGS_INDEPENDENT_DEFAULTS_VERSION
	end

	-- Apply defaults
	for key, value in pairs(defaults) do
		if BetterFriendlistDB[key] == nil then
			if type(value) == "table" then
				BetterFriendlistDB[key] = self:InternalDeepCopy(value)
			else
				BetterFriendlistDB[key] = value
			end
		end
	end
	self:NormalizeThemeSettings()
	self:NormalizeThemeSetting()

	-- MIGRATION: Merge defaults for raidShortcuts (Fix for missing keys in existing DB)
	if BetterFriendlistDB.raidShortcuts then
		for key, val in pairs(defaults.raidShortcuts) do
			if not BetterFriendlistDB.raidShortcuts[key] then
				BetterFriendlistDB.raidShortcuts[key] = self:InternalDeepCopy(val)
			end
		end
	end

	-- MIGRATION: Fix Color Tables initialized as empty tables (Bug fix Phase 20)
	-- Check if color settings are empty tables and reset to defaults
	local colorKeys = { "fontColorFriendName", "fontColorFriendInfo" }
	for _, key in ipairs(colorKeys) do
		local dbVal = BetterFriendlistDB[key]
		if type(dbVal) == "table" and (dbVal.r == nil or dbVal.g == nil or dbVal.b == nil) then
			-- Reset to default if any color component is missing
			if defaults[key] then
				BetterFriendlistDB[key] = self:InternalDeepCopy(defaults[key])
				-- BFL:DebugPrint("Database: Fixed corrupted color table for " .. key)
			end
		end
	end

	-- MIGRATION: Fix Aliased Color Tables (Phase 21)
	-- Ensure color tables are not the same table reference (fixes inheritance bug)
	if
		BetterFriendlistDB.groupCountColors
		and BetterFriendlistDB.groupArrowColors
		and BetterFriendlistDB.groupCountColors == BetterFriendlistDB.groupArrowColors
	then
		BetterFriendlistDB.groupArrowColors = self:InternalDeepCopy(BetterFriendlistDB.groupCountColors)
	end

	-- MIGRATION: Name Display Format (Phase 15 -> Phase 22 Preset System)
	-- Step 1: Convert ancient boolean flags to nameDisplayFormat string (legacy path)
	if
		BetterFriendlistDB.showNotesAsName ~= nil
		or BetterFriendlistDB.showNicknameAsName ~= nil
		or BetterFriendlistDB.showNicknameInName ~= nil
	then
		local showNotes = BetterFriendlistDB.showNotesAsName
		local showNick = BetterFriendlistDB.showNicknameAsName
		local showNickInName = BetterFriendlistDB.showNicknameInName

		local format = "%name%"

		if showNick then
			format = "%nickname%"
		elseif showNotes then
			format = "%note%"
		end

		if showNickInName then
			if format ~= "%nickname%" then
				format = format .. " (%nickname%)"
			end
		end

		BetterFriendlistDB.nameDisplayFormat = format

		-- Remove old keys
		BetterFriendlistDB.showNotesAsName = nil
		BetterFriendlistDB.showNicknameAsName = nil
		BetterFriendlistDB.showNicknameInName = nil
	end

	-- Step 2: nameDisplayFormat migration moved to pre-defaults block above

	-- Migration: Ensure defaultFrameWidth meets new minimum (380px)
	if BetterFriendlistDB.defaultFrameWidth and BetterFriendlistDB.defaultFrameWidth < 380 then
		BetterFriendlistDB.defaultFrameWidth = 380
	end

	-- Migration: Remove deprecated "activity" sort mode (ActivityTracker removed)
	if BetterFriendlistDB.primarySort == "activity" then
		BetterFriendlistDB.primarySort = "status"
	end
	if BetterFriendlistDB.secondarySort == "activity" then
		BetterFriendlistDB.secondarySort = "name"
	end
	-- Clean up orphaned friendActivity data
	BetterFriendlistDB.friendActivity = nil

	-- Migration: raidToolsSortMode "meter" -> raidToolsBalanceDps
	if BetterFriendlistDB.raidToolsSortMode == "meter" then
		BetterFriendlistDB.raidToolsSortMode = "tmrh"
		BetterFriendlistDB.raidToolsBalanceDps = true
	end

	-- Migration: raidToolsGroupOffset (number) -> raidToolsPreserveGroups (table)
	if BetterFriendlistDB.raidToolsGroupOffset ~= nil then
		local offset = BetterFriendlistDB.raidToolsGroupOffset
		local preserved = {}
		for i = 1, offset do
			preserved[i] = true
		end
		BetterFriendlistDB.raidToolsPreserveGroups = preserved
		BetterFriendlistDB.raidToolsGroupOffset = nil
	end

	-- Version migration if needed
	if BetterFriendlistDB.version ~= BFL.VERSION then
		self:MigrateData(BetterFriendlistDB.version, BFL.VERSION)
		BetterFriendlistDB.version = BFL.VERSION
	end

	-- Initialize CustomNames Sync if available
	CustomNamesLib = LibStub and LibStub("CustomNames", true)
	local lib = CustomNamesLib

	if lib then
		-- BFL:DebugPrint("Database: CustomNames Library DETECTED and Sync enabled.")

		-- Push existing BFL nicknames to Library (One-way sync on load)
		if BetterFriendlistDB.nicknames then
			for uid, nickname in pairs(BetterFriendlistDB.nicknames) do
				local libKey = self:GetLibKey(uid)
				-- Only push if we have a valid lib key (not a raw bnet_ID that failed to resolve)
				if libKey and nickname and not libKey:match("^bnet_") then
					local libName = lib.Get(libKey)
					-- Only push if Lib doesn't have a custom name yet (returns original name)
					if libName == libKey then
						lib.Set(libKey, nickname)
						-- BFL:DebugPrint("Database: Synced " .. libKey .. " to CustomNames Lib")
					end
				end
			end
		end

		-- Push existing guild nicknames to Library (One-way sync on load)
		if BetterFriendlistDB.guildNicknames then
			for fullName, nickname in pairs(BetterFriendlistDB.guildNicknames) do
				if fullName and nickname then
					local libName = lib.Get(fullName)
					if libName == fullName then
						lib.Set(fullName, nickname)
					end
				end
			end
		end

		-- Register callbacks to keep internal DB in sync
		lib.RegisterCallback(self, "Name_Added", function(_, name, customName)
			self:SetNicknameInternal(name, customName)
		end)
		lib.RegisterCallback(self, "Name_Update", function(_, name, customName)
			self:SetNicknameInternal(name, customName)
		end)
		lib.RegisterCallback(self, "Name_Removed", function(_, name)
			self:SetNicknameInternal(name, nil)
		end)
	else
		-- BFL:DebugPrint("Database: CustomNames Library NOT detected.")
	end
end

function DB:GetLibKey(friendUID)
	if not friendUID then
		return nil
	end

	if friendUID:match("^wow_") then
		-- WoW: Strip "wow_" prefix for Lib (Name-Realm)
		return friendUID:gsub("^wow_", "")
	elseif friendUID:match("^bnet_%d+") then
		-- BNet ID: Try to resolve to BattleTag for Lib
		local bnetID = tonumber(friendUID:match("^bnet_(%d+)"))
		if bnetID then
			local accountInfo = C_BattleNet.GetAccountInfoByID(bnetID)
			if accountInfo and accountInfo.battleTag then
				return accountInfo.battleTag
			end
		end
		-- CRITICAL: If resolution fails, return nil (not the ID!)
		-- This prevents storing nicknames under inconsistent keys
		return nil
	elseif friendUID:match("^bnet_") then
		-- BNet Tag: Strip "bnet_" prefix
		return friendUID:gsub("^bnet_", "")
	elseif friendUID:match("#") then
		-- Already a BattleTag
		return friendUID
	end

	-- Fallback: Assume it's a WoW name without prefix (shouldn't happen with BFL UIDs but safe to return)
	return friendUID
end

-- Internal setter for DB only (used by Sync and SetNickname)
function DB:SetNicknameInternalDB(key, nickname)
	if not key then
		return
	end
	if not BetterFriendlistDB.nicknames then
		BetterFriendlistDB.nicknames = {}
	end

	if nickname and nickname ~= "" then
		BetterFriendlistDB.nicknames[key] = nickname
	else
		BetterFriendlistDB.nicknames[key] = nil
	end

	-- Invalidate Nickname Cache on change (Phase 6)
	if BFL.NicknameCacheVersion then
		BFL.NicknameCacheVersion = BFL.NicknameCacheVersion + 1
	end
end

-- Callback handler for CustomNames Sync
function DB:SetNicknameInternal(name, nickname)
	if not name then
		return
	end
	-- BFL:DebugPrint("DB:SetNicknameInternal (Sync) for " .. tostring(name) .. " to " .. tostring(nickname))

	-- Convert Lib Name back to DB Key
	local dbKey = name
	if not name:match("#") then
		-- It's a WoW Name (Name-Realm) -> Prepend "wow_" for BFL DB
		dbKey = "wow_" .. name
	else
		-- It's a BattleTag -> Use as is (matches our BNet storage strategy)
		dbKey = name
	end

	self:SetNicknameInternalDB(dbKey, nickname)

	-- Also update guild nicknames if this is a WoW character name (Name-Realm)
	if not name:match("#") and name:match("%-") then
		if not BetterFriendlistDB.guildNicknames then
			BetterFriendlistDB.guildNicknames = {}
		end
		if nickname and nickname ~= "" then
			BetterFriendlistDB.guildNicknames[name] = nickname
		else
			BetterFriendlistDB.guildNicknames[name] = nil
		end
	end

	-- Force UI Refresh to show new nickname immediately
	if BFL and BFL.ForceRefreshFriendsList then
		BFL:ForceRefreshFriendsList()
	end
end

function DB:MigrateData(oldVersion, newVersion)
	-- Future: Add data migration logic here
	-- print("Migrating data from", oldVersion or "unknown", "to", newVersion)
end

-- Get a value from the database
function DB:ResetFontColors()
	-- Reset font colors to Blizzard defaults
	if defaults.fontColorFriendName then
		BetterFriendlistDB.fontColorFriendName = self:InternalDeepCopy(defaults.fontColorFriendName)
	end
	if defaults.fontColorFriendInfo then
		BetterFriendlistDB.fontColorFriendInfo = self:InternalDeepCopy(defaults.fontColorFriendInfo)
	end

	-- Force full display refresh
	if BFL and BFL.ForceRefreshFriendsList then
		BFL:ForceRefreshFriendsList()
	end

	BFL:DebugPrint("Database: Font colors reset to Blizzard defaults.")
end

function DB:Get(key, default)
	-- If no key provided, return entire database
	if key == nil then
		return BetterFriendlistDB
	end

	if type(BetterFriendlistDB) ~= "table" then
		return default
	end

	if BetterFriendlistDB[key] ~= nil then
		return BetterFriendlistDB[key]
	end
	return default
end

-- Set a value in the database
function DB:Set(key, value)
	-- Optimization: Increment version counter to invalidate render caches
	if BFL.SettingsVersion then
		BFL.SettingsVersion = BFL.SettingsVersion + 1
	end
	BetterFriendlistDB[key] = value

	-- PERFY FIX: Immediately update FriendsList settings cache to prevent stale data
	-- This ensures UI responds instantly to setting changes without delay/flicker
	local FriendsList = BFL and BFL:GetModule("FriendsList")
	if FriendsList and FriendsList.InvalidateSettingsCache then
		FriendsList:InvalidateSettingsCache()
	end

	-- NoteSync: Re-sync all friend notes when group order changes
	if key == "groupOrder" then
		local NoteSync = BFL:GetModule("NoteSync")
		if NoteSync then
			NoteSync:OnGroupOrderChanged()
		end
	end
end

function DB:GetLastInvitedAccount(friendUID)
	if not friendUID then
		return nil
	end

	local map = BetterFriendlistDB.lastInvitedAccounts
	if not map then
		return nil
	end

	return map[friendUID]
end

function DB:SetLastInvitedAccount(friendUID, gameAccountID)
	if not friendUID or not gameAccountID then
		return
	end

	if not BetterFriendlistDB.lastInvitedAccounts then
		BetterFriendlistDB.lastInvitedAccounts = {}
	end

	BetterFriendlistDB.lastInvitedAccounts[friendUID] = gameAccountID
end

-- Preferred Game Account: user-selected account override for multi-account BNet friends
function DB:GetPreferredGameAccount(friendUID)
	if not friendUID then
		return nil
	end
	local map = BetterFriendlistDB.preferredGameAccounts
	if not map then
		return nil
	end
	return map[friendUID]
end

function DB:SetPreferredGameAccount(friendUID, gameAccountID)
	if not friendUID then
		return
	end
	if not BetterFriendlistDB.preferredGameAccounts then
		BetterFriendlistDB.preferredGameAccounts = {}
	end
	BetterFriendlistDB.preferredGameAccounts[friendUID] = gameAccountID
	-- Bump settings version so display refreshes
	if BFL.SettingsVersion then
		BFL.SettingsVersion = BFL.SettingsVersion + 1
	end
end

function DB:ClearPreferredGameAccount(friendUID)
	if not friendUID then
		return
	end
	local map = BetterFriendlistDB.preferredGameAccounts
	if not map then
		return
	end
	map[friendUID] = nil
	if BFL.SettingsVersion then
		BFL.SettingsVersion = BFL.SettingsVersion + 1
	end
end

-- Get group state (collapsed/expanded)
function DB:GetGroupState(groupId)
	return BetterFriendlistDB.groupStates[groupId]
end

-- Set group state
function DB:SetGroupState(groupId, collapsed)
	BetterFriendlistDB.groupStates[groupId] = collapsed
end

-- Get custom groups
function DB:GetCustomGroups()
	return BetterFriendlistDB.customGroups
end

-- Get custom group info
function DB:GetCustomGroup(groupId)
	return BetterFriendlistDB.customGroups[groupId]
end

-- Save custom group
function DB:SaveCustomGroup(groupId, groupInfo)
	BetterFriendlistDB.customGroups[groupId] = groupInfo
end

-- Delete custom group
function DB:DeleteCustomGroup(groupId)
	BetterFriendlistDB.customGroups[groupId] = nil
end

-- Get friend's groups
function DB:GetFriendGroups(friendUID)
	-- If friendUID is provided, return groups for that specific friend
	if friendUID then
		return BetterFriendlistDB.friendGroups[friendUID] or {}
	end
	-- If no friendUID provided, return ALL friendGroups mappings
	return BetterFriendlistDB.friendGroups or {}
end

-- Set friend's groups
function DB:SetFriendGroups(friendUID, groups)
	local sanitizedGroups = {}
	local seenGroups = {}
	for _, groupId in ipairs(groups or {}) do
		if
			type(groupId) == "string"
			and not NON_PERSISTED_FRIEND_GROUPS[groupId]
			and not seenGroups[groupId]
		then
			table.insert(sanitizedGroups, groupId)
			seenGroups[groupId] = true
		end
	end

	if #sanitizedGroups == 0 then
		BetterFriendlistDB.friendGroups[friendUID] = nil
	else
		BetterFriendlistDB.friendGroups[friendUID] = sanitizedGroups
	end

	-- NoteSync: Sync group change to friend note
	local NoteSync = BFL:GetModule("NoteSync")
	if NoteSync then
		NoteSync:OnGroupChanged(friendUID)
	end
end

-- Add friend to group
function DB:AddFriendToGroup(friendUID, groupId)
	if not friendUID or type(groupId) ~= "string" or NON_PERSISTED_FRIEND_GROUPS[groupId] then
		return false
	end

	if not BetterFriendlistDB.friendGroups[friendUID] then
		BetterFriendlistDB.friendGroups[friendUID] = {}
	end

	-- Check if already in group
	for _, gid in ipairs(BetterFriendlistDB.friendGroups[friendUID]) do
		if gid == groupId then
			return false -- Already in group
		end
	end

	table.insert(BetterFriendlistDB.friendGroups[friendUID], groupId)

	-- Optimization: Increment version counter
	if BFL.SettingsVersion then
		BFL.SettingsVersion = BFL.SettingsVersion + 1
	end
	-- Invalidate caches
	local FriendsList = BFL:GetModule("FriendsList")
	if FriendsList and FriendsList.InvalidateSettingsCache then
		FriendsList:InvalidateSettingsCache()
	end

	-- NoteSync: Sync group change to friend note
	local NoteSync = BFL:GetModule("NoteSync")
	if NoteSync then
		NoteSync:OnGroupChanged(friendUID)
	end

	return true
end

-- Remove friend from group
function DB:RemoveFriendFromGroup(friendUID, groupId)
	if not BetterFriendlistDB.friendGroups[friendUID] then
		return false
	end

	for i = #BetterFriendlistDB.friendGroups[friendUID], 1, -1 do
		if BetterFriendlistDB.friendGroups[friendUID][i] == groupId then
			table.remove(BetterFriendlistDB.friendGroups[friendUID], i)

			-- Clean up if no groups left
			if #BetterFriendlistDB.friendGroups[friendUID] == 0 then
				BetterFriendlistDB.friendGroups[friendUID] = nil
			end

			-- Optimization: Increment version counter
			if BFL.SettingsVersion then
				BFL.SettingsVersion = BFL.SettingsVersion + 1
			end
			-- Invalidate caches
			local FriendsList = BFL:GetModule("FriendsList")
			if FriendsList and FriendsList.InvalidateSettingsCache then
				FriendsList:InvalidateSettingsCache()
			end

			-- NoteSync: Sync group change to friend note
			local NoteSync = BFL:GetModule("NoteSync")
			if NoteSync then
				NoteSync:OnGroupChanged(friendUID)
			end

			return true
		end
	end

	return false
end

-- Check if friend is in group
function DB:IsFriendInGroup(friendUID, groupId)
	if not BetterFriendlistDB.friendGroups[friendUID] then
		return false
	end

	for _, gid in ipairs(BetterFriendlistDB.friendGroups[friendUID]) do
		if gid == groupId then
			return true
		end
	end

	return false
end

-- Get all friends in database
function DB:GetAllFriendUIDs()
	local uids = {}
	for uid in pairs(BetterFriendlistDB.friendGroups) do
		table.insert(uids, uid)
	end
	return uids
end

-- Get nickname
function DB:GetNickname(friendUID)
	if not friendUID then
		return nil
	end

	local libKey = self:GetLibKey(friendUID)

	-- 1. Try CustomNames Lib (Optimized Cache)
	if CustomNamesLib and libKey then
		local customName = CustomNamesLib.Get(libKey)
		if customName and customName ~= libKey then
			return customName
		end
	end

	-- 2. Fallback to Internal DB
	-- For WoW friends, we use the original UID (wow_Name-Realm) as key
	-- For BNet friends, we prefer BattleTag (libKey) but fallback to friendUID for legacy support

	local dbKey = friendUID
	if friendUID:match("^bnet_") and libKey then
		dbKey = libKey -- Use BattleTag for BNet storage
	end

	local nickname = BetterFriendlistDB.nicknames and BetterFriendlistDB.nicknames[dbKey]

	-- FALLBACK: If not found with primary key and this is a BNet friend, try friendUID
	-- (for backwards compatibility with nicknames stored under "bnet_12345" format)
	if not nickname and friendUID:match("^bnet_") and dbKey ~= friendUID then
		nickname = BetterFriendlistDB.nicknames and BetterFriendlistDB.nicknames[friendUID]
	end

	return nickname
end

-- Set nickname
function DB:SetNickname(friendUID, nickname)
	if not friendUID then
		return
	end

	local libKey = self:GetLibKey(friendUID)

	-- 1. Update CustomNames Lib
	if CustomNamesLib and libKey then
		if nickname and nickname ~= "" then
			CustomNamesLib.Set(libKey, nickname)
		else
			CustomNamesLib.Set(libKey, nil)
		end
	end

	-- 2. Update Internal DB
	-- Determine DB Key (same logic as GetNickname)
	local dbKey = friendUID
	if friendUID:match("^bnet_") and libKey then
		dbKey = libKey
	end

	-- CLEANUP: If this is a BNet friend and we're using libKey as dbKey,
	-- remove any legacy entry under friendUID to avoid duplicates
	if friendUID:match("^bnet_") and libKey and dbKey ~= friendUID then
		if BetterFriendlistDB.nicknames and BetterFriendlistDB.nicknames[friendUID] then
			BetterFriendlistDB.nicknames[friendUID] = nil
		end
	end

	self:SetNicknameInternalDB(dbKey, nickname)
end

-- Get guild member nickname (keyed by "Name-Realm")
function DB:GetGuildNickname(fullName)
	if not fullName then
		return nil
	end

	-- 1. Try CustomNames Lib first
	if CustomNamesLib then
		local customName = CustomNamesLib.Get(fullName)
		if customName and customName ~= fullName then
			return customName
		end
	end

	-- 2. Fallback to internal DB
	return BetterFriendlistDB.guildNicknames and BetterFriendlistDB.guildNicknames[fullName]
end

-- Set guild member nickname (keyed by "Name-Realm")
function DB:SetGuildNickname(fullName, nickname)
	if not fullName then
		return
	end

	-- 1. Update CustomNames Lib
	if CustomNamesLib then
		if nickname and nickname ~= "" then
			CustomNamesLib.Set(fullName, nickname)
		else
			CustomNamesLib.Set(fullName, nil)
		end
	end

	-- 2. Update internal DB
	if not BetterFriendlistDB.guildNicknames then
		BetterFriendlistDB.guildNicknames = {}
	end
	if nickname and nickname ~= "" then
		BetterFriendlistDB.guildNicknames[fullName] = nickname
	else
		BetterFriendlistDB.guildNicknames[fullName] = nil
	end
end
