local addonName, privateDB = ...
local HDG = _G.HousingDecorGuide

-- ============================================================================
-- CONFIGURATION MODULE
-- Constants, Settings Validation, Logging
-- ============================================================================

-- Addon version (dynamically read from .toc file - single source of truth)
local ADDON_VERSION = C_AddOns.GetAddOnMetadata(addonName, "Version") or "Unknown"
privateDB.ADDON_VERSION = ADDON_VERSION

-- Database version - increment this to force shopping list clear on update
local DB_VERSION = 4 -- v2.0.2: Force refresh for crafting history field name changes

-- Configuration Constants
local CONFIG = {
    -- UI Sizes (OPTIMIZED for v1.0.8 - with tabs and proper spacing)
    MAIN_FRAME_WIDTH = 870,
    MAIN_FRAME_HEIGHT = 704, -- banner(34) + tabs(32) + gap(8) + panels(612) + footer(18)
    TAB_HEIGHT = 32,
    BANNER_HEIGHT = 34, -- 12:1 aspect ratio banner (408x34)
    ICON_SIZE = 32,  -- Standard icon size for buttons and tabs
    TAB_ICON_SIZE = 18,
    TAB_TEXT_WIDTH = 80,
    TAB_ICON_ONLY_WIDTH = 30,
    TAB_GAP = 2,

    -- Panel widths (3-panel layout maintained)
    -- With 4px padding on each side and 8px gaps: 870 - 8 - 16 = 846 / 3 = 282
    LEFT_PANEL_WIDTH = 282,
    MIDDLE_PANEL_WIDTH = 282,
    RIGHT_PANEL_WIDTH = 282,

    PANEL_HEIGHT = 606, -- matches TabPanel: 704 - 74(top) - 24(bottom) = 606

    -- Recipe elements
    RECIPE_BUTTON_WIDTH = 238, -- Reduced to avoid scrollbar (was 250)
    RECIPE_BUTTON_HEIGHT = 30, -- was 35 (tighter)
    PROFESSION_HEADER_HEIGHT = 24,

    -- Queue elements
    QUEUE_ITEM_HEIGHT = 24, -- Match MATERIAL_ITEM_HEIGHT for visual consistency
    QUEUE_ITEM_WIDTH = 240, -- Fits "Valdrakken Hanging Cauldron" in 270px panel

    -- Materials elements
    MATERIAL_ITEM_HEIGHT = 24, -- was 30 (tighter)
    MATERIAL_ITEM_WIDTH = 250, -- Reduced for compact 280px panel

    -- Lumber sections
    LUMBER_WIDGET_HEIGHT = 120, -- Bottom of recipe panel (reduced from 140)
    LUMBER_ITEM_HEIGHT = 20,    -- Individual lumber items (reduced from 22)

    -- Lumber Column Widths (New for alignment)
    LUMBER_COL_1 = 90, -- Type
    LUMBER_COL_2 = 76, -- Needed
    LUMBER_COL_3 = 76, -- In Stock

    -- Lumber Coordinates limit (v2.2.x fix for performance degradation)
    MAX_LUMBER_COORDINATES = 500, -- Maximum stored gathering locations (FIFO pruning)

    -- SavedVariables size caps (prevent unbounded growth)
    MAX_USER_NOTES = 500,        -- Per-item user notes
    MAX_VENDOR_NOTES = 200,      -- Per-vendor notes
    MAX_TRACKED_REAGENTS = 200,  -- User-managed reagent tracking
    MAX_FARMING_HISTORY = 100,   -- Lumber farming sessions
    MAX_CHARACTERS = 50,         -- Character profession data

    -- Spacing (TIGHTER)
    SPACING = {
        PANEL_PADDING = 4,  -- was 8, tightened for more compact layout
        PANEL_GAP = 8,      -- Between panels
        PROFESSION_GAP = 6, -- was 10
        RECIPE_GAP = 2,     -- was 5 (very tight)
        HEADER_HEIGHT = 26,
        RECIPE_HEIGHT = 32, -- was 40
        SECTION_GAP = 8,
        ITEM_PADDING = 4,   -- Internal item padding

        -- Buttons (shared across tabs)
        BTN_HEIGHT = 20,    -- Standard small action button
        BTN_WIDTH = 90,     -- Standard small action button
        BTN_GAP = 4,        -- Vertical gap between stacked buttons
        BTN_MARGIN = 8,     -- Button inset from panel edge

        -- Info panels (Preview tab detail pane, etc.)
        INFO_LINE_HEIGHT = 14, -- Per-line spacing in info panes
        INFO_LEFT_MARGIN = 8,  -- Text inset from panel left
        INFO_TOP_MARGIN = 8,   -- First line offset from panel top
    },

    -- Textures (WoW UI)
    TEXTURES = {
        CHECKMARK = "Interface\\RAIDFRAME\\ReadyCheck-Ready",
        XMARK = "Interface\\RAIDFRAME\\ReadyCheck-NotReady",
        WHITE_BG = "Interface\\Buttons\\WHITE8x8",
        TOOLTIP_BORDER = "Interface\\Tooltips\\UI-Tooltip-Border",
        STATUSBAR = "Interface\\Buttons\\WHITE8x8", -- Flatter texture
        CRAFTED_ICON = "Interface\\Icons\\Trade_BlackSmithing",
    },

    -- Tab Icons (WoW built-in icons, theme-aware)
    TAB_ICONS = {
        recipes     = "Interface\\Icons\\INV_Misc_Book_09",
        acquisition = "Interface\\Icons\\INV_Misc_Bag_10",
        preview     = "Interface\\Icons\\INV_Misc_SpyGlass_03",
        goblin      = "Interface\\Icons\\INV_Misc_Coin_17",
        trainers    = "Interface\\Icons\\Spell_Holy_SealOfSacrifice",
        characters  = "Interface\\Icons\\Achievement_GuildPerk_EverybodysFriend",
        styles      = "Interface\\Icons\\INV_Inscription_Pigment_Grey",
        config      = "Interface\\Icons\\Trade_Engineering",
        data        = "Interface\\Icons\\INV_Scroll_15",
    },

    -- Custom Addon Textures (in textures/ folder)
    ADDON_TEXTURES = {
        -- UI buttons
        CLOSE = "Interface\\AddOns\\HousingDecorGuide\\textures\\close",
        PIN = "Interface\\AddOns\\HousingDecorGuide\\textures\\strongpin",
        CSV_EXPORT = "Interface\\AddOns\\HousingDecorGuide\\textures\\csvexportbutton",
        -- Icons
        SORT_ARROW = "Interface\\AddOns\\HousingDecorGuide\\textures\\sort-arrow",
        DISCORD = "Interface\\AddOns\\HousingDecorGuide\\textures\\discord",
        TITLE1 = "Interface\\AddOns\\HousingDecorGuide\\textures\\title1",
        TITLE2 = "Interface\\AddOns\\HousingDecorGuide\\textures\\title2",
        TRACKER_TITLE = "Interface\\AddOns\\HousingDecorGuide\\textures\\tracker_title",
    },

    -- Factions that use Renown ranks instead of traditional rep standings (Friendly/Honored/etc.)
    -- Used by VendorDB: if factionID is in this table, minRep = renown rank; otherwise minRep = standing (4-8)
    RENOWN_FACTIONS = {
        -- Dragonflight
        [2503] = true,  -- Maruuk Centaur
        [2507] = true,  -- Dragonscale Expedition
        [2510] = true,  -- Valdrakken Accord
        [2511] = true,  -- Iskaara Tuskarr
        [2517] = true,  -- Dream Wardens
        [2518] = true,  -- Loamm Niffen
        -- The War Within
        [2590] = true,  -- Council of Dornogal
        [2594] = true,  -- Assembly of the Deeps
        [2600] = true,  -- Hallowfall Arathi
        [2601] = true,  -- Severed Threads
        [2653] = true,  -- Cartels of Undermine
        [2658] = true,  -- K'aresh Trust
    },

    -- Progress bars
    PROGRESS_BAR = {
        WIDTH = 70, -- Narrower to prevent overlap
        HEIGHT = 11,
        SHOW_BARS = true,
        CHAR_FILLED = "|",
        CHAR_EMPTY = ".",
        BLOCKS = 10,
    },

    -- Positioning constants (NEW - Phase 1)
    POSITIONING = {
        PROGRESS_BAR_MATERIALS_X = -125,
        PROGRESS_BAR_LUMBER_LEFT_X = -80,
        PROGRESS_BAR_LUMBER_RIGHT_X = -120,
        TITLE_BAR_Y = -12,
        RECIPE_ICON_LEFT = 18,
        RECIPE_TEXT_LEFT = 6,
        MATERIAL_NAME_LEFT = 6,
        MATERIAL_NAME_WIDTH = 140,
    },

    -- Font sizes
    FONTS = {
        TITLE = 14,
        HEADER = 12,
        NORMAL = 11, -- was 12 (slightly smaller)
        SMALL = 10,
    },

    -- Text color codes (v1.9.5 - centralized)
    TEXT_COLORS = {
        -- Primary colors
        TITLE = "|cFFe2e8f0",      -- Cool white for titles (#e2e8f0)
        ACCENT = "|cFF14b8a6",     -- Teal accent (#14b8a6)
        GOLD = "|cFFfcd34d",       -- Gold/yellow for highlights (#fcd34d)
        WHITE = "|cFFFFFFFF",      -- Pure white

        -- Status colors
        SUCCESS = "|cFF00FF00",    -- Green for success/positive
        ERROR = "|cFFFF0000",      -- Red for errors
        WARNING = "|cFFFFAA00",    -- Orange for warnings

        -- Muted colors
        MUTED = "|cFF888888",      -- Gray for secondary text
        DARK_MUTED = "|cFF666666", -- Darker gray
        LIGHT_MUTED = "|cFF999999", -- Lighter gray
        SUBTLE = "|cFFAAAAAA",     -- Very light gray

        -- Special colors
        KNOWN = "|cFF88FF88",      -- Light green for "have/known"
        NEED = "|cFFFF5555",       -- Light red for "need"
        LINK = "|cFFAAAAFF",       -- Light blue for clickable/links

        -- Reset
        RESET = "|r",
    },

    -- Padding constants (v1.9.5 - centralized magic numbers)
    PADDING = {
        TIGHT = 4,
        STANDARD = 8,
        LOOSE = 12,
        COLUMN_GAP = 2,
        TITLE_OFFSET = -12,
        SECTION_GAP = 6,
    },

    -- Common sizes (v1.9.5 - centralized magic numbers)
    SIZES = {
        ROW_HEIGHT = 18,
        BUTTON_HEIGHT = 22,
        ICON_SMALL = 16,
        ICON_MEDIUM = 24,
        ICON_LARGE = 32,
    },

    -- ============================================================================
    -- PREVIEW TAB CONSTANTS (Phase 3 refactor)
    -- ============================================================================
    PREVIEW_TAB = {
        CACHE_TTL = 300,              -- 5 minutes cache for decor list
        SEARCH_BOX_WIDTH = 140,
        SEARCH_BOX_HEIGHT = 18,
        MODEL_PANEL_HEIGHT = 410,
        ITEM_ROW_HEIGHT = 24,
        ITEM_ROW_SPACING = 26,        -- Row height + gap
        LIST_PANEL_WIDTH = 260,
        LIST_CONTENT_WIDTH = 230,
        FILTER_ROW_HEIGHT = 20,
        TAG_ROW_HEIGHT = 18,
        TAG_BUTTON_MAX_WIDTH = 70,
        CATEGORY_BUTTON_MAX_WIDTH = 80,
        FILTER_LABEL_OFFSET = 38,     -- Offset after "Filter:" label
        MAX_FILTER_WIDTH = 780,       -- Max width for filter buttons row
        -- Icon sizes
        STATUS_ICON_SIZE = 12,
        ITEM_ICON_SIZE = 20,
    },

    -- ============================================================================
    -- TRAINERS TAB CONSTANTS (Phase 3 refactor)
    -- ============================================================================
    TRAINERS_TAB = {
        -- Column widths
        FACTION_COL = 46,
        NAME_COL = 160,
        ZONE_COL = 140,
        COORDS_COL = 70,
        -- Row dimensions
        ROW_HEIGHT = 22,
        ENTRY_PADDING = 24,
        EXPANSION_INDENT = 12,
        EXPANSION_HEIGHT = 20,
        HEADER_HEIGHT = 28,
        ACCENT_BAR_WIDTH = 3,
        ACCENT_BAR_HEIGHT = 16,
        -- Faction indicator
        FACTION_BOX_WIDTH = 20,
        FACTION_BOX_HEIGHT = 16,
        -- Buttons
        WAYPOINT_BTN_SIZE = 16,
    },

    -- ============================================================================
    -- UI CONSTANTS (Phase 3 refactor)
    -- ============================================================================
    UI = {
        COMPACT_BTN_SIZE = 24,
        CLOSE_BTN_OFFSET = -5,
        TAB_HEIGHT = 32,
        HEADER_BG_HEIGHT = 22,
        DISCORD_BTN_WIDTH = 80,
        DISCORD_BTN_HEIGHT = 20,
    },

    -- ============================================================================
    -- SOURCE TYPE CONSTANTS (AllDecorDB)
    -- ============================================================================
    SOURCE_TYPE = {
        UNKNOWN = 0, ACHIEVEMENT = 1, QUEST = 2, WORLD_QUEST = 3, DROP = 4,
        VENDOR = 5, CRAFTED = 6, LEARNED = 7, GATHERING = 8,
        TREASURE = 9, PROMOTION = 10, PROFESSION = 11,
    },
    -- Source types shown in Acquisition tab (non-vendor, actionable)
    ACQUIRABLE_SOURCE_TYPES = {1, 2, 3, 4, 6, 9, 10},

    -- Reputation standing levels (0=Hated through 7=Exalted)
    REP_STANDINGS = {
        [0] = "Hated", [1] = "Hostile", [2] = "Unfriendly", [3] = "Neutral",
        [4] = "Friendly", [5] = "Honored", [6] = "Revered", [7] = "Exalted",
    },

    -- ============================================================================
    -- ACQUISITION TAB CONSTANTS
    -- ============================================================================
    ACQUISITION_TAB = {
        -- Layout
        LEFT_PANEL_PCT = 0.40,       -- 40% width for item list
        RIGHT_PANEL_PCT = 0.60,      -- 60% width for vendor detail
        -- Item list
        ITEM_ROW_HEIGHT = 24,
        ITEM_ROW_SPACING = 26,
        SEARCH_DEBOUNCE = 0.3,       -- seconds
        SEARCH_BOX_WIDTH = 120,
        SEARCH_BOX_HEIGHT = 18,
        -- Vendor info section
        VENDOR_INFO_HEIGHT = 48,
        HEADER_PANEL_HEIGHT = 62,
        -- 3D gallery grid
        CELL_SIZE = 96,
        CELL_SPACING = 6,
        CELL_POOL_SIZE = 24,         -- Max visible cells before scroll
        CELL_NAME_FONT_SIZE = 8,
        CELL_NAME_WIDTH = 90,
        DEFAULT_SCENE_ID = 859,
        -- Selected item section (includes 3D preview of selected item)
        SELECTED_ITEM_HEIGHT = 200,
        SELECTED_PREVIEW_SIZE = 240, -- 3D model preview for selected item
        GALLERY_HEIGHT = 192,        -- "Also sells" gallery at bottom
        -- Status icon sizes
        STATUS_ICON_SIZE = 12,
        ITEM_ICON_SIZE = 16,
        -- Waypoint button
        WAYPOINT_BTN_WIDTH = 100,
        WAYPOINT_BTN_HEIGHT = 20,
        -- Dual-view mode
        VIEW_TOGGLE_HEIGHT = 22,
        GROUP_HEADER_HEIGHT = 28,
        VENDOR_ROW_HEIGHT = 26,
        VENDOR_INDENT = 16,
        -- Currency display
        CURRENCY_ICON_SIZE = 14,
        ENDEAVOR_CURRENCY_ID = 3363, -- Community Coupons
        MAX_NOTE_LENGTH = 200,
    },

    -- ============================================================================
    -- STYLES TAB CONSTANTS
    -- ============================================================================
    STYLES_TAB = {
        CELL_SIZE = 97,              -- Blizzard's BaseHousingCatalogEntryTemplate
        CELL_SPACING = 5,            -- Blizzard uses 5px horizontal spacing
        CELL_NAME_WIDTH = 90,
        CELL_NAME_FONT_SIZE = 8,
        DEFAULT_SCENE_ID = 691,      -- Blizzard's catalog thumbnail scene
        STATUS_ICON_SIZE = 12,
    },

    -- ============================================================================
    -- STYLE EDITOR CONSTANTS
    -- ============================================================================
    STYLE_EDITOR = {
        TAG_BTN_HEIGHT = 20,
        TAG_BTN_MIN_WIDTH = 64,
        TAG_BTN_MAX_WIDTH = 120,
        TAG_GRID_COLS = 5,
        TAG_GRID_SPACING = 3,
        DEBOUNCE_TIMER = 0.3,
        MAX_CUSTOM_STYLES = 50,
        NAME_MAX_LENGTH = 40,
        DESC_MAX_LENGTH = 200,
        -- 4-column layout
        CATEGORY_COL_WIDTH = 105,
        CHIPS_COL_WIDTH = 140,
        PICK_LIST_WIDTH = 382,
        BASKET_WIDTH = 199,
        COLUMN_GAP = 6,
        -- Cells (reduced from 97 -> 86)
        EDITOR_CELL_SIZE = 86,
        EDITOR_CELL_SPACING = 1,
        EDITOR_CELL_COLS = 4,
        PICK_LIST_PADDING = 8,
        -- Info bar
        INFO_BAR_HEIGHT = 40,
        -- Basket
        BASKET_ROW_HEIGHT = 36,
        BASKET_ICON_SIZE = 32,
        -- Arrow scroll
        ARROW_BTN_SIZE = 16,
        ARROW_SCROLL_STEP = 80,
        -- Tier strip
        TIER_STRIP_HEIGHT = 22,
        -- Top bar
        TOP_BAR_HEIGHT = 30,
        -- Bottom bar
        BOTTOM_BAR_HEIGHT = 30,
        -- Facet tabs for the editor (display name -> FacetVocab key)
        FACET_TABS = {
            { key = "room",      label = "Room" },
            { key = "category",  label = "Category" },
            { key = "subject",   label = "Subject" },
            { key = "mood",      label = "Mood" },
            { key = "material",  label = "Material" },
            { key = "color",     label = "Color" },
            { key = "culture",   label = "Culture" },
            { key = "motif",     label = "Motif" },
            { key = "formality", label = "Formality" },
            { key = "condition", label = "Condition" },
            { key = "light",     label = "Light" },
            { key = "palette",   label = "Palette" },
            { key = "seasonal",  label = "Seasonal" },
            { key = "freeform",  label = "Freeform" },
        },
    },

    -- ============================================================================
    -- TOOLTIP SETTINGS
    -- ============================================================================
    TOOLTIP = {
        DEFAULT_ANCHOR = "ANCHOR_TOPRIGHT",  -- Tooltip above-right of cursor (cursor at bottom-left)
    },

    -- ============================================================================
    -- TAB PANEL FACTORY CONSTANTS (v2.2.x UI refactor)
    -- Standardized layout for all tab content panels
    -- ============================================================================
    TAB_PANEL = {
        -- Header bar (contains title with wood sign decoration)
        HEADER_HEIGHT = 24,
        HEADER_PADDING = 8,                   -- Left/right padding for header content
        -- Panel positioning (relative to tab content area)
        TOP_OFFSET = 8,                       -- Gap below tab buttons
        SIDE_PADDING = 4,                     -- Left/right margin (uses SPACING.PANEL_PADDING)
        BOTTOM_MARGIN = 24,                   -- Space for footer
        -- Scroll frame offsets (relative to panel)
        SCROLL_TOP_WITH_HEADER = -30,         -- When panel has header bar
        SCROLL_TOP_NO_HEADER = -8,            -- When panel has no header bar
        SCROLL_RIGHT_MARGIN = -28,            -- Right margin (scrollbar space)
        SCROLL_BOTTOM_MARGIN = 8,             -- Bottom margin
        SCROLL_LEFT_MARGIN = 8,               -- Left margin
        -- Multi-column layout (RecipesTab style)
        COLUMN_GAP = 8,                       -- Gap between columns
    },

    -- ============================================================================
    -- FLOATING WIDGET FACTORY CONSTANTS (v2.2.x UI refactor)
    -- Standardized layout for standalone floating windows (e.g., pinned lumber tracker)
    -- ============================================================================
    FLOATING_WIDGET = {
        -- Header
        HEADER_HEIGHT = 34,
        HEADER_BTN_SIZE = 24,
        HEADER_BTN_GAP = 4,
        HEADER_BTN_MARGIN = 6,
        -- Panel sections
        PANEL_MARGIN = 6,                     -- Margin from widget edge to panel
        PANEL_GAP = 4,                        -- Gap between panels
        PANEL_PADDING = 12,                   -- Internal padding (header + content)
        FOOTER_PADDING = 8,
        -- Section headers
        SECTION_HEADER_HEIGHT = 22,
        -- Row heights
        ROW_HEIGHT = 18,
        ROW_HEIGHT_EXPANDED = 62,             -- Expanded row (e.g., farming session)
        -- Lumber tracker specific
        LUMBER_HEADER_HEIGHT = 18,                -- Column header row (Type/Needed/In Stock)
        LUMBER_WIDTH = 280,
        LUMBER_MATERIALS_HEIGHT = 390,
        LUMBER_COL_1 = 90,                    -- Type column
        LUMBER_COL_2 = 76,                    -- Needed column
        LUMBER_COL_3 = 76,                    -- Stock column
        -- Dye Panel
        DYE_PANEL_WIDTH = 450,
        DYE_PANEL_HEIGHT = 520,
        DYE_SIDEBAR_WIDTH = 72,
        DYE_SWATCH_SIZE = 18,
        DYE_SWATCH_SPACING = 2,
        DYE_PALETTE_STRIP_HEIGHT = 24,
        DYE_MAX_CUSTOM_THEMES = 20,
        DYE_MAX_DYES_PER_THEME = 20,
        -- Vendor Shopping List / Zone Scanner
        VENDOR_LIST_WIDTH = 380,
        VENDOR_LIST_HEIGHT = 480,
        VENDOR_LIST_TITLE_HEIGHT = 28,
        VENDOR_LIST_SELECTOR_HEIGHT = 26,
        VENDOR_LIST_ATTRIBUTION_HEIGHT = 24,
        VENDOR_LIST_MODE_BAR_HEIGHT = 24,
        VENDOR_LIST_ACTION_BAR_HEIGHT = 26,
        VENDOR_LIST_MODE_BTN_WIDTH = 100,
        VENDOR_LIST_ZONE_HEADER_HEIGHT = 24,
        VENDOR_LIST_VENDOR_ROW_HEIGHT = 22,
        VENDOR_LIST_ITEM_ROW_HEIGHT = 22,
        VENDOR_LIST_ITEM_ICON_SIZE = 18,
        VENDOR_LIST_INDENT_VENDOR = 12,
        VENDOR_LIST_INDENT_ITEM = 24,
    },
}

-- Store config in privateDB
privateDB.CONFIG = CONFIG

-- Logging System
local LOG_LEVEL = {
    DEBUG = 1,
    INFO = 2,
    WARN = 3,
    ERROR = 4,
}

local currentLogLevel = LOG_LEVEL.WARN -- Production version

local function Log(level, message, ...)
    if level >= currentLogLevel then
        local prefix = {
            [LOG_LEVEL.DEBUG] = "|cFF888888[HDG-DEBUG]|r",
            [LOG_LEVEL.INFO] = "|cFF00FF00[HDG-INFO]|r",
            [LOG_LEVEL.WARN] = "|cFFFFFF00[HDG-WARN]|r",
            [LOG_LEVEL.ERROR] = "|cFFFF0000[HDG-ERROR]|r",
        }
        print(prefix[level], string.format(message, ...))
    end
end

-- Export Log to privateDB
privateDB.Log = Log
privateDB.LOG_LEVEL = LOG_LEVEL

-- Settings Validation
function privateDB.ValidateSettings()
    -- Ensure HDG_Settings exists
    _G.HDG_Settings = _G.HDG_Settings or {}
    local settings = _G.HDG_Settings

    local defaults = {
        shoppingList = {},
        scale = 1.0,
        minimapButton = { hide = false },
        professionFilter = "All", -- Default to showing all professions
        collapsedMaterials = {},  -- Track collapsed material categories
        trainersCollapsed = {},   -- Track collapsed professions in Trainers tab
        framePosition = nil,      -- Will be set dynamically
        dbVersion = 0,            -- Track database version for migration
        craftingHistory = {},     -- v1.9.5: Track crafted decor items
        craftableFilter = false,  -- v1.9.5: "What Can I Craft?" filter mode
        -- v2.0: Multi-character profession tracking
        characters = {},          -- { ["CharName-RealmName"] = { professions = {...}, lastUpdated = timestamp } }
        -- v2.0.1: Lumber tracker pinned state
        lumberPinned = false,     -- Whether pinned lumber window is open
        -- v2.0.2: Independent pinned lumber widget
        pinnedLumberPosition = nil,   -- Position of independent pinned lumber frame
        showLumberMaterials = false,  -- Show all materials (not just lumber) in pinned widget
        -- v2.0.3: UI settings
        showProfessionButtons = true, -- Show quick-access buttons on profession window
        -- v2.0.8: Power Crafting
        showRawMaterials = false,     -- Show expanded raw materials instead of direct reagents
        -- v2.1.1: Price addon preference
        preferredPriceAddon = nil,    -- nil = auto (TSM first), "TSM", or "Auctionator"
        lumberCollapsed = false,      -- Collapse pinned tracker to only show active farming row
        lumberAutoShow = false,       -- Auto-show pinned lumber tracker when lumber collected
        quotesEnabled = true,         -- Master toggle for all quotes (lumber, craft, etc.)
        vamooseChattiness = 30,       -- Vamoose quote frequency (0=off, 1-99=lumber collected between quotes)
        vamooseChatOutput = false,    -- Also output Vamoose quotes to chat window
        trackedReagents = {},         -- Gathering Tracker: {[itemID] = {name, targetQty, addedTime}}
        lumberQuotesEnabled = true,   -- Show lumber quotes (Vamoose, Squirrel, Gazlowe) while farming
        craftQuotesEnabled = true,    -- Show profession-specific quotes when crafting/acquiring decor items
        quotesOnlyChat = false,       -- Only print quotes to chat (no talking head display)
        closeProfWindowAfterCraft = true, -- Close profession window after crafting completes
        -- v2.2.0: Lumber Compass HUD
        lumberHUDEnabled = true,      -- Enable lumber compass HUD (auto-show when farming)
        lumberHUDPosition = nil,      -- Saved position {point, relativePoint, x, y}
        lumberHUDVisible = false,     -- Current visibility state
        -- v2.2.1: Minimap settings (full structure for Store migration)
        minimap = {
            hide = false,
            minimapPos = 220,
            lock = false,
            showMinimapButton = true,
            showInCompartment = true,
            icon = nil,
        },
        lumberCoordinates = {},       -- Lumber node farming history
        farmingHistory = {},          -- Lumber farming session history
        -- v2.2.2: Debug mode
        debugModeEnabled = false,     -- Enable /hdgdebug slash commands
        -- v2.8.0: Waypoint system preference
        waypointProvider = "auto",    -- "auto" (TomTom if available), "native", "tomtom"
    }

    -- Ensure all required keys exist
    for key, defaultValue in pairs(defaults) do
        if settings[key] == nil then
            settings[key] = defaultValue
            Log(LOG_LEVEL.INFO, "Initialized missing setting: %s", key)
        end
    end

    -- v2.0.7: Remove deprecated settings from legacy users
    -- These settings are no longer used but may exist in SavedVariables
    local deprecatedSettings = { "compactMode", "compactPosition", "collapsedProfessions" }
    for _, key in ipairs(deprecatedSettings) do
        if settings[key] ~= nil then
            settings[key] = nil
            Log(LOG_LEVEL.INFO, "Removed deprecated setting: %s", key)
        end
    end

    -- Check for database version change - migrate shopping list if outdated
    if settings.dbVersion ~= DB_VERSION then
        Log(LOG_LEVEL.INFO, "Database version changed from %d to %d - migrating shopping list",
            settings.dbVersion or 0, DB_VERSION)

        -- Migrate shopping list items to use correct IDs from new database
        if HDG_DecorDB and settings.shoppingList then
            local migratedList = {}
            local migratedCount = 0

            -- Pre-build recipe index by name for O(1) lookup (instead of O(n) per item)
            local recipeByName = {}
            for _, recipe in pairs(HDG_DecorDB) do
                if recipe.name then
                    recipeByName[recipe.name] = recipe
                end
            end

            for _, item in ipairs(settings.shoppingList) do
                local recipe = item.name and recipeByName[item.name]
                if recipe then
                    -- Convert reagents from database format to addon format
                    local convertedReagents = {}
                    if recipe.reagents then
                        for reagentID, reagentData in pairs(recipe.reagents) do
                            local source = (HDG_ReagentsDB and HDG_ReagentsDB[reagentID] and HDG_ReagentsDB[reagentID][1]) or "Unknown"
                            table.insert(convertedReagents, {
                                id = reagentID,
                                amount = reagentData.qty,
                                source = source
                            })
                        end
                    end

                    table.insert(migratedList, {
                        id = recipe.itemID,
                        name = recipe.name,
                        spell = recipe.spellID,
                        reagents = convertedReagents,
                        expansion = item.expansion,
                        quantity = item.quantity or 1
                    })
                    migratedCount = migratedCount + 1
                elseif item.id and item.quantity then
                    -- Keep item as-is if we can't migrate it
                    table.insert(migratedList, item)
                end
            end

            settings.shoppingList = migratedList
            if migratedCount > 0 then
                print(string.format(
                    "|cFF00FF00[Housing Decor Guide]|r Database updated! Migrated %d shopping list items.", migratedCount))
            end
        end

        settings.dbVersion = DB_VERSION
    end

    -- Validate shopping list structure
    if type(settings.shoppingList) ~= "table" then
        Log(LOG_LEVEL.WARN, "Invalid shoppingList, resetting to empty")
        settings.shoppingList = {}
    end

    -- Clean invalid shopping list entries
    local validList = {}
    for i, item in ipairs(settings.shoppingList) do
        if type(item) == "table" and item.id and item.quantity then
            if item.quantity > 0 then
                table.insert(validList, item)
            else
                Log(LOG_LEVEL.WARN, "Removed item with invalid quantity: %s", tostring(item.id))
            end
        else
            Log(LOG_LEVEL.WARN, "Removed malformed shopping list item at index %d", i)
        end
    end
    settings.shoppingList = validList

    -- Validate scale
    if type(settings.scale) ~= "number" or settings.scale < 0.5 or settings.scale > 2.0 then
        Log(LOG_LEVEL.WARN, "Invalid scale value, resetting to 1.0")
        settings.scale = 1.0
    end

    Log(LOG_LEVEL.DEBUG, "Settings validation complete")
end

-- ============================================================================
-- MIGRATION HELPER: HDG_Settings → HDG_DB (Store)
-- Phase 2: Migrate legacy SavedVariables to new Store pattern
-- ============================================================================

function privateDB.MigrateToStore()
    -- Only migrate if HDG_Settings exists
    if not _G.HDG_Settings then
        return
    end

    _G.HDG_DB = _G.HDG_DB or {}

    -- Check migration version - allows re-migration when new keys are added
    local MIGRATION_VERSION = 3 -- v2.2.x: Added lumberCoordinates pruning
    if _G.HDG_DB._migrationVersion and _G.HDG_DB._migrationVersion >= MIGRATION_VERSION then
        return -- Already migrated to current version
    end

    Log(LOG_LEVEL.INFO, "Migrating HDG_Settings to HDG_DB Store (v%d)...", MIGRATION_VERSION)

    local settings = _G.HDG_Settings

    -- Migrate config settings
    _G.HDG_DB.config = _G.HDG_DB.config or {}
    local configKeys = {
        "scale", "quotesEnabled", "vamooseChattiness", "vamooseChatOutput",
        "lumberQuotesEnabled", "craftQuotesEnabled", "quotesOnlyChat",
        "closeProfWindowAfterCraft", "showProfessionButtons", "preferredPriceAddon",
        "showRawMaterials", "craftableFilter", "lumberCollapsed", "lumberAutoShow",
        "showLumberMaterials", "lumberHUDEnabled", "lumberHUDPosition", "lumberHUDVisible",
        "searchReagents", "uncollectedFilter", "debug", "theme",
    }
    for _, key in ipairs(configKeys) do
        if settings[key] ~= nil and _G.HDG_DB.config[key] == nil then
            _G.HDG_DB.config[key] = settings[key]
        end
    end

    -- Migrate minimap button setting (legacy format)
    if settings.minimapButton and _G.HDG_DB.config.showMinimapButton == nil then
        _G.HDG_DB.config.showMinimapButton = not settings.minimapButton.hide
    end

    -- Migrate UI state
    _G.HDG_DB.ui = _G.HDG_DB.ui or {}
    local uiKeys = {
        "framePosition", "professionFilter", "collapsedMaterials",
        "trainersCollapsed", "lumberPinned", "pinnedLumberPosition",
        "activeTab", "characterProfessionFilter",
    }
    for _, key in ipairs(uiKeys) do
        if settings[key] ~= nil and _G.HDG_DB.ui[key] == nil then
            _G.HDG_DB.ui[key] = settings[key]
        end
    end

    -- Migrate data tables (check for empty tables, not just nil)
    local function isEmpty(tbl)
        return not tbl or (type(tbl) == "table" and next(tbl) == nil)
    end

    if settings.shoppingList and #settings.shoppingList > 0 and isEmpty(_G.HDG_DB.shoppingList) then
        _G.HDG_DB.shoppingList = settings.shoppingList
    end
    if settings.characters and next(settings.characters) and isEmpty(_G.HDG_DB.characters) then
        _G.HDG_DB.characters = settings.characters
    end
    if settings.craftingHistory and #settings.craftingHistory > 0 and isEmpty(_G.HDG_DB.craftingHistory) then
        _G.HDG_DB.craftingHistory = settings.craftingHistory
    end
    if settings.trackedReagents and next(settings.trackedReagents) and isEmpty(_G.HDG_DB.trackedReagents) then
        _G.HDG_DB.trackedReagents = settings.trackedReagents
    end
    if settings.dbVersion and not _G.HDG_DB.dbVersion then
        _G.HDG_DB.dbVersion = settings.dbVersion
    end

    -- Migrate minimap settings (new format with position, lock, etc.)
    if settings.minimap and not _G.HDG_DB.minimap then
        _G.HDG_DB.minimap = settings.minimap
    end

    -- Migrate lumber coordinates
    if settings.lumberCoordinates and #settings.lumberCoordinates > 0 and isEmpty(_G.HDG_DB.lumberCoordinates) then
        _G.HDG_DB.lumberCoordinates = settings.lumberCoordinates
    end

    -- Migrate farming history (copy if source has data and dest is empty)
    if settings.farmingHistory and #settings.farmingHistory > 0 then
        -- Only copy if source has actual data
        if not _G.HDG_DB.farmingHistory or #_G.HDG_DB.farmingHistory == 0 then
            _G.HDG_DB.farmingHistory = settings.farmingHistory
        end
    elseif not _G.HDG_DB.farmingHistory then
        _G.HDG_DB.farmingHistory = {}
    end

    -- v2.2.x: Prune bloated lumberCoordinates (fix for performance degradation)
    local coords = _G.HDG_DB.lumberCoordinates
    local maxCoords = CONFIG.MAX_LUMBER_COORDINATES
    if coords and #coords > maxCoords then
        local excess = #coords - maxCoords
        local newCoords = {}
        for i = excess + 1, #coords do
            newCoords[#newCoords + 1] = coords[i]
        end
        _G.HDG_DB.lumberCoordinates = newCoords
        Log(LOG_LEVEL.INFO, "Pruned %d old lumber coordinates (limit: %d)", excess, maxCoords)
    end

    -- Mark migration version
    _G.HDG_DB._migrationVersion = MIGRATION_VERSION
    _G.HDG_DB._migrated = true -- Keep for backward compat

    Log(LOG_LEVEL.INFO, "Migration complete - HDG_Settings data moved to HDG_DB")
end

-- ============================================================================
-- BACKWARD COMPATIBILITY PROXY: HDG_Settings
-- Allows legacy code to read/write HDG_Settings while using Store internally
-- ============================================================================

function privateDB.CreateSettingsProxy()
    -- Mapping from HDG_Settings keys to Store state paths
    local configKeys = {
        scale = true, quotesEnabled = true, vamooseChattiness = true,
        vamooseChatOutput = true, lumberQuotesEnabled = true, craftQuotesEnabled = true,
        quotesOnlyChat = true, closeProfWindowAfterCraft = true, showProfessionButtons = true,
        preferredPriceAddon = true, showRawMaterials = true, craftableFilter = true,
        lumberCollapsed = true, lumberAutoShow = true, showLumberMaterials = true,
        lumberHUDEnabled = true, lumberHUDPosition = true, lumberHUDVisible = true,
        localeOverride = true,
    }

    local uiKeys = {
        framePosition = true, professionFilter = true, collapsedMaterials = true,
        trainersCollapsed = true, lumberPinned = true, pinnedLumberPosition = true,
    }

    local dataKeys = {
        shoppingList = true, characters = true, craftingHistory = true,
        trackedReagents = true, dbVersion = true, lumberCoordinates = true,
        farmingHistory = true, minimap = true,
    }

    -- Create proxy metatable
    -- Note: privateDB._rawSettings must be set before this function is called
    local proxy = {}
    local mt = {
        __index = function(t, key)
            -- Check if Store is available
            if not HDG or not HDG.Store then
                -- Fall back to raw HDG_Settings during early load
                local raw = privateDB._rawSettings or {}
                return rawget(raw, key)
            end

            local state = HDG.Store:GetState()

            if key == "minimapButton" then
                -- Special case: minimapButton.hide maps to config.showMinimapButton
                return { hide = not state.config.showMinimapButton }
            elseif configKeys[key] then
                return state.config[key]
            elseif uiKeys[key] then
                return state.ui[key]
            elseif dataKeys[key] then
                return state[key]
            else
                -- Unknown key, check raw settings
                local raw = privateDB._rawSettings or {}
                return rawget(raw, key)
            end
        end,

        __newindex = function(t, key, value)
            -- Check if Store is available
            if not HDG or not HDG.Store then
                -- Fall back to raw HDG_Settings during early load
                privateDB._rawSettings = privateDB._rawSettings or {}
                rawset(privateDB._rawSettings, key, value)
                return
            end

            if key == "minimapButton" then
                -- Special case: minimapButton.hide maps to config.showMinimapButton
                if type(value) == "table" then
                    HDG.Store:Dispatch("SET_CONFIG", { key = "showMinimapButton", value = not value.hide })
                end
            elseif configKeys[key] then
                HDG.Store:Dispatch("SET_CONFIG", { key = key, value = value })
            elseif uiKeys[key] then
                -- Map UI keys to specific actions
                if key == "framePosition" then
                    HDG.Store:Dispatch("SET_FRAME_POSITION", { position = value })
                elseif key == "professionFilter" then
                    HDG.Store:Dispatch("SET_PROFESSION_FILTER", { filter = value })
                elseif key == "lumberPinned" then
                    HDG.Store:Dispatch("SET_LUMBER_PINNED", { pinned = value })
                elseif key == "pinnedLumberPosition" then
                    HDG.Store:Dispatch("SET_LUMBER_POSITION", { position = value })
                else
                    -- Generic config update for collapsedMaterials, trainersCollapsed
                    -- Direct state update (not ideal but maintains backward compat)
                    HDG.Store.state.ui[key] = value
                    HDG.Store:QueueSave()
                end
            elseif dataKeys[key] then
                -- Direct data updates (shopping list, etc.) - use state directly
                HDG.Store.state[key] = value
                HDG.Store:QueueSave()
            else
                -- Unknown key, store in raw settings
                privateDB._rawSettings = privateDB._rawSettings or {}
                rawset(privateDB._rawSettings, key, value)
            end
        end,
    }

    setmetatable(proxy, mt)
    return proxy
end

-- Initialize the proxy (called after Store is ready)
function privateDB.InitSettingsProxy()
    -- Keep reference to original HDG_Settings for raw access (before proxy replaces it)
    privateDB._rawSettings = _G.HDG_Settings

    -- First migrate legacy data
    privateDB.MigrateToStore()

    -- Load migrated data into Store
    if HDG and HDG.Store then
        HDG.Store:LoadFromSavedVariables()
    end

    -- Create and install the proxy (uses privateDB._rawSettings for fallback)
    local proxy = privateDB.CreateSettingsProxy()

    -- Replace global HDG_Settings with proxy
    _G.HDG_Settings = proxy

    Log(LOG_LEVEL.INFO, "HDG_Settings proxy installed - backward compatibility enabled")
end
