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

-- 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_CRAFTING_HISTORY = 100,  -- Crafted decor history entries
    MAX_FARMING_HISTORY = 100,   -- Lumber farming sessions
    MAX_CHARACTERS = 50,         -- Character profession data
    SCROLL_CHILD_HEIGHT = 1200,  -- Sentinel height for scroll children

    -- 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
        -- Midnight
        [2792] = true,  -- Ritual Sites
    },

    -- 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
        DIM = "|cFF94a3b8",        -- Slate-400 for tertiary text (#94a3b8)

        -- Special colors
        KNOWN = "|cFF88FF88",      -- Light green for "have/known"
        NEED = "|cFFFF5555",       -- Light red for "need"
        LINK = "|cFFAAAAFF",       -- Light blue for clickable/links
        ZONE = "|cFF87CEEB",       -- Sky blue for zone names (#87CEEB)
        WOW_GOLD = "|cFFFFD700",   -- WoW standard gold (#FFD700)

        -- Faction colors
        ALLIANCE = "|cFF3366CC",   -- Alliance blue
        HORDE = "|cFFCC3333",      -- Horde red

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

    -- RGBA color tuples for SetTextColor / SetBackdropColor / SetVertexColor
    COLORS = {
        -- Text states
        TEXT_ACTIVE = { 1, 1, 1, 1 },           -- White for active/enabled text
        TEXT_INACTIVE = { 0.6, 0.6, 0.6, 1 },   -- Gray for inactive/disabled text
        TEXT_DISABLED = { 0.69, 0.69, 0.69, 1 }, -- Muted gray for disabled buttons
        TEXT_BODY = { 0.9, 0.9, 0.9, 1 },       -- Near-white for body text
        TEXT_DIM = { 0.5, 0.5, 0.5, 1 },        -- Dim gray for placeholders

        -- Semantic status
        COLLECTED = { 0.2, 0.9, 0.2, 1 },       -- Green checkmark / collected state
        UNCOLLECTED = { 1, 0.2, 0.2, 1 },        -- Red X / uncollected state
        CRAFTABLE = { 1, 0.85, 0.1, 1 },         -- Yellow for craftable items
        ACHIEVEMENT_GOLD = { 0.99, 0.83, 0.22, 1 }, -- WoW achievement gold

        -- Panel backdrops (bg, border pairs)
        PANEL_DARK = { 0.08, 0.08, 0.10, 0.97 },
        PANEL_DARK_BORDER_WARM = { 0.45, 0.35, 0.2, 0.9 },   -- Warm wood-toned border
        PANEL_DARK_BORDER_COOL = { 0.29, 0.33, 0.41, 0.8 },   -- Cool slate border
        PANEL_DARK_BORDER_TEAL = { 0.08, 0.72, 0.65, 1 },     -- Accent teal border
        PANEL_POPUP = { 0.05, 0.06, 0.08, 0.95 },             -- Darker popup bg
        PANEL_ROW = { 0.12, 0.12, 0.15, 1 },                  -- Standard row/header bg
        PANEL_ROW_HOVER = { 0.18, 0.18, 0.22, 1 },            -- Row hover state

        -- Faction backgrounds
        FACTION_ALLIANCE_BG = { 0.1, 0.2, 0.4, 1 },
        FACTION_HORDE_BG = { 0.4, 0.1, 0.1, 1 },
    },

    -- 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)
    -- ============================================================================
    -- Single source of truth for source-type metadata. Each entry has:
    --   name       — Lua-safe constant name (used by SOURCE_TYPE alias)
    --   display    — UI label (also feeds SOURCE_TYPE_NAMES alias)
    --   acquirable — true if it appears in Acquire-tab filters / can be tracked
    --   atlas      — optional source-type icon for row prefixes
    --   views      — { vendor = bool, item = bool } — which Acquire view modes
    --                this option is offered in. Vendor view filters NPC inventory
    --                so source types that don't apply to vendors are item-only.
    SOURCE_TYPES = {
        [0]  = { name = "UNKNOWN",     display = "Unknown",     short = "?",      color = {0.53, 0.53, 0.53} },
        [1]  = { name = "ACHIEVEMENT", display = "Achievement", short = "Ach",    color = {1.00, 0.82, 0.29}, acquirable = true, atlas = "UI-HUD-MicroMenu-Achievements-Mouseover", views = { vendor = true, item = true } },
        [2]  = { name = "QUEST",       display = "Quest",       short = "Q",      color = {0.91, 0.58, 0.22}, acquirable = true, atlas = "QuestNormal",                              views = { vendor = true, item = true } },
        [3]  = { name = "WORLD_QUEST", display = "World Quest", short = "Q",      color = {0.24, 0.62, 0.90}, acquirable = true, atlas = "QuestNormal",                              views = { item = true } },
        [4]  = { name = "DROP",        display = "Drop",        short = "Drop",   color = {0.63, 0.30, 0.78}, acquirable = true, atlas = "Crosshair_lootall_32",                    views = { item = true } },
        [5]  = { name = "VENDOR",      display = "Vendor",      short = "Vendor", color = {0.36, 0.70, 0.41},                                                                          views = { vendor = true, item = true } },
        [6]  = { name = "CRAFTED",     display = "Crafted",     short = "Craft",  color = {0.88, 0.39, 0.24}, acquirable = true, atlas = "TEXTURE:Interface\\Icons\\Trade_BlackSmithing", views = { item = true } },
        [7]  = { name = "LEARNED",     display = "Learned",     short = "Learn",  color = {0.55, 0.75, 0.90} },
        [8]  = { name = "GATHERING",   display = "Gathering",   short = "Gather", color = {0.61, 0.48, 0.29} }, -- root/ore brown
        [9]  = { name = "TREASURE",    display = "Treasure",    short = "Treas",  color = {0.78, 0.60, 0.35}, acquirable = true, atlas = "VignetteLoot",                             views = { item = true } },
        [10] = { name = "PROMOTION",   display = "Promotional", short = "Promo",  color = {0.78, 0.43, 0.75}, acquirable = true, atlas = "Professions-Crafting-Orders-Icon",        views = { item = true } },
        [11] = { name = "PROFESSION",  display = "Profession",  short = "Prof",   color = {0.36, 0.66, 0.71} }, -- teal
        [12] = { name = "SHOP",        display = "In-Game Shop", short = "Shop",  color = {0.91, 0.43, 0.62}, acquirable = true, atlas = "Garr-Shop",                               views = { item = true } },
        -- Synthetic source types (ids 100+) — not from Blizzard's AllDecorDB
        -- sourceType field; derived at runtime from vendor item gating. From a
        -- player's POV these ARE how you get the item.
        [100] = { name = "REPUTATION", display = "Reputation", short = "Rep",  color = {0.44, 0.48, 0.71}, acquirable = true, synthetic = true, views = { vendor = true, item = true } }, -- slate violet
        [101] = { name = "ENDEAVOR",   display = "Endeavor",   short = "Endv", color = {0.30, 0.70, 0.62}, acquirable = true, synthetic = true, views = { vendor = true, item = true } },
        [102] = { name = "GOLD_ONLY",  display = "Gold Only",  short = "Gold", color = {0.95, 0.78, 0.35}, acquirable = true, synthetic = true, views = { vendor = true, item = true } },
    },

    -- 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 = 76,
        -- 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
-- Auto-derive backwards-compatible aliases from the unified SOURCE_TYPES table.
-- Consumers that read CONFIG.SOURCE_TYPE.VENDOR / CONFIG.SOURCE_TYPE_NAMES[id]
-- / CONFIG.ACQUIRABLE_SOURCE_TYPES keep working; SOURCE_TYPES is the source of truth.
CONFIG.SOURCE_TYPE = {}
CONFIG.SOURCE_TYPE_NAMES = {}
CONFIG.ACQUIRABLE_SOURCE_TYPES = {}
for id, info in pairs(CONFIG.SOURCE_TYPES) do
    CONFIG.SOURCE_TYPE[info.name] = id
    CONFIG.SOURCE_TYPE_NAMES[id] = info.display
    -- ACQUIRABLE_SOURCE_TYPES is matched against Blizzard's AllDecorDB.entry[2]
    -- field (0..12). Synthetic source types (id 100+) live outside that range.
    if info.acquirable and not info.synthetic then
        table.insert(CONFIG.ACQUIRABLE_SOURCE_TYPES, id)
    end
end
table.sort(CONFIG.ACQUIRABLE_SOURCE_TYPES)

privateDB.CONFIG = CONFIG

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

local currentLogLevel = LOG_LEVEL.WARN -- Production version

local LOG_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",
}

local function Log(level, message, ...)
    if level >= currentLogLevel then
        print(LOG_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 removed in v2.x (migration code retired)
        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"
        tsmPriceMode = "server",      -- "server" (DBMarket) or "region" (DBRegionMarketAvg)
        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"
        stylesCollapsed = {},         -- Styles tab: collapsed tier sections
    }

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

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

-- MigrateToStore removed in v2.x - legacy HDG_Settings→HDG_DB migration retired

-- ============================================================================
-- 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, tsmPriceMode = 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, stylesCollapsed = true,
        lumberPinned = true, pinnedLumberPosition = true,
    }

    local dataKeys = {
        shoppingList = true, characters = true, craftingHistory = true,
        trackedReagents = 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

    -- Load saved data into Store
    HDG.Store:LoadFromSavedVariables()

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