-- 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

-- Default values
local defaults = {
	groupStates = {},
	customGroups = {},
	friendGroups = {},
	showBlizzardOption = false, -- Show "Show Blizzard's Friendlist" in menu
	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

	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
	enableElvUISkin = false, -- Enable ElvUI Skin (default: OFF)
	fontSize = "normal", -- "small", "normal", "large"
	-- Font Customization (LibSharedMedia)
	fontFriendName = "Friz Quadrata TT",
	fontSizeFriendName = 12,
	fontOutlineFriendName = "NONE",
	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 = "NONE",
	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 = "NONE",
	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 = "NONE",
	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 = "NONE",
	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, wow, bnet, hideafk, retail, ingame (default: all)
	-- 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)
	-- 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
	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)
	-- Classic Guild Tab Settings
	closeOnGuildTabClick = false, -- Close BetterFriendlist when opening Guild Frame (Classic only, default: OFF)
	hideGuildTab = false, -- Hide the Guild tab completely (Classic only, default: OFF)
	-- UI Panel System
	useUIPanelSystem = false, -- Use ShowUIPanel/HideUIPanel for automatic repositioning (default: OFF)
	-- Note Sync
	syncGroupsToNote = false, -- Sync group assignments to friend notes (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
	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%)",
	["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
}

-- 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: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

	-- 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

	-- 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

	-- 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

	-- Debug: Check ElvUI Skin setting
	if BetterFriendlistDB.enableElvUISkin then
		-- BFL:DebugPrint("Database: ElvUI Skin is ENABLED")
	else
		-- BFL:DebugPrint("Database: ElvUI Skin is DISABLED")
	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

		-- 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)

	-- 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 defaults.")
	print("|cff00ff00BetterFriendlist:|r 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 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)
	if not groups or #groups == 0 then
		BetterFriendlistDB.friendGroups[friendUID] = nil
	else
		BetterFriendlistDB.friendGroups[friendUID] = groups
	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 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
