--[[
============================================================
Decor Vendor Addon
© 2026 MidniteDestiny. All Rights Reserved.
============================================================

This file is part of the Decor Vendor addon.

All code, structure, and design are the intellectual
property of MidniteDestiny unless otherwise stated.

You may NOT:
• Copy, reproduce, or redistribute this code
• Modify and redistribute this code
• Use this code in other addons or projects

without explicit permission from the author.

This addon is distributed for personal use only.

============================================================
]]

local addonName, dv = ...
-------------------------------------------------
-- 🔹 Texture Path
-------------------------------------------------
local function GetFullTexturePath(texturePath)
    if texturePath and not string.match(texturePath, "[\\/]") then
        return "Interface\\AddOns\\DecorVendor\\Assets\\" .. texturePath
    end
    return texturePath
end
local COLLECTED_ICON_TEXTURE = "Interface\\AddOns\\DecorVendor\\Assets\\collected"

local function IsLoaded(addon)
    return C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded(addon)
end

hasTomTom = IsLoaded("TomTom")
hasWaypointUI = IsLoaded("WaypointUI")
-------------------------------------------------
-- 🔹 Saved Variables
-------------------------------------------------
vendorSettings = vendorSettings or {
    useTomTom = true,
    scale = .70,
    closeOnEsc = true,
    showMinimapButton = true,
	showVendorCheckmarks = true,
	showMerchantCheckmarks = false,
	showWaypointButton = false,
    visited = {},          -- vendors the player has interacted with
	completedAchievs = {},
    completedDrop = {},    -- item-level completion cache (safe to keep)
	completedDropNoXP = {},
	markCompletedThings = false,
	markFoundVendors = false,
	hideCompletedThings = false,
	openAchievementFrame = true,
}

dbDV = dbDV or {}
dbDV.minimap = dbDV.minimap or { hide = false }
-------------------------------------------------
-- 🔹 Constants
-------------------------------------------------
local C = dv.CONSTANTS
local COLORS = C.COLORS
local CAMERA = C.CAMERA
local MIN_WIDTH = C.MIN_FRAME_WIDTH
local MIN_HEIGHT = C.MIN_FRAME_HEIGHT
local DEFAULT_WIDTH = C.DEFAULT_FRAME_WIDTH
local DEFAULT_HEIGHT = C.DEFAULT_FRAME_HEIGHT
local SIDEBAR_WIDTH = C.SIDEBAR_WIDTH
local HEADER_HEIGHT = C.HEADER_HEIGHT
local DEFAULT_SCENE_ID = C.DEFAULT_SCENE_ID
local CAMERA_IMMEDIATE = C.CAMERA.TRANSITION_IMMEDIATE
local CAMERA_DISCARD = C.CAMERA.MODIFICATION_DISCARD
-------------------------------------------------
-- 🔹 Locals
-------------------------------------------------
local dv_optionsCategory = nil
local decorThumbCache = {}
local itemNameCache = {}
local refreshTimer = nil
local MAX_ITEMS_PER_PAGE = 24
local LibDBIcon = LibStub("LibDBIcon-1.0", true)
local minimapButton
local vendorSessionCache = {}
local BuildSidebarFilters

local MAIN_BACKDROP = {
    bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    tileSize = 16,
    edgeSize = 14,
    insets = { left = 3, right = 3, top = 3, bottom = 3 }
}
local CORE_TABS = {
    { id = "vendors",      text = "Vendors",      icon = "Interface\\Icons\\inv_misc_5potionbag_special" },
    { id = "professions",  text = "Professions",  icon = "Interface\\Icons\\Trade_Tailoring" },
    { id = "quests",       text = "Quests",       icon = "Interface\\Icons\\Inv_misc_note_01" },
    { id = "achievements", text = "Achievements", icon = "Interface\\Icons\\achievement_level_100" },
    { id = "bossdrops",    text = "Boss Drops",   icon = "Interface\\Icons\\achievement_boss_blackhand" },
	--{ id = "currency",     text = "Currency",	  icon = "Interface\\Icons\\inv_misc_coin_01"	},
}
-- ============================================================
-- 🔥 BUILD LOOKUPS FROM ActiveItems
-- ============================================================
dv.ActiveItems = dv.ActiveItems or {}
function dv:BuildActiveItemLookups()

    self.vendorGoodies = {}
    self.decorToItem = {}
    self.itemToNPC = {}

    for itemID, data in pairs(self.ActiveItems) do

        -- Map decor → item
        if data.decorID then
            self.decorToItem[data.decorID] = itemID
        end

        -- Map vendors → items
        if data.soldBy then
            for _, npcID in ipairs(data.soldBy) do

                self.vendorGoodies[npcID] = self.vendorGoodies[npcID] or {
                    items = {}
                }

                table.insert(self.vendorGoodies[npcID].items, itemID)
            end
        end

    end
end

dv.searchQuery = ""
dv.decorItem = dv.decorItem or {}
dv.waitingForMarketData = false
dv.catalogReady = false
dv.currentTab = dv.currentTab or "vendors"
vendorFilteredItems = {}
currentVendorPage = 1
dv.activeWowheadBox = nil
dv.collapsedHeaders = dv.collapsedHeaders or {}
dv.activeWidgets = dv.activeWidgets or {}
dv.questTitleCache = dv.questTitleCache or {}
dv.collectionCache = dv.collectionCache or {}
dv:BuildActiveItemLookups()
-------------------------------------------------
-- 🔹 Filter Categories
-------------------------------------------------
dv.filters = dv.filters or {}

dv.filters.vendors = dv.filters.vendors or {
    expansions = {},
    factions = {},
}

dv.filters.quests = dv.filters.quests or {
    expansions = {},
    factions = {},
}

dv.filters.bossdrops = dv.filters.bossdrops or {
    expansions = {},
}

dv.filters.professions = dv.filters.professions or {
    professions = {},
}

dv.filters.achievements = dv.filters.achievements or {
    groups = {},
    factions = {},
}

dv.EXPANSION_ORDER = {
    "Classic",
    "The Burning Crusade",
    "Wrath of the Lich King",
    "Cataclysm",
    "Mists of Pandaria",
    "Warlords of Draenor",
    "Legion",
    "Battle for Azeroth",
    "Shadowlands",
    "Dragonflight",
    "The War Within",
	"Midnight",
	"The Neighborhoods",
	"Race Locked",
}

dv.Profession_Order = {
    "Alchemy", 
    "Blacksmithing",
    "Cooking", 
    "Enchanting",
    "Engineering", 
    "Inscriptions", 
    "Jewelcrafting", 
    "Junkyard Tinkering",
    "Leatherworking", 
    "Tailoring", 
}


-------------------------------------------------
-- 🔹 Main FRAME  (BasicFrameTemplateWithInset)
--    Blizzard metal border + header bar + built-in close button.
--    Your purple/gold title colours are preserved.
-------------------------------------------------
local frame = CreateFrame("Frame", "DV_MainFrame", UIParent, "BackdropTemplate")
frame:SetSize(DEFAULT_WIDTH, DEFAULT_HEIGHT)
frame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 100, -100)
frame:SetFrameStrata("HIGH")
frame:SetFrameLevel(100)
frame:SetBackdrop(MAIN_BACKDROP)
frame:SetBackdropColor(0, 0, 0, 0.9)
frame:SetBackdropBorderColor(0.3, 0.3, 0.3, 1)
frame:SetMovable(true)
frame:SetResizable(true)
frame:EnableMouse(true)
frame:Hide()
frame:SetScript("OnMouseDown", function(self, button)
    if button == "LeftButton" then
        self:StartMoving()
    end
end)
frame:SetScript("OnMouseUp", function(self, button)
    if button == "LeftButton" then
        self:StopMovingOrSizing()
    end
end)


function dv:BuildMainFrameHeader()
    local titleBar = CreateFrame("Frame", "DV_TitleBar", frame)
    frame.titleBar = titleBar
    titleBar:SetPoint("TOPLEFT", frame, "TOPLEFT", 4, -4)
    titleBar:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -4, -4)
    titleBar:SetHeight(HEADER_HEIGHT)
	
    local titleBg = titleBar:CreateTexture("DV_TitleBG", "BACKGROUND")
    titleBg:SetAllPoints()
    titleBg:SetColorTexture(0.08, 0.08, 0.1, 0.95)
    frame.titleBg = titleBg	
	
    local icon = titleBar:CreateTexture(nil, "ARTWORK")
    icon:SetSize(25, 25)
    icon:SetPoint("LEFT", titleBar, "LEFT", 12, 0)
    icon:SetTexture("Interface\\AddOns\\Decorvendor\\Assets\\decorvendoricon.tga")
	
    local infoIcon = CreateFrame("Button", nil, titleBar)
    infoIcon:SetSize(C.INFOSIZE_WIDTH , C.INFOSIZE_HEIGHT)
    infoIcon:SetPoint("LEFT", icon, "RIGHT", 8, 0)
    local iconTexture = infoIcon:CreateTexture(nil, "ARTWORK")
    iconTexture:SetTexture("Interface\\BUTTONS\\UI-GuildButton-OfficerNote-Up")
    iconTexture:SetAllPoints(infoIcon)
    infoIcon:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight", "ADD")
    infoIcon:SetScript("OnEnter", function(self)
      GameTooltip:SetOwner(self, "ANCHOR_BOTTOMLEFT")
      GameTooltip:AddLine("Decor Vendor Notice", 1, 0.82, 0)
      GameTooltip:AddLine("Current Working Version 1.91", 1, 1, 1, true)
      GameTooltip:Show()
    end)
    infoIcon:SetScript("OnLeave", function(self)
      GameTooltip:Hide()
    end)

    local title = titleBar:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOP", titleBar, "TOP", 0, -4)
    title:SetText("The Original Decor Vendors")
    title:SetTextColor(unpack(C.COLORS.TITLE))	
	
    -- Subtitle beneath the title
    local subtitle = titleBar:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    subtitle:SetPoint("TOP", title, "BOTTOM", 0, -2)
    subtitle:SetText("What should we collect next")
    subtitle:SetTextColor(unpack(C.COLORS.GOLD))

    local closeBtn = CreateFrame("Button", nil, titleBar, "UIPanelCloseButton")
    closeBtn:SetPoint("RIGHT", titleBar, "RIGHT", 0, 0)
    closeBtn:SetScript("OnClick", function()
        frame:Hide()
    end)

    ------------------------------------------------
    -- Reset Buttons
    ------------------------------------------------
    local resetBtn = CreateFrame("Button", "DV_ResetProgressBtn", titleBar, "UIPanelButtonTemplate")
    resetBtn:SetSize(C.RESETBTN_WIDTH, C.RESETBTN_HEIGHT)
    resetBtn:SetPoint("RIGHT", closeBtn, "LEFT", -4, 0)
    resetBtn:SetText("Reset Vendors")
    resetBtn:SetScript("OnClick", function()
        StaticPopupDialogs["DV_RESET_VENDORS"] = {
            text = "Reset all vendor progress?\nThis cannot be undone.",
            button1 = "Reset",
            button2 = "Cancel",
            OnAccept = function()
                ResetAllVendors()
                BuildVendorUI()
            end,
            timeout = 0,
            whileDead = true,
            hideOnEscape = true,
            preferredIndex = 3,
        }
        StaticPopup_Show("DV_RESET_VENDORS")
    end)

    ------------------------------------------------
    -- Search Box
    ------------------------------------------------
    local searchBox = CreateFrame("EditBox", "DV_SearchBox", titleBar, "SearchBoxTemplate")
    searchBox:SetSize(C.SEARCHBOX_WIDTH, C.SEARCHBOX_HEIGHT)
    searchBox:SetPoint("RIGHT", resetBtn, "LEFT", -20, 0)
    searchBox:SetScale(1.2)
    searchBox:SetAutoFocus(false)
    searchBox.Instructions:SetText("Search Vendors or Decor...")
    searchBox:SetScript("OnTextChanged", function(self)
        SearchBoxTemplate_OnTextChanged(self)
        local text = self:GetText()
        dv.searchQuery = text ~= "" and string.lower(text) or nil
        BuildVendorUI()
    end)

end

function dv:BuildMainFrameSidebar()
    frame.sidebar = CreateFrame("Frame", "DV_Sidebar", frame, "BackdropTemplate")
    frame.sidebar:SetWidth(SIDEBAR_WIDTH)
    frame.sidebar:SetPoint("TOPLEFT", frame.titleBar, "BOTTOMLEFT", 8, -6)
    frame.sidebar:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 12, 45)
    frame.sidebar:SetBackdrop(nil)

    local sbg = frame.sidebar:CreateTexture(nil, "BACKGROUND")
    sbg:SetAllPoints()
    sbg:SetTexture("Interface\\Buttons\\WHITE8x8")
    sbg:SetColorTexture(unpack(C.COLORS.SIDEBAR_BG))
    dv.sidebar = frame.sidebar

    frame.sidebarFilters = CreateFrame("Frame", "DV_SidebarFilters", frame.sidebar)
    frame.sidebarFilters:SetAllPoints()
    dv.sidebarFilters = frame.sidebarFilters

    local sidebarFooter = CreateFrame("Frame", nil, dv.sidebar)
    sidebarFooter:SetPoint("TOPLEFT", dv.sidebarFilters, "BOTTOMLEFT", 0, 0)
    sidebarFooter:SetPoint("BOTTOMRIGHT", dv.sidebar, "BOTTOMRIGHT", 0, 0)
end

dv:BuildMainFrameHeader()
dv:BuildMainFrameSidebar()

function dv:CreateContentArea()
    local content = CreateFrame("Frame", "DV_content", frame)
    content:SetPoint("TOPLEFT", frame.sidebar, "TOPRIGHT", 0, 0)
    content:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -3, 3)
    content:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -3, 45)
    content:SetFrameLevel(frame:GetFrameLevel())
    self.contentArea = content

    local contentBg = content:CreateTexture(nil, "BACKGROUND")
    contentBg:SetAllPoints()
    contentBg:SetColorTexture(unpack(C.COLORS.CONTENT_BG))
end

dv:CreateContentArea()
-------------------------------------------------
-- 🔹 Helpers
-------------------------------------------------
local function RequestUpdate()
  if refreshTimer then refreshTimer:Cancel() end
  refreshTimer = C_Timer.NewTimer(0.2, function()
    refreshTimer = nil
    if DV_MainFrame and DV_MainFrame:IsShown() then
      BuildVendorUI()
    end
  end)
end

local function IsCatalogUsable()
    local test = C_HousingCatalog.GetCatalogEntryInfoByRecordID(1, 1, true)
    return test ~= nil
end

local function GetCachedItemName(itemID)
	if not itemID then return "Unknown Item", false end
	if itemNameCache[itemID] then return itemNameCache[itemID], false end
	local item = Item:CreateFromItemID(itemID)
	if not item:IsItemEmpty() then
		item:ContinueOnItemLoad(function()
			itemNameCache[itemID] = item:GetItemName()
			RequestUpdate()
		end)
	end
end

local function GetDecorThumbnail(itemID)
    if decorThumbCache[itemID] then
        return decorThumbCache[itemID]
    end

    local decorData = dv.ActiveItems and dv.ActiveItems[itemID]
    if not decorData then return nil end

    local info = C_HousingCatalog.GetCatalogEntryInfoByRecordID(1, decorData.decorID, true)
    if info and info.iconTexture then
        decorThumbCache[itemID] = info.iconTexture
        return info.iconTexture
    end
end

function UpdateEscBehavior()
    local frameName = "DV_MainFrame"

    for i = #UISpecialFrames, 1, -1 do
        if UISpecialFrames[i] == frameName then
            table.remove(UISpecialFrames, i)
        end
    end

    if vendorSettings.closeOnEsc then
        table.insert(UISpecialFrames, frameName)
    end
end

local function NormalizeNPCName(name)
    if not name then return nil end

    name = string.format("%s", name)
    name = string.gsub(name, "%s*<.-%>", "")
    name = string.gsub(name, "'", "'")
    name = strtrim(name)
    name = string.lower(name)

    return name
end

function dv.GetDecorIconByItemID(itemID)
    local decorData = dv.ActiveItems and dv.ActiveItems[itemID]
    if not decorData or not decorData.decorID then
        return nil
    end

    local info = C_HousingCatalog.GetCatalogEntryInfoByRecordID(
        1,
        decorData.decorID,
        true
    )

    return info and info.iconTexture
end

function dv.IsDecorOwned(itemID)
    if not dv.catalogReady then return false end
    if not itemID then return false end

    local data = dv.ActiveItems and dv.ActiveItems[itemID]
    if not data or not data.decorID then
        return false
    end

    if C_HousingCatalog and C_HousingCatalog.GetCatalogEntryInfoByRecordID then
        local info = C_HousingCatalog.GetCatalogEntryInfoByRecordID(
            Enum.HousingCatalogEntryType.Decor,
            data.decorID,
            true
        )

        if type(info) == "table" then
            local owned =
                (tonumber(info.numPlaced) or 0) +
                (tonumber(info.numStored) or 0) +
                (tonumber(info.ownedCount) or 0) +
                (tonumber(info.quantity) or 0)

            return owned > 0
        end
    end

    return false
end

local function GetVendorStatus(vendorID)
    if not dv.catalogReady then
        return false, 0
    end

    if vendorSessionCache[vendorID] then
        return vendorSessionCache[vendorID].isComplete,
               vendorSessionCache[vendorID].missingCount
    end

   -- local items = dv.vendorGoodies[vendorID]
   local vendor = dv.vendorGoodies[vendorID]
local items = vendor and (vendor.items or vendor)
    local missingCount = 0
    local relevantCount = 0

    for _, itemID in ipairs(items) do
        local decor = dv.ActiveItems and dv.ActiveItems[itemID]
        if decor and decor.decorID then
            relevantCount = relevantCount + 1
            if not dv.IsDecorOwned(itemID) then
                missingCount = missingCount + 1
            end
        end
    end

    local isComplete = (relevantCount > 0 and missingCount == 0)

    vendorSessionCache[vendorID] = {
        isComplete   = isComplete,
        missingCount = missingCount,
    }

    return isComplete, missingCount
end

function dv.GetVendorInfo(vendorID)
    if not vendorID or not dv.npcs then return nil end

    for _, group in ipairs(dv.npcs) do
        for _, vendor in ipairs(group.vendors or {}) do
            if vendor.id == vendorID then
                vendor.groupName = group.name
                vendor.expansion = group.expansion
                return vendor
            end
        end
    end

    return nil
end

local function ShowWowheadBox(parent, wowheadBox, id, type)
    local url = dv:GetWowheadLink(id, type)

    if dv.activeWowheadBox and dv.activeWowheadBox ~= wowheadBox then
        dv.activeWowheadBox:Hide()
    end

    wowheadBox:SetText(url)
    wowheadBox:Show()
    wowheadBox:SetFocus()
    wowheadBox:HighlightText()
    dv.activeWowheadBox = wowheadBox
end

function ResetAllVendors()
    vendorSettings.visited = {}
    print("|cff88ff88DecorVendor:|r Vendor progress reset.")
end

function dv.ClearWidgets()
    for _, w in ipairs(dv.activeWidgets) do
        w:Hide()
    end
    wipe(dv.activeWidgets)
end
------------------------------------------------
-- Event Handler For Found Vendors  (unchanged)
------------------------------------------------
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("MERCHANT_SHOW")
eventFrame:SetScript("OnEvent", function()

    if not dv.npcs then return end

    local guid = UnitGUID("target")
    if not guid then return end

    local npcID = select(6, strsplit("-", guid))
    npcID = tonumber(npcID)
    if not npcID then return end

    vendorSettings.visited = vendorSettings.visited or {}

    for _, group in ipairs(dv.npcs) do
        for _, vendor in ipairs(group.vendors or {}) do

            if vendor.id == npcID then

                if not vendorSettings.visited[vendor.id] then
                    vendorSettings.visited[vendor.id] = true

                    if vendorSettings.hideCompletedThings or vendorSettings.markFoundVendors then
                        BuildVendorUI()
                    end
                end

                return
            end
        end
    end
end)
------------------------------------------------
-- Tab / Sidebar helpers  (unchanged)
------------------------------------------------
function dv.UpdatePreviewSize()
    if not dv.modelContainer then return end

    if dv.currentTab == "achievements" or dv.currentTab == "quests" then
        dv.modelContainer:SetHeight(280)
    else
        dv.modelContainer:SetHeight(360)
    end
end

local TAB_BAR_HEIGHT = 36
local bottomTabs = {}

for i, tabData in ipairs(CORE_TABS) do
    local tab = CreateFrame("Button", "DV_MainTab"..i, frame)
    tab:SetSize(140, 28)

    if i == 1 then
        tab:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 12, 10)
    else
        tab:SetPoint("LEFT", bottomTabs[i-1], "RIGHT", 6, 0)
    end

    -------------------------------------------------
    -- Background
    -------------------------------------------------
    local bg = tab:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetTexture("Interface\\Buttons\\WHITE8x8")

    -- Deep WoW blue
    bg:SetColorTexture(0.08, 0.14, 0.32, 0.95)

    tab.bg = bg

    -------------------------------------------------
    -- Hover Highlight
    -------------------------------------------------
    local highlight = tab:CreateTexture(nil, "HIGHLIGHT")
    highlight:SetAllPoints()
    highlight:SetTexture("Interface\\Buttons\\WHITE8x8")

    -- Soft gold hover glow
    highlight:SetColorTexture(1, 0.82, 0.2, 0.25)

    -------------------------------------------------
    -- Text
    -------------------------------------------------
    local text = tab:CreateFontString(nil, "OVERLAY")
    text:SetFont(STANDARD_TEXT_FONT, 12, "OUTLINE")
    text:SetPoint("CENTER")
    text:SetText(tabData.text)

    -- Default text color
    text:SetTextColor(1, 1, 1)

    tab.text = text
    tab.id = tabData.id

    -------------------------------------------------
    -- Click Logic
    -------------------------------------------------
    tab:SetScript("OnClick", function()

        dv.currentTab = tabData.id

        for _, t in ipairs(bottomTabs) do
            if t == tab then
                -- Active tab (bright blue + gold text)
                t.bg:SetColorTexture(0.18, 0.35, 0.75, 1)
                t.text:SetTextColor(1, 0.82, 0)
            else
                -- Inactive tabs
                t.bg:SetColorTexture(0.08, 0.14, 0.32, 0.95)
                t.text:SetTextColor(1, 1, 1)
            end
        end

        dv.UpdatePreviewSize()
        UpdateSidebarForTab()
        BuildVendorUI()

        PlaySound(SOUNDKIT.IG_CHARACTER_INFO_TAB)
    end)

    bottomTabs[i] = tab
end
-------------------------------------------------
-- 🔹 UpdateSidebarForTab  (unchanged)
-------------------------------------------------
function UpdateSidebarForTab()	
    if dv.reagentsPopup then dv.reagentsPopup:Hide() end
    if dv.vendorPopup then dv.vendorPopup:Hide() end
    if dv.achievementWowheadWrapper then dv.achievementWowheadWrapper:Hide() end
    if dv.questWowheadWrapper then dv.questWowheadWrapper:Hide() end
    if dv.bossNotes then dv.bossNotes:Hide() end
	if dv.profNotes then dv.profNotes:Hide() end
    if dv.questNotes then dv.questNotes:Hide() end
    if dv.achievementPanel then dv.achievementPanel:Hide() end
    if dv.achievementNotes then dv.achievementNotes:Hide() end
	if dv.vendorWaypointBtn then dv.vendorWaypointBtn:Hide() end

    dv.vendorPrevBtn:Hide()
    dv.vendorNextBtn:Hide()
    dv.vendorPageText:Hide()

-- 🔥 CLEAR MODEL SCENE (NEW SYSTEM)
if dv.modelScene then
    dv.modelScene:Hide()

    local actor = dv.previewActor
    if actor then
        actor:ClearModel() -- 💥 THIS prevents ghost models
    end

    -- Optional: full scene reset (very clean)
    if dv.modelScene.sceneID then
        dv.modelScene:TransitionToModelSceneID(
            dv.modelScene.sceneID,
            Enum.ModelSceneCameraTransitionType.Immediate,
            Enum.ModelSceneCameraModificationType.Discard,
            true
        )
    end
end

    if dv.texture then
    dv.texture:SetTexture(nil)
    dv.texture:Hide()
    end

    if dv.modelTitle then
        dv.modelTitle:SetText("Select an item")
    end

    frame:SetWidth(C.DEFAULT_FRAME_WIDTH)

    -- 🔹 Divider Visibility
    if dv.modelDivider then
        dv.modelDivider:Show()
    end

    dv.sidebar:Show()
    dv.ResetSidebarFilters()
    dv.sidebarFilters:Hide()
    dv.sidebarFilters:Show()

    -- Run Tab Specific Builders
    if dv.currentTab == "vendors" then dv.BuildVendorFilters()
    elseif dv.currentTab == "quests" then dv.BuildQuestFilters()
    elseif dv.currentTab == "achievements" then dv.BuildAchievementFilters()
    elseif dv.currentTab == "professions" then dv.BuildProfessionFilters()
    elseif dv.currentTab == "bossdrops" then dv.BuildBossDropFilters()
    end
end

-- 🔹 1. The ScrollFrame (List Area)
local scrollFrame = CreateFrame("ScrollFrame", "DV_ScrollFrame", dv.contentArea, "ScrollFrameTemplate")
scrollFrame:ClearAllPoints()
scrollFrame:SetPoint("TOPLEFT", dv.contentArea, "TOPLEFT", 10, -10)
-- This keeps the list on the left side (400px wide)
scrollFrame:SetPoint("BOTTOMRIGHT", dv.contentArea, "BOTTOMLEFT", 410, 10) 
scrollFrame.ScrollBar:Hide()

local scrollChild = CreateFrame("Frame", "DV_ScrollChild", scrollFrame)
scrollChild:SetSize(C.SCROLLCHILD_WIDTH, 1)
scrollFrame:SetScrollChild(scrollChild)

-- 🔹 2. Show Texture (Fallback)
function dv.ShowPreviewTexture(texture, title)
    if dv.modelScene then dv.modelScene:Hide() end
    
    if dv.texture then
        dv.texture:SetTexture(texture)
        dv.texture:Show()
    end
    if dv.modelTitle then dv.modelTitle:SetText(title or "Preview") end
end

-- 🔹 3. Show 3D Model (The main preview)
function dv.ShowPreviewModel(modelFileID, title)
    local ms = dv.modelScene
    local actor = dv.previewActor
    if not ms or not actor then return end

    if dv.texture then dv.texture:Hide() end
    
    ms:Show()
    dv:ShowModel(dv.modelScene, modelFileID)
    
    if ms.camera then
        C_Timer.After(0.05, function()
            ms.camera:TargetActor(actor, true)
        end)
    end

    if dv.modelTitle then dv.modelTitle:SetText(title or "Preview") end
end

-- 🔹 4. Hide Everything
function dv.HidePreview()
    if dv.modelScene then dv.modelScene:Hide() end
    if dv.texture then dv.texture:Hide() end
    if dv.profNotes then dv.profNotes:Hide() end
    if dv.bossNotes then dv.bossNotes:Hide() end
end

dv.modelContainer = CreateFrame("Frame", nil, dv.contentArea) 
local modelContainer = dv.modelContainer
modelContainer:SetPoint("TOPLEFT", dv.contentArea, "TOPLEFT", 420, -10) 
modelContainer:SetPoint("TOPRIGHT", dv.contentArea, "TOPRIGHT", -10, -10)
modelContainer:SetHeight(C.MODELCONTAINER_HEIGHT)

function dv:CreatePreviewScene(parent)
    local scene = CreateFrame("ModelScene", nil, parent, "ModelSceneMixinTemplate")
    scene:SetAllPoints()
	scene:TransitionToModelSceneID(DEFAULT_SCENE_ID, CAMERA_IMMEDIATE, CAMERA_DISCARD, true)
    scene:Hide()
    scene.currentSceneID = sceneID
    -- Mouse zoom
	scene:EnableMouse(true)
    scene:EnableMouseWheel(true)
    scene:SetScript("OnMouseWheel", function(self, delta)
        local camera = self:GetActiveCamera()
        if camera and camera.ZoomByPercent then
            camera:ZoomByPercent(delta * 0.08)
        end
    end)

    -- Optional: rotation handled by Blizzard automatically (OrbitCamera)

    return scene
end	

dv.modelScene = dv:CreatePreviewScene(dv.modelContainer)

-- 🔥 GET ACTOR FROM SCENE (VERY IMPORTANT)
dv.previewActor = dv.modelScene:GetActorByTag("item") or dv.modelScene:GetActorByTag("decor")

-- 🔹 1. The Divider
if not dv.modelDivider then
    local dividerFrame = CreateFrame("Frame", nil, dv.contentArea) 
    dividerFrame:SetHeight(C.DIVIDER_HEIGHT or 2)
    dividerFrame:SetPoint("TOPLEFT", modelContainer, "BOTTOMLEFT", 10, -6)
    dividerFrame:SetPoint("TOPRIGHT", modelContainer, "BOTTOMRIGHT", -10, -6)
    dividerFrame:SetFrameLevel(dv.contentArea:GetFrameLevel() + 10)
    local tex = dividerFrame:CreateTexture(nil, "OVERLAY")
    tex:SetAllPoints()
    tex:SetColorTexture(1, 0.82, 0.2, 0.9)
    dv.modelDivider = dividerFrame
end

function dv:ShowModel(scene, modelID, displayID)
    if not scene then return end

    scene:Show()

    -- IMPORTANT: reset scene properly
    scene:TransitionToModelSceneID(DEFAULT_SCENE_ID, CAMERA_IMMEDIATE, CAMERA_DISCARD, true)

    -- Get actor from preset
    local actor = scene:GetActorByTag("item") or scene:GetActorByTag("decor")

    if not actor then
        print("DV: No actor found in scene")
        return
    end

    -- 🔥 THIS FIXES EVERYTHING
    actor:SetPreferModelCollisionBounds(true)

    -- Load model
    if modelID then
        actor:SetModelByFileID(modelID)
    elseif displayID then
        actor:SetModelByCreatureDisplayID(displayID)
    end
end

dv.itemContainer = CreateFrame("Frame", nil, dv.contentArea)
local itemContainer = dv.itemContainer
itemContainer:SetPoint("TOPLEFT", modelContainer, "BOTTOMLEFT", 0, -20)
itemContainer:SetPoint("TOPRIGHT", modelContainer, "BOTTOMRIGHT", 0, -20)
itemContainer:SetPoint("BOTTOMLEFT", dv.contentArea, "BOTTOMLEFT", 420, 10)
itemContainer:SetPoint("BOTTOMRIGHT", dv.contentArea, "BOTTOMRIGHT", -10, 10)

function dv:GetWowheadLink(id, rewardType)
    if rewardType == "quest" then
        return "https://www.wowhead.com/quest=" .. tostring(id)
    elseif rewardType == "item" then
        return "https://www.wowhead.com/item=" .. tostring(id)
    else
        return "https://www.wowhead.com/achievement=" .. tostring(id)
    end
end

if not tContains(UISpecialFrames, "DV_VendorPopup") then
    tinsert(UISpecialFrames, "DV_VendorPopup")
end

dv.vendorPopup = CreateFrame("Frame", "DV_VendorPopup", itemContainer, "BackdropTemplate")
local vendorPopup = dv.vendorPopup
local modelWidth = dv.modelScene and dv.modelScene:GetWidth() or 300
vendorPopup:SetWidth(modelWidth - 20)
vendorPopup:ClearAllPoints()
vendorPopup:SetPoint("TOPLEFT", dv.itemContainer, "TOPLEFT", 5, -5)
vendorPopup:SetPoint("BOTTOMRIGHT", dv.itemContainer, "BOTTOMRIGHT", -5, 5)
vendorPopup:Hide()

local popupIconCache = {}

--[[vendorPopupTitle = vendorPopup:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
vendorPopupTitle:ClearAllPoints()
vendorPopupTitle:SetPoint("TOPLEFT", vendorPopup, "TOPLEFT", 15, -12)
vendorPopupTitle:SetJustifyH("LEFT")
vendorPopupTitle:SetText("Vendor Goodies")
vendorPopupTitle:SetTextColor(unpack(C.COLORS.TEXT_PRIMARY))

local vendorZoneText = vendorPopup:CreateFontString(nil, "ARTWORK", "GameFontNormal")
vendorZoneText:SetPoint("RIGHT", vendorPopupTitle, "RIGHT", 210, 0)
vendorZoneText:SetTextColor(unpack(C.COLORS.TEXT_PRIMARY))
vendorZoneText:SetJustifyH("RIGHT")
vendorZoneText:SetText("")
dv.vendorZoneText = vendorZoneText]]

vendorPopupTitle = vendorPopup:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
vendorPopupTitle:ClearAllPoints()
vendorPopupTitle:SetPoint("TOPLEFT", dv.itemContainer, "TOPLEFT", 10, -6)
vendorPopupTitle:SetWidth(190)
vendorPopupTitle:SetJustifyH("LEFT")
vendorPopupTitle:SetText("Vendor Goodies")
vendorPopupTitle:SetTextColor(unpack(C.COLORS.TEXT_PRIMARY))

local vendorZoneText = vendorPopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
vendorZoneText:ClearAllPoints()
vendorZoneText:SetPoint("TOPLEFT", dv.itemContainer, "TOPLEFT", 215, -8)
vendorZoneText:SetWidth(180)
vendorZoneText:SetJustifyH("LEFT")
vendorZoneText:SetTextColor(unpack(C.COLORS.TEXT_PRIMARY))
vendorZoneText:SetText("")
dv.vendorZoneText = vendorZoneText

local vendorPopupHiddenText = vendorPopup:CreateFontString(nil, "OVERLAY", "GameFontNormal")
vendorPopupHiddenText:SetFont(STANDARD_TEXT_FONT, 12)
vendorPopupHiddenText:SetTextColor(0.9, 0.9, 0.9, 1)
vendorPopupHiddenText:SetPoint("TOP", vendorPopupTitle, "BOTTOM", 0, -2)
vendorPopupHiddenText:Hide()
vendorPopup.hiddenText = vendorPopupHiddenText

vendorPopup.content = CreateFrame("Frame", nil, vendorPopup)
vendorPopup.content:SetPoint("TOPLEFT", 12, 12)
vendorPopup.content:SetPoint("BOTTOMRIGHT", -16, 16)
-- ======================================
-- Paging Layout  (unchanged)
-- ======================================
dv.pagingFrame = CreateFrame("Frame", nil, frame)
dv.pagingFrame:SetSize(120, 30)
dv.pagingFrame:SetPoint("BOTTOM", dv.itemContainer, "BOTTOM", 0, -15)

dv.vendorPrevBtn = CreateFrame("Button", nil, dv.pagingFrame)
dv.vendorPrevBtn:SetSize(32, 32)
dv.vendorPrevBtn:SetNormalTexture("Interface\\Buttons\\UI-SpellbookIcon-PrevPage-Up")
dv.vendorPrevBtn:SetPushedTexture("Interface\\Buttons\\UI-SpellbookIcon-PrevPage-Down")
dv.vendorPrevBtn:SetDisabledTexture("Interface\\Buttons\\UI-SpellbookIcon-PrevPage-Disabled")
dv.vendorPrevBtn:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight", "ADD")

dv.vendorNextBtn = CreateFrame("Button", nil, dv.pagingFrame)
dv.vendorNextBtn:SetSize(32, 32)
dv.vendorNextBtn:SetNormalTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Up")
dv.vendorNextBtn:SetPushedTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Down")
dv.vendorNextBtn:SetDisabledTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Disabled")
dv.vendorNextBtn:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight", "ADD")

dv.vendorPageText = dv.pagingFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
dv.vendorPageText:SetPoint("CENTER", dv.pagingFrame, "CENTER", 0, 0)

dv.vendorPrevBtn:SetPoint("RIGHT", dv.vendorPageText, "LEFT", -6, 0)
dv.vendorNextBtn:SetPoint("LEFT", dv.vendorPageText, "RIGHT", 6, 0)

local UpdateVendorPopup

dv.vendorPrevBtn:SetScript("OnClick", function()
	if currentVendorPage > 1 then
		currentVendorPage = currentVendorPage - 1
		UpdateVendorPopup()
	end
end)

dv.vendorNextBtn:SetScript("OnClick", function()
	local totalPages = math.ceil(#vendorFilteredItems / MAX_ITEMS_PER_PAGE)
	if currentVendorPage < totalPages then
		currentVendorPage = currentVendorPage + 1
		UpdateVendorPopup()
	end
end)

dv.vendorPopup:SetScript("OnMouseWheel", function(self, delta)

    local totalPages = math.ceil(#vendorFilteredItems / MAX_ITEMS_PER_PAGE)

    if delta > 0 then
        -- Scroll up = previous page
        if currentVendorPage > 1 then
            currentVendorPage = currentVendorPage - 1
            PlaySound(SOUNDKIT.IG_ABILITY_PAGE_TURN)
            UpdateVendorPopup()
        end
    else
        -- Scroll down = next page
        if currentVendorPage < totalPages then
            currentVendorPage = currentVendorPage + 1
            PlaySound(SOUNDKIT.IG_ABILITY_PAGE_TURN)
            UpdateVendorPopup()
        end
    end

end)

local recipeTitle = vendorPopup:CreateFontString(nil, "OVERLAY")
recipeTitle:SetFont(STANDARD_TEXT_FONT, 14); recipeTitle:SetText("Recipe:"); recipeTitle:Hide()

local function GetPopupIconFrame(index)
	local container = popupIconCache[index]
	if not container then
		container = CreateFrame("Frame", nil, vendorPopup.content)
		container:SetSize(50, 50)

		local borderFrame = CreateFrame("Frame", nil, container, "BackdropTemplate")
		borderFrame:SetSize(50, 50)
		borderFrame:SetPoint("TOP")
		borderFrame:SetBackdrop({ edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 2 })
		borderFrame:SetClipsChildren(true)
		container.borderFrame = borderFrame

		local btn = CreateFrame("Button", nil, borderFrame)
		btn:SetAllPoints(borderFrame)
		btn:RegisterForClicks("AnyUp")
		container.btn = btn

		local glow = btn:CreateTexture(nil, "BACKGROUND")
		glow:SetPoint("TOPLEFT", -2, 2); glow:SetPoint("BOTTOMRIGHT", 2, -2)
		glow:SetColorTexture(0, 0, 0, 0.5)
		container.glow = glow

		local icon = btn:CreateTexture(nil, "ARTWORK")
		icon:SetPoint("TOPLEFT", 2, -2); icon:SetPoint("BOTTOMRIGHT", -2, 2)
		icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
		container.icon = icon

		local checkFrame = CreateFrame("Frame", nil, container)
		checkFrame:SetSize(18, 18)
		checkFrame:SetPoint("BOTTOM", 0, -10)
		checkFrame:SetFrameLevel(borderFrame:GetFrameLevel() + 10)
		checkFrame:Hide()

		local checkTex = checkFrame:CreateTexture(nil, "ARTWORK")
		checkTex:SetAllPoints()
		checkTex:SetTexture(COLLECTED_ICON_TEXTURE)
		checkFrame.texture = checkTex
		container.checkFrame = checkFrame

		local countBar = CreateFrame("Frame", nil, container)
		countBar:SetSize(50, 16)
		countBar:SetPoint("TOP", borderFrame, "BOTTOM", 0, 0)
		container.countBar = countBar

		local countBg = countBar:CreateTexture(nil, "BACKGROUND")
		countBg:SetAllPoints()
		countBg:SetColorTexture(0, 0, 0, 1)

		local countText = countBar:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
		countText:SetFont(STANDARD_TEXT_FONT, 14, nil)
		countText:SetTextColor(1, 1, 1, 1)
		countText:SetPoint("CENTER", countBar, "CENTER", 0, 0)
		container.countText = countText
		btn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square")
		btn:GetHighlightTexture():SetBlendMode("ADD"); btn:GetHighlightTexture():SetAllPoints(icon)
		table.insert(popupIconCache, container)
	end
	return container
end

function PopupButton_OnEnter(self)
    local container = self:GetParent():GetParent()
    local borderFrame = container.borderFrame
    local glow = container.glow

    if self.isReagent or self.isRecipe then
        SetCursor("CAST_CURSOR")
        borderFrame:SetBackdropBorderColor(1, 0.82, 0, 1)
        glow:SetColorTexture(1, 0.82, 0, 0.2)
    else
        SetCursor("INSPECT_CURSOR")
        if self.isCollected then
            borderFrame:SetBackdropBorderColor(1, 1, 1, 1)
            glow:SetColorTexture(1, 1, 1, 0.1)
        else
            borderFrame:SetBackdropBorderColor(1, 0.82, 0, 1)
            glow:SetColorTexture(1, 0.82, 0, 0.2)
        end
    end

    GameTooltip:SetOwner(self, "ANCHOR_LEFT")
    GameTooltip:SetHyperlink("item:" .. self.itemID)
    GameTooltip:Show()
end

function PopupButton_OnLeave(self)
    local container = self:GetParent():GetParent()
    local borderFrame = container.borderFrame
    local glow = container.glow

    ResetCursor()

    if self.isReagent or self.isRecipe or not self.isCollected then
        borderFrame:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)
    else
        borderFrame:SetBackdropBorderColor(1, 1, 1, 1)
    end

    glow:SetColorTexture(0, 0, 0, 0.5)
    GameTooltip:Hide()
end

function PopupButton_OnClick(self, button)
    if IsModifiedClick("CHATLINK") then
        local _, link = GetItemInfo(self.itemID)
        if link then
            ChatEdit_InsertLink(link)
        end
    elseif (self.isReagent or self.isRecipe) and (button == "LeftButton" or button == "RightButton") then
        if dv.ShowWowheadLinkPopup then
            dv.ShowWowheadLinkPopup(self.itemID, "item")
        end
    elseif button == "LeftButton" then
        DressUpItemLink("item:" .. self.itemID)
    end
end

local function SetupPopupButton(container, decor, typeStr)
	local btn = container.btn
	local borderFrame = container.borderFrame
	local itemID = decor.id
	container.itemID = itemID
	btn.itemID = itemID
	btn.isReagent = (typeStr == "reagent")
	btn.isRecipe = (typeStr == "recipe")
	btn.isCollected = false
	if dv.catalogReady then
	    btn.isCollected = dv.IsItemCollected(itemID)
	end

	if typeStr == "vendor" then
		btn.isCollected = dv.catalogReady and dv.IsItemCollected(itemID)
		borderFrame:SetBackdropBorderColor(btn.isCollected and 1 or 0.4, btn.isCollected and 1 or 0.4, btn.isCollected and 1 or 0.4, 1)

		if btn.isCollected and vendorSettings.showVendorCheckmarks then
			container.checkFrame:Show()
		else
			container.checkFrame:Hide()
		end

		container:SetSize(50, 50)
		container.countBar:Hide()
	elseif typeStr == "reagent" then
		borderFrame:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)
		container.checkFrame:Hide()
		container:SetSize(50, 66)
		container.countText:SetText(data.amount or 1)
		container.countBar:Show()
	else
		borderFrame:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)
		container.checkFrame:Hide()
		container:SetSize(50, 50)
		container.countBar:Hide()
	end
	local texture = GetItemIcon(itemID)
	container.icon:SetTexture(texture or (typeStr == "recipe" and "Interface\\Icons\\INV_Scroll_03" or "Interface\\Icons\\INV_Misc_QuestionMark"))
	btn:SetScript("OnEnter", PopupButton_OnEnter)
	btn:SetScript("OnLeave", PopupButton_OnLeave)
	btn:SetScript("OnClick", PopupButton_OnClick)
	container:Show()
end

local function LayoutPopupItems(items, typeStr, startY, verticalStep)
    local tileSize = 50
    local margin   = 12
    local columns  = 6

    local totalItems = #items
    local rows = math.ceil(totalItems / columns)

    for i, item in ipairs(items) do
        local container = GetPopupIconFrame(i)
        container:ClearAllPoints()

        local col = (i - 1) % columns
        local row = math.floor((i - 1) / columns)

        local itemsInThisRow = math.min(columns, totalItems - (row * columns))
        local rowWidth = (itemsInThisRow * tileSize) + ((itemsInThisRow - 1) * margin)

        local xOffset = (col * (tileSize + margin)) - (rowWidth / 2) + (tileSize / 2)

        container:SetPoint(
            "TOP",
            vendorPopup.content,
            "TOP",
            xOffset,
            startY - (row * verticalStep)
        )

        SetupPopupButton(
            container,
            typeStr == "vendor" and { id = item } or item,
            typeStr
        )
    end

    local totalHeight = math.abs(startY) + (rows * verticalStep)
    return totalHeight
end

UpdateVendorPopup = function()

    for _, frame in pairs(popupIconCache) do
        frame:Hide()
    end

    local totalItems = #vendorFilteredItems
    local totalPages = math.ceil(totalItems / MAX_ITEMS_PER_PAGE)
    local itemsToShow = {}

    if totalPages > 1 then
        local startIndex = (currentVendorPage - 1) * MAX_ITEMS_PER_PAGE + 1
        local endIndex = math.min(startIndex + MAX_ITEMS_PER_PAGE - 1, totalItems)

        for i = startIndex, endIndex do
            table.insert(itemsToShow, vendorFilteredItems[i])
        end
    else
        itemsToShow = vendorFilteredItems
    end

    if dv.currentTab == "vendors" and totalPages > 1 then
        dv.vendorPrevBtn:Show()
        dv.vendorNextBtn:Show()
        dv.vendorPageText:Show()
        dv.vendorPageText:SetText(string.format("Page %d of %d", currentVendorPage, totalPages))
        dv.vendorPrevBtn:SetEnabled(currentVendorPage > 1)
        dv.vendorNextBtn:SetEnabled(currentVendorPage < totalPages)
    else
        dv.vendorPrevBtn:Hide()
        dv.vendorNextBtn:Hide()
        dv.vendorPageText:Hide()
    end

    local topOffset = vendorPopup.hiddenText:IsShown() and -62 or -48
    local tileSize, margin = 50, 12
    local columns = 6

    local height = LayoutPopupItems(itemsToShow, "vendor", topOffset, tileSize + margin)

    dv.vendorPopup:SetHeight(height + 4)
end



function dv.ShowVendorPopup(vendorID, vendorName)
    --if not vendorID or not dv.vendorGoodies or not dv.vendorGoodies[vendorID] then return end
    if not vendorID then return end

    local vendor = dv.GetVendorInfo(vendorID)
    if not vendor then return end

    local vendorData = dv.vendorGoodies and dv.vendorGoodies[vendorID]
	
    currentPopupVendorID = vendorID
    currentPopupNpcName = vendorName or currentPopupNpcName

    --local vendorData = dv.vendorGoodies[vendorID]

    local addedItems = {}
    local hiddenCount = 0
local items = vendorData and vendorData.items

    if items then
        for _, itemID in ipairs(items) do
            table.insert(addedItems, itemID)
        end
    end

    vendorPopupTitle:SetText(currentPopupNpcName or "Vendor") 
	    
    recipeTitle:Hide()
    for _, frame in pairs(popupIconCache) do frame:Hide() end

    vendorFilteredItems = addedItems
    currentVendorPage = 1
    UpdateVendorPopup()
    dv.vendorPopup:Show()
	if C_HousingCatalog and C_HousingCatalog.RequestHousingMarketInfoRefresh then
        C_HousingCatalog.RequestHousingMarketInfoRefresh()
    end

    C_Timer.After(0.1, function()
        if dv.vendorPopup:IsShown() then
            UpdateVendorPopup()
        end
    end)
    local vendor = dv.GetVendorInfo(vendorID)
    dv.selectedVendor = vendor

if dv.vendorZoneText then
    if vendor and vendor.zone and vendor.zone ~= "" then
        dv.vendorZoneText:SetText(vendor.zone)
        dv.vendorZoneText:Show()
    else
        dv.vendorZoneText:SetText("")
        dv.vendorZoneText:Hide()
    end
end
-- 1. Create or Update the Global Waypoint Button
if not dv.vendorWaypointBtn then
    dv.vendorWaypointBtn = CreateFrame("Button", nil, dv.itemContainer, "UIPanelButtonTemplate")
    dv.vendorWaypointBtn:SetSize(100, 22)
    dv.vendorWaypointBtn:SetText("Waypoint")
end

local waypointBtn = dv.vendorWaypointBtn


-- 2. Position it at the top-right of your itemContainer
waypointBtn:ClearAllPoints()
waypointBtn:SetPoint("TOPRIGHT", dv.itemContainer, "TOPRIGHT", -10, -10)

-- 3. Use your WORKING logic for the visibility and click
if vendorSettings.showWaypointButton and vendor and vendor.mapID and vendor.x and vendor.y then
    waypointBtn:Show()
    
    waypointBtn:SetScript("OnClick", function()
        -- Your original working TomTom code
        if hasTomTom and TomTom then
            TomTom:AddWaypoint(
                vendor.mapID,
                vendor.x / 100,
                vendor.y / 100,
                {
                    title = vendor.title .. " - " .. (vendor.zone or ""),
                    persistent = false,
                    minimap = true,
                    world = true,
                }
            )
        end

        -- Your original working Blizzard code
        local vec = CreateVector2D(vendor.x / 100, vendor.y / 100)
        local mapPoint = UiMapPoint.CreateFromVector2D(vendor.mapID, vec)
        C_Map.SetUserWaypoint(mapPoint)
        C_SuperTrack.SetSuperTrackedUserWaypoint(true)
        
        PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
        print("|cff00ff00DecorVendor:|r Waypoint set for " .. vendor.title)
    end)
else
    waypointBtn:Hide()
end
end


if not tContains(UISpecialFrames, "DV_ReagentsPopup") then
    tinsert(UISpecialFrames, "DV_ReagentsPopup")
end

-- ======================================
-- Reagents Popup  (unchanged)
-- ======================================
dv.reagentsPopup = CreateFrame("Frame", "DV_ReagentsPopup", dv.itemContainer, "BackdropTemplate")
local rpopup = dv.reagentsPopup
rpopup:ClearAllPoints()
rpopup:SetPoint("TOPLEFT", dv.itemContainer, "TOPLEFT", 5, -5)
rpopup:SetPoint("BOTTOMRIGHT", dv.itemContainer, "BOTTOMRIGHT", -5, 5)
rpopup:SetFrameStrata(dv.contentArea:GetFrameStrata())
rpopup:SetFrameLevel(dv.itemContainer:GetFrameLevel() + 5)
rpopup:Hide()

rpopup.title = rpopup:CreateFontString(nil, "OVERLAY", "GameFontHighlightMedium")
rpopup.title:SetPoint("TOPLEFT", 14, -14)
rpopup.title:SetPoint("TOPRIGHT", -36, -14)
rpopup.title:SetJustifyH("CENTER")
rpopup.title:SetTextColor(unpack(C.COLORS.GOLD))
rpopup.title:SetText("Reagents Needed")

dv.reagentIconCache = dv.reagentIconCache or {}

local function GetReagentIconFrame(i)
    local f = dv.reagentIconCache[i]
    if f then
        f:Show()
        return f
    end

    f = CreateFrame("Frame", nil, rpopup.content, "BackdropTemplate")
    f:SetSize(50, 66)
    f:SetBackdrop({ edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 2 })
    f:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)
    f:SetClipsChildren(true)

    local btn = CreateFrame("Button", nil, f)
    btn:SetAllPoints()
    btn:RegisterForClicks("AnyUp")
    f.btn = btn

    local icon = btn:CreateTexture(nil, "ARTWORK")
    icon:SetPoint("TOPLEFT", 2, -2)
    icon:SetPoint("BOTTOMRIGHT", -2, 18)
    icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
    f.icon = icon

    local countBg = f:CreateTexture(nil, "BACKGROUND")
    countBg:SetPoint("BOTTOMLEFT", 0, 0)
    countBg:SetPoint("BOTTOMRIGHT", 0, 0)
    countBg:SetHeight(16)
    countBg:SetColorTexture(0, 0, 0, 1)

    local countText = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    countText:SetPoint("BOTTOM", 0, 2)
    countText:SetTextColor(1, 1, 1, 1)
    f.countText = countText

    btn:SetScript("OnEnter", function(self)
        SetCursor("CAST_CURSOR")
        f:SetBackdropBorderColor(1, 0.82, 0, 1)
        GameTooltip:SetOwner(self, "ANCHOR_LEFT")
        GameTooltip:SetHyperlink("item:" .. self.itemID)
        GameTooltip:Show()
    end)

    btn:SetScript("OnLeave", function()
        ResetCursor()
        f:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)
        GameTooltip:Hide()
    end)

    btn:SetScript("OnClick", function(self)
        if dv.ShowWowheadLinkPopup then
            dv.ShowWowheadLinkPopup(self.itemID, "item")
        end
    end)

    dv.reagentIconCache[i] = f
    return f
end

function dv.ShowReagentsPopup(itemData)

    if not dv.reagentsPopup then return end
    local rpopup = dv.reagentsPopup

    if not itemData or not itemData.reagents or #itemData.reagents == 0 then
        rpopup:Hide()
        return
    end

    for _, f in pairs(dv.reagentIconCache or {}) do
        f:Hide()
    end

    rpopup.recipeFrame:Hide()
    rpopup.recipeFrame.recipeID = nil

    local yOffset = 0

    if itemData.recipe and itemData.recipe > 0 then

        rpopup.recipeFrame.recipeID = itemData.recipe

        rpopup.recipeIcon:SetTexture(
            GetItemIcon(itemData.recipe) or
            "Interface\\Icons\\INV_Scroll_03"
        )

        local itemObj = Item:CreateFromItemID(itemData.recipe)
        itemObj:ContinueOnItemLoad(function()
            if rpopup.recipeText then
                rpopup.recipeText:SetText(itemObj:GetItemName() or "Recipe")
            end
        end)

        rpopup.recipeFrame:Show()

        yOffset = -48
    end

    local tileSize   = 50
    local spacing    = 12
    local iconsPerRow = 5

    for i, reagent in ipairs(itemData.reagents) do
        local f = GetReagentIconFrame(i)
        f:ClearAllPoints()

        local row = math.floor((i - 1) / iconsPerRow)
        local col = (i - 1) % iconsPerRow

        f:SetPoint(
            "TOPLEFT",
            rpopup.content,
            "TOPLEFT",
            col * (tileSize + spacing),
            yOffset - (row * (tileSize + spacing))
        )

        f.btn.itemID = reagent.id
        f.icon:SetTexture(
            GetItemIcon(reagent.id) or
            "Interface\\Icons\\INV_Misc_QuestionMark"
        )
        f.countText:SetText(reagent.amount or 1)
        f:Show()
    end

    local rows = math.ceil(#itemData.reagents / iconsPerRow)

    local popupHeight =
        (rows * (tileSize + spacing))
        + (itemData.recipe and 110 or 60)

    rpopup:SetHeight(popupHeight)
    rpopup:SetScale(vendorSettings and vendorSettings.scale or .70)
    rpopup:Show()
end

rpopup.content = CreateFrame("Frame", nil, rpopup)
rpopup.content:SetPoint("TOPLEFT", 12, -40)
rpopup.content:SetPoint("BOTTOMRIGHT", -12, 12)

rpopup.recipeFrame = CreateFrame("Button", nil, rpopup.content)
rpopup.recipeFrame:SetSize(C.RECIPEFRAME_WIDTH, C.RECIPEFRAME_HEIGHT)
rpopup.recipeFrame:SetPoint("TOPLEFT", rpopup.content, "TOPLEFT", 0, 0)
rpopup.recipeFrame:Hide()

rpopup.recipeIcon = rpopup.recipeFrame:CreateTexture(nil, "ARTWORK")
rpopup.recipeIcon:SetSize(C.RECIPEICON_WIDTH, C.RECIPEICON_HEIGHT)
rpopup.recipeIcon:SetPoint("LEFT", 0, 0)
rpopup.recipeIcon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

rpopup.recipeText = rpopup.recipeFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
rpopup.recipeText:SetPoint("LEFT", rpopup.recipeIcon, "RIGHT", 8, 0)
rpopup.recipeText:SetJustifyH("LEFT")
rpopup.recipeText:SetWidth(C.RECIPETEXT_WIDTH)
rpopup.recipeText:SetWordWrap(false)
rpopup.recipeText:SetMaxLines(1)
rpopup.recipeFrame:SetScript("OnEnter", function(self)
    if not self.recipeID then return end

    GameTooltip:SetOwner(self, "ANCHOR_NONE")
    GameTooltip:ClearAllPoints()

    GameTooltip:SetPoint(
        "TOP",
        rpopup,
        "BOTTOM",
        0,
        -6
    )

    GameTooltip:SetHyperlink("item:" .. self.recipeID)
    GameTooltip:Show()
end)

rpopup.recipeFrame:SetScript("OnLeave", function()
    GameTooltip:Hide()
end)

rpopup.recipeFrame:SetScript("OnClick", function(self)
    if dv.ShowWowheadLinkPopup then
        dv.ShowWowheadLinkPopup(self.recipeID, "item")
    end
end)

if not dv.vendorNotes then
    dv.vendorNotes = CreateFrame("Frame", nil, dv.itemContainer)

    dv.vendorNotes:SetPoint("TOPLEFT", dv.modelDivider, "BOTTOMLEFT", 10, -280)
    dv.vendorNotes:SetPoint("TOPRIGHT", dv.modelDivider, "BOTTOMRIGHT", -10, -280)

    dv.vendorNotes.bg = dv.vendorNotes:CreateTexture(nil, "BACKGROUND")
    dv.vendorNotes.bg:SetAllPoints()
    dv.vendorNotes.bg:SetColorTexture(0.08, 0.08, 0.08, 0.75)

    dv.vendorNotes.text = dv.vendorNotes:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    dv.vendorNotes.text:SetPoint("TOPLEFT", 10, -10)
    dv.vendorNotes.text:SetPoint("TOPRIGHT", -10, -10)
    dv.vendorNotes.text:SetJustifyH("LEFT")
    dv.vendorNotes.text:SetWordWrap(true)
    dv.vendorNotes.text:SetFont(STANDARD_TEXT_FONT, 12, "")
end
-------------------------------------------------
-- 🔹 The headers and Lines  (all unchanged)
-------------------------------------------------
function dv.CreateVendorHeader(parent, group, y, completed, total)

    completed = tonumber(completed) or 0
    total     = tonumber(total) or 0

    if dv.collapsedHeaders[group.name] == nil then
        dv.collapsedHeaders[group.name] = true
    end

    if dv.filtersJustChanged then
        local expSel = dv.filters.vendors.expansions
        if expSel[group.name] then
            dv.collapsedHeaders[group.name] = false
        else
            dv.collapsedHeaders[group.name] = true
        end
    end

    local header = CreateFrame("Button", nil, parent)
    header:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, y)
    header:SetPoint("RIGHT", parent, "RIGHT", -6, 0)
    header:SetHeight(C.VENDOR_HEADER_HEIGHT)

    local bg = header:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetTexture("Interface\\Buttons\\WHITE8x8")
    bg:SetColorTexture(0.12, 0.08, 0.20, 0.65)

    header.icon = header:CreateFontString(nil, "OVERLAY")
    header.icon:SetFont(STANDARD_TEXT_FONT, 13)
    header.icon:SetPoint("LEFT", 10, 0)

    if dv.collapsedHeaders[group.name] then
        header.icon:SetText("+")
    else
        header.icon:SetText("-")
    end

    header.icon:SetTextColor(0.85, 0.85, 0.85)

    header.text = header:CreateFontString(nil, "OVERLAY")
    header.text:SetFont(STANDARD_TEXT_FONT, 12,"OUTLINE")
    header.text:SetPoint("LEFT", 20, 0)
    header.text:SetText(group.name or "Unknown")
    header.text:SetTextColor(0.95, 0.95, 0.95)

    header.progress = header:CreateFontString(nil, "OVERLAY")
    header.progress:SetFont(STANDARD_TEXT_FONT, 11,"OUTLINE")
    header.progress:SetPoint("RIGHT", -8, 0)
    header.progress:SetText(string.format("%d/%d", completed, total))

    if total > 0 and completed == total then
        header.progress:SetTextColor(0.4, 1, 0.4)
    elseif completed >= total / 2 then
        header.progress:SetTextColor(1, 0.85, 0.3)
    else
        header.progress:SetTextColor(0.75, 0.75, 0.75)
    end
    header.progress:SetText(string.format("%d/%d found", completed, total))

    header:SetScript("OnEnter", function()
        bg:SetColorTexture(0.18, 0.12, 0.30, 0.8)
    end)

    header:SetScript("OnLeave", function()
        bg:SetColorTexture(0.12, 0.08, 0.20, 0.65)
    end)

    header:SetScript("OnClick", function()
        dv.collapsedHeaders[group.name] = not dv.collapsedHeaders[group.name]

        if dv.collapsedHeaders[group.name] then
            header.icon:SetText("+")
        else
            header.icon:SetText("-")
        end

        BuildVendorUI()
    end)

    table.insert(dv.activeWidgets, header)

    return header, dv.collapsedHeaders[group.name], y - 26
end

function dv.CreateVendorLine(parent, vendor, y)

    local line = CreateFrame("Button", nil, parent)
    line:SetPoint("TOPLEFT", parent, "TOPLEFT", 18, y)
    line:SetPoint("RIGHT", parent, "RIGHT", -6, 0)
    line:SetHeight(C.LINE_HEIGHT)
    line:RegisterForClicks("LeftButtonUp", "RightButtonUp")

    local text = line:CreateFontString(nil, "OVERLAY")
    text:SetFont(STANDARD_TEXT_FONT, 12,"OUTLINE")
    text:SetPoint("LEFT", 0, 0)
    text:SetText(vendor.title or "Unknown Vendor")

    local isFound =
        vendorSettings.visited
        and vendorSettings.visited[vendor.id]

    local function SetFactionColor()
        if vendor.faction == "alliance" then
            text:SetTextColor(unpack(C.COLORS.ALLIANCE))
        elseif vendor.faction == "horde" then
            text:SetTextColor(unpack(C.COLORS.HORDE))
        else
            text:SetTextColor(unpack(C.COLORS.NEUTRAL))
        end
    end

    SetFactionColor()

    if isFound and vendorSettings.markFoundVendors then
        text:SetTextColor(0.6, 0.6, 0.6)
        text:SetAlpha(0.7)
    end

    --[[local zoneText
    if vendor.zone then
        zoneText = line:CreateFontString(nil, "OVERLAY")
        zoneText:SetFont(STANDARD_TEXT_FONT, 11,"OUTLINE")
        zoneText:SetPoint("RIGHT", -10, 0)
        zoneText:SetText(vendor.zone)
        zoneText:SetTextColor(unpack(C.COLORS.GOLD))
    end]]

local function UpdatePreview(vendor)
    if not vendor then return end
    
    local ms = dv.modelScene
    local actor = dv.previewActor
    if not ms or not actor then return end

    dv.contentArea._isVendorPreview = true

    -- Title
    if dv.modelTitle then
        dv.modelTitle:SetText(vendor.title or "Vendor")
    end

if vendor.model3D then
    ms:Show()

    dv:ShowModel(ms, nil, vendor.model3D)

    actor:SetAnimation(0)
if actor.SetFacing then
    actor:SetFacing(0)
end

    C_Timer.After(0.05, function()
        local camera = ms:GetActiveCamera()
        if not camera then return end

        local cam = dv.VendorCameraOverrides[vendor.model3D] or {
            targetZ = 0.4,
            pitch = 0.1,
            zoom = 5.5, -- default for normal vendors
        }

        if camera.SetTarget then
            camera:SetTarget(0, 0, cam.targetZ or 0.4)
        end

        if camera.SetPitch then
            camera:SetPitch(cam.pitch or -0.8)
        end

        if camera.SetZoomDistance then
            camera:SetZoomDistance(cam.zoom or 5.5)
        end
    end)
else
    ms:Hide()
end

if dv.texture then
    dv.texture:Hide()
end
end

line:SetScript("OnClick", function(self, button)

    -- 🟢 LEFT CLICK
if button == "LeftButton" then
    UpdatePreview(vendor)
    dv.ShowVendorPopup(vendor.id, vendor.title)
if not dv.vendorNotes then return end
    if vendor.note then
        dv.vendorNotes.text:SetText(vendor.note)
        dv.vendorNotes:SetHeight(dv.vendorNotes.text:GetStringHeight() + 20)
        dv.vendorNotes:Show()
    else
        dv.vendorNotes:Hide()
    end
end

    -- 🔵 RIGHT CLICK (WAYPOINT)
    if button == "RightButton" then
        if vendor.mapID and vendor.x and vendor.y then

            if hasTomTom and TomTom then
                TomTom:AddWaypoint(
                    vendor.mapID,
                    vendor.x / 100,
                    vendor.y / 100,
                    {
                        title = vendor.title .. " - " .. (vendor.zone or ""),
                        persistent = false,
                        minimap = true,
                        world = true,
                    }
                )
            end

            local vec = CreateVector2D(vendor.x / 100, vendor.y / 100)
            local mapPoint = UiMapPoint.CreateFromVector2D(vendor.mapID, vec)
            C_Map.SetUserWaypoint(mapPoint)
            C_SuperTrack.SetSuperTrackedUserWaypoint(true)
        end
    end

end)

    line:SetScript("OnEnter", function()

        text:SetTextColor(1, 0.82, 0.2)

        GameTooltip:SetOwner(line, "ANCHOR_LEFT")
        GameTooltip:AddLine(vendor.title, 1, 1, 1)

        if vendor.zone then
            GameTooltip:AddLine("Zone: " .. vendor.zone, 0.8, 0.8, 0.8)
        end

        if vendor.mapID then
            local mapInfo = C_Map.GetMapInfo(vendor.mapID)
            if mapInfo then
                GameTooltip:AddLine(mapInfo.name, C.COLORS.GOLD)
            end
        end

        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cff00ff00Left Click|r Open Vendor Items")
        GameTooltip:AddLine("|cff00ff00Right Click|r Set Waypoint")

        GameTooltip:Show()
    end)

    line:SetScript("OnLeave", function()
        GameTooltip:Hide()

        if isFound and vendorSettings.markFoundVendors then
            text:SetTextColor(0.6, 0.6, 0.6)
            text:SetAlpha(0.7)
        else
            SetFactionColor()
            text:SetAlpha(1)
        end
    end)

    table.insert(dv.activeWidgets, line)

    return y - 22
end


function dv.CreateProfessionHeader(parent, profession, y, completed, total)
    completed = tonumber(completed) or 0
    total     = tonumber(total) or 0

    dv.collapsedHeaders = dv.collapsedHeaders or {}

    local key = "prof_" .. profession.name

    if dv.collapsedHeaders[key] == nil then
        dv.collapsedHeaders[key] = true
    end

    local collapsed = dv.collapsedHeaders[key]

    if dv.filtersJustChanged then
        local profSel = dv.filters.professions.professions

        if not profSel[profession.name] then
            dv.collapsedHeaders[key] = true
            collapsed = true
        end
    end

    local header = CreateFrame("Button", nil, parent)
    header:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, y)
    header:SetPoint("RIGHT", parent, "RIGHT", -6, 0)
    header:SetHeight(C.VENDOR_HEADER_HEIGHT)

    local bg = header:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetTexture("Interface\\Buttons\\WHITE8x8")
    bg:SetGradient("HORIZONTAL",
        CreateColor(0.15, 0.10, 0.25, 0.9),
        CreateColor(0.05, 0.05, 0.15, 0.9)
    )

    header.icon = header:CreateFontString(nil, "OVERLAY")
    header.icon:SetFont(STANDARD_TEXT_FONT, 13,"OUTLINE")
    header.icon:SetPoint("LEFT", 10, 0)
    header.icon:SetText(collapsed and "+" or "-")

    header.text = header:CreateFontString(nil, "OVERLAY")
    header.text:SetFont(STANDARD_TEXT_FONT, 12,"OUTLINE")
    header.text:SetPoint("LEFT", 20, 0)
    header.text:SetText(string.format("%s (%d/%d learned)", profession.name or "Unknown", completed, total))

    header.progress = header:CreateFontString(nil, "OVERLAY")
    header.progress:SetFont(STANDARD_TEXT_FONT, 11,"OUTLINE")
    header.progress:SetPoint("RIGHT", -8, 0)
    header.progress:SetText(string.format("%d/%d learned", completed, total))

    local color
    if total > 0 and completed == total then
        color = CreateColor(0.2, 1, 0.2, 1)
    elseif completed >= total / 2 then
        color = CreateColor(1, 0.82, 0, 1)
    else
        color = CreateColor(0.9, 0.9, 0.9, 1)
    end
    header.progress:SetTextColor(color:GetRGBA())

    header:SetScript("OnClick", function()
        dv.collapsedHeaders["prof_" .. profession.name] = not collapsed
        BuildVendorUI()
    end)

    table.insert(dv.activeWidgets, header)
    return collapsed, y - 26
end

-- ✅ Moved outside to be a proper standalone function
function dv.UpdateProfessionPreview(profItem)
    -- ✅ Use the new ModelScene and Actor
    local ms = dv.modelScene
    local actor = dv.previewActor
    if not ms or not actor or not profItem then return end

    -- 1. Handle Model Display
    dv.contentArea._isVendorPreview = false
    
    if profItem.model3D then
        ms:Show()
        
        -- ✅ Load the furniture/item by its FileID
        dv:ShowModel(dv.modelScene, profItem.model3D)
        
        -- ✅ The "Magic" Auto-Frame
        -- This ensures the chair, table, or giant statue fits the window perfectly
                C_Timer.After(0.01, function()
            local camera = ms:GetActiveCamera()
            if not camera then return end

            if camera.TargetActor then
                camera:TargetActor(actor, true)
            end

            if camera.SetPitch then
                camera:SetPitch(0.1)
            end
        end)

        if dv.texture then dv.texture:Hide() end
    else
        ms:Hide()
    end

    -- 2. Handle Title update (Stays the same)
    if profItem.id then
        local itemObj = Item:CreateFromItemID(profItem.id)
        itemObj:ContinueOnItemLoad(function()
            if dv.modelTitle then
                dv.modelTitle:SetText(itemObj:GetItemName() or "Preview")
            end
        end)
    else
        if dv.modelTitle then dv.modelTitle:SetText("Preview") end
    end

    -- 3. THE PROFESSION INFO LOGIC (Stays the same)
    if not dv.profNotes then
        dv.profNotes = CreateFrame("Frame", nil, dv.itemContainer)
        dv.profNotes:SetPoint("TOPLEFT", dv.modelDivider, "BOTTOMLEFT", 10, -280)
        dv.profNotes:SetPoint("TOPRIGHT", dv.modelDivider, "BOTTOMRIGHT", -10, -280)
        
        dv.profNotes.bg = dv.profNotes:CreateTexture(nil, "BACKGROUND")
        dv.profNotes.bg:SetAllPoints()
        dv.profNotes.bg:SetColorTexture(0.08, 0.08, 0.08, 0.75)

        dv.profNotes.text = dv.profNotes:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        dv.profNotes.text:SetPoint("TOPLEFT", 10, -10)
        dv.profNotes.text:SetFont(STANDARD_TEXT_FONT, 12, "")
        dv.profNotes.text:SetJustifyH("LEFT")
    end

    local profInfo = ""
    if profItem.skill then
        profInfo = profInfo .. "|cffFFD200Profession:|r " .. profItem.skill .. "\n"
    end
    if profItem.skillNeeded then
        profInfo = profInfo .. "|cff00fbffSkill Required:|r " .. profItem.skillNeeded .. "\n"
    end
    if profItem.note and profItem.note ~= "" then
        profInfo = profInfo .. "\n|cff00ff00Note:|r " .. profItem.note
    end

    if profInfo ~= "" then
        dv.profNotes.text:SetText(profInfo)
        dv.profNotes.text:SetWidth(dv.itemContainer:GetWidth() - 20)
        dv.profNotes:SetHeight(dv.profNotes.text:GetStringHeight() + 20)
        dv.profNotes:Show()
    else
        dv.profNotes:Hide()
    end
end

function dv.CreateProfessionLine(parent, profItem, y)
    local isCompleted = false
    if profItem.spell then
        isCompleted = IsSpellKnown(profItem.spell) or IsPlayerSpell(profItem.spell)
    end

    local line = CreateFrame("Button", nil, parent)
    line:SetPoint("TOPLEFT", parent, "TOPLEFT", 10, y)
    line:SetPoint("RIGHT", parent, "RIGHT", -6, 0)
    line:SetHeight(C.LINE_HEIGHT)
    line:RegisterForClicks("AnyUp")

    local nameText = line:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    nameText:SetFont(STANDARD_TEXT_FONT, 13, "OUTLINE")
    nameText:SetPoint("TOPLEFT", 0, -2)
    nameText:SetJustifyH("LEFT")
    nameText:SetText("• Loading item...")
    
    nameText:SetTextColor(isCompleted and 0.5 or 0.95, isCompleted and 1 or 0.95, isCompleted and 0.5 or 0.95)

    local itemObj = Item:CreateFromItemID(profItem.id)
    itemObj:ContinueOnItemLoad(function()
        if nameText then nameText:SetText(itemObj:GetItemName()) end
    end)

    -- ✅ CLEAN CLICK LOGIC: Calls the external function
    line:SetScript("OnClick", function(self, button)
        if button == "LeftButton" then
            dv.UpdateProfessionPreview(profItem)
            dv.ShowReagentsPopup(profItem)
        end
    end)

    line:SetScript("OnEnter", function(self)
        SetCursor("INSPECT_CURSOR")
        GameTooltip:SetOwner(self, "ANCHOR_LEFT")
        GameTooltip:SetItemByID(profItem.id)
        GameTooltip:AddLine("\n|cff00ff00<Left Click>|r View Decor & Reagents", 1, 1, 1)
        GameTooltip:Show()
    end)

    line:SetScript("OnLeave", function()
        ResetCursor()
        GameTooltip:Hide()
    end)

    table.insert(dv.activeWidgets, line)
    return y - 22
end

function dv.CreateAchievementHeader(parent, achievement, y, completed, total)
    completed = tonumber(completed) or 0
    total     = tonumber(total) or 0

    local headerName
    if type(achievement) == "string" then
        headerName = achievement
    elseif type(achievement) == "table" and achievement.name then
        headerName = achievement.name
    else
        headerName = "Unknown Category"
    end

    local collapseKey = "ach_" .. headerName

    if dv.collapsedHeaders[collapseKey] == nil then
        dv.collapsedHeaders[collapseKey] = true
    end

    local collapsed = dv.collapsedHeaders[collapseKey]

    if dv.filtersJustChanged then
        local groupSel = dv.filters.achievements.groups

        if not groupSel[headerName] then
            dv.collapsedHeaders[collapseKey] = true
            collapsed = true
        end
    end

    local header = CreateFrame("Button", nil, parent)
    header:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, y)
    header:SetPoint("RIGHT", parent, "RIGHT", -6, 0)
    header:SetHeight(C.VENDOR_HEADER_HEIGHT)

    local bg = header:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetTexture("Interface\\Buttons\\WHITE8x8")
    bg:SetGradient("HORIZONTAL",
        CreateColor(0.15, 0.10, 0.25, 0.9),
        CreateColor(0.05, 0.05, 0.15, 0.9)
    )

    header.icon = header:CreateFontString(nil, "OVERLAY")
    header.icon:SetFont(STANDARD_TEXT_FONT, 13)
    header.icon:SetPoint("LEFT", 10, 0)
    header.icon:SetText(collapsed and "+" or "-")

    header.text = header:CreateFontString(nil, "OVERLAY")
    header.text:SetFont(STANDARD_TEXT_FONT, 12,"OUTLINE")
    header.text:SetPoint("LEFT", 20, 0)
    header.text:SetText(string.format("%s (%d/%d completed)", headerName or "Unknown", completed, total))

    header.progress = header:CreateFontString(nil, "OVERLAY")
    header.progress:SetFont(STANDARD_TEXT_FONT, 11,"OUTLINE")
    header.progress:SetPoint("RIGHT", -8, 0)
    header.progress:SetText(string.format("%d/%d completed", completed, total))

    local color
    if total > 0 and completed == total then
        color = CreateColor(0.2, 1, 0.2, 1)
    elseif completed >= total / 2 then
        color = CreateColor(1, 0.82, 0, 1)
    else
        color = CreateColor(0.9, 0.9, 0.9, 1)
    end
    header.progress:SetTextColor(color:GetRGBA())

    header:SetScript("OnClick", function()
        dv.collapsedHeaders[collapseKey] = not dv.collapsedHeaders[collapseKey]
        BuildVendorUI()
    end)

    table.insert(dv.activeWidgets, header)
    return collapsed, y - 26
end

function dv.UpdateAchievementPreview(achievement)
    if not achievement then return end

    local ms = dv.modelScene
    local actor = dv.previewActor
    local texture = dv.texture
    if not ms or not actor then return end

    dv.contentArea._isVendorPreview = false

    -- Title
    if dv.modelTitle then
        dv.modelTitle:SetText(achievement.title or "Achievement Preview")
    end

    -- =========================
    -- MODEL / TEXTURE
    -- =========================
if achievement.model3D then
    if texture then texture:Hide() end

    ms:Show()

    -- reset lock
    ms._cameraLocked = nil

    dv:ShowModel(ms, achievement.model3D)

    if not ms._cameraLocked then
        ms._cameraLocked = true

        C_Timer.After(0.05, function()
            local camera = ms:GetActiveCamera()
            if not camera then return end

            -- 🎯 For OBJECTS (achievements), DO NOT use TargetActor
            if camera.SetTarget then
                camera:SetTarget(0, 0, 0)
            end

            -- 🔥 Use orbit zoom properly
            if camera.SetZoomDistance then
                camera:SetZoomDistance(4.5)
            end

            -- slight tilt
            if camera.SetPitch then
                camera:SetPitch(0.1)
            end
        end)
    end

    elseif achievement.texture then
        ms:Hide()

        if texture then
            texture:SetTexture(achievement.texture)
            texture:Show()
        end
    else
        ms:Hide()
        if texture then texture:Hide() end
    end

    -- =========================
    -- WOWHEAD WRAPPER
    -- =========================
    if not dv.achievementWowheadWrapper then
        dv.achievementWowheadWrapper = CreateFrame("Frame", nil, dv.itemContainer, "BackdropTemplate")
        local wrapper = dv.achievementWowheadWrapper

        wrapper:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8x8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 12,
            insets = { left = 3, right = 3, top = 3, bottom = 3 }
        })

        wrapper:SetBackdropColor(0.08, 0.08, 0.12, 0.95)
        wrapper:SetBackdropBorderColor(0.6, 0.4, 1, 1)
        wrapper:SetHeight(28)

        wrapper.icon = wrapper:CreateTexture(nil, "ARTWORK")
        wrapper.icon:SetSize(18, 18)
        wrapper.icon:SetPoint("LEFT", 6, 0)
        wrapper.icon:SetTexture("Interface\\Icons\\Achievement_Quests_Completed_08")

        dv.achievementWowheadBox = CreateFrame("EditBox", nil, wrapper, "InputBoxTemplate")
        local box = dv.achievementWowheadBox
        box:SetAutoFocus(false)
        box:SetHeight(22)
        box:SetPoint("LEFT", wrapper.icon, "RIGHT", 6, 0)
        box:SetPoint("RIGHT", wrapper, "RIGHT", -8, 0)
        box:SetScript("OnMouseUp", function(self) self:HighlightText() end)
    end

    dv.achievementWowheadWrapper:ClearAllPoints()
    dv.achievementWowheadWrapper:SetPoint("TOPLEFT", dv.modelDivider, "BOTTOMLEFT", 0, -8)
    dv.achievementWowheadWrapper:SetPoint("TOPRIGHT", dv.modelDivider, "BOTTOMRIGHT", 0, -8)
    dv.achievementWowheadBox:SetText("https://www.wowhead.com/achievement=" .. achievement.id)
    dv.achievementWowheadWrapper:Show()

    -- =========================
    -- NOTES
    -- =========================
    if not dv.achievementNotes then
        dv.achievementNotes = CreateFrame("Frame", nil, dv.itemContainer)

        dv.achievementNotes.bg = dv.achievementNotes:CreateTexture(nil, "BACKGROUND")
        dv.achievementNotes.bg:SetAllPoints()
        dv.achievementNotes.bg:SetColorTexture(0.08, 0.08, 0.08, 0.75)

        dv.achievementNotes.text = dv.achievementNotes:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        dv.achievementNotes.text:SetPoint("TOPLEFT", 10, -10)
        dv.achievementNotes.text:SetJustifyH("LEFT")
        dv.achievementNotes.text:SetWordWrap(true)
        dv.achievementNotes.text:SetFont(STANDARD_TEXT_FONT, 12, "")
    end

    dv.achievementNotes:ClearAllPoints()
    dv.achievementNotes:SetPoint("TOPLEFT", dv.achievementWowheadWrapper, "BOTTOMLEFT", 0, -8)
    dv.achievementNotes:SetPoint("TOPRIGHT", dv.achievementWowheadWrapper, "BOTTOMRIGHT", 0, -8)

    if achievement.note then
        dv.achievementNotes.text:SetText("• " .. achievement.note)

        local width = dv.itemContainer:GetWidth() - 20
        dv.achievementNotes.text:SetWidth(width - 20)
        dv.achievementNotes:SetHeight(dv.achievementNotes.text:GetStringHeight() + 20)
        dv.achievementNotes:Show()
    else
        dv.achievementNotes:Hide()
    end
end

function dv.CreateAchievementLine(parent, achievement, y)
    local id = achievement.id
    local isCompleted = dv.IsAchievementComplete(id)

    if isCompleted and vendorSettings.hideCompletedThings and not vendorSettings.markCompletedThings then
        return y
    end

    local name = select(2, GetAchievementInfo(id)) or "Unknown Achievement"

    local line = CreateFrame("Button", nil, parent)
    line:SetPoint("TOPLEFT", parent, "TOPLEFT", 10, y)
    line:SetPoint("RIGHT", parent, "RIGHT", -6, 0)
    line:SetHeight(C.LINE_HEIGHT)
    line:RegisterForClicks("AnyUp")

    line.text = line:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    line.text:SetPoint("LEFT", 0, 0)
    line.text:SetFont(STANDARD_TEXT_FONT, 12)
    line.text:SetText(name)

    local function SetBaseColor()
        if isCompleted and vendorSettings.markCompletedThings then
            line.text:SetTextColor(0.62, 0.62, 0.62)
            line.text:SetAlpha(0.7)
        elseif isCompleted then
            line.text:SetTextColor(0.2, 1, 0.2)
            line.text:SetAlpha(1)
        elseif achievement.faction then
            local f = string.lower(achievement.faction)
            local color = (f == "alliance" and C.COLORS.ALLIANCE) or 
                          (f == "horde" and C.COLORS.HORDE) or 
                          (f == "neutral" and C.COLORS.NEUTRAL)
            line.text:SetTextColor(unpack(color or {0.9, 0.9, 0.9}))
            line.text:SetAlpha(1)
        else
            line.text:SetTextColor(0.9, 0.9, 0.9)
            line.text:SetAlpha(1)
        end
    end
    SetBaseColor()

    line:SetScript("OnEnter", function()
        line.text:SetTextColor(unpack(C.COLORS.GOLD))
        GameTooltip:SetOwner(line, "ANCHOR_LEFT")
        GameTooltip:AddLine(" ")
        GameTooltip:AddLine("|cff00ff00<Left Click>|r Open Achievement")
        GameTooltip:AddLine("|cff00ff00<Left Click>|r View Decor Item")
        GameTooltip:AddLine("|cffff5500<Left Click>|r Copy Wowhead Link")
        GameTooltip:Show()
    end)

    line:SetScript("OnClick", function(_, button)
        if button == "LeftButton" then
            -- ✅ Calling the external Logic function
            dv.UpdateAchievementPreview(achievement)

            if vendorSettings.openAchievementFrame then
                if not AchievementFrame or not AchievementFrame:IsShown() then
                    AchievementFrame_LoadUI()
                    AchievementFrame_ToggleAchievementFrame()
                end
                AchievementFrame_SelectAchievement(id)
            end

            if dv.achievementWowheadBox then
                dv.achievementWowheadBox:HighlightText()
            end
        end
    end)

    line:SetScript("OnLeave", function()
        GameTooltip:Hide()
        SetBaseColor()
    end)

    table.insert(dv.activeWidgets, line)
    return y - 22
end

function dv.CreateQuestHeader(parent, questGroup, y, completed, total)
    completed = tonumber(completed) or 0
    total     = tonumber(total) or 0

    local groupName = questGroup.name or tostring(questGroup)

    local collapseKey = "quest_" .. groupName

    if dv.collapsedHeaders[collapseKey] == nil then
        dv.collapsedHeaders[collapseKey] = true
    end

    if dv.filtersJustChanged then
        local expSel = dv.filters.quests.expansions

        if not expSel[groupName] then
            dv.collapsedHeaders[collapseKey] = true
            collapsed = true
        end
    end

    local collapsed = dv.collapsedHeaders[collapseKey]

    local header = CreateFrame("Button", nil, parent)
    header:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, y)
    header:SetPoint("RIGHT", parent, "RIGHT", -6, 0)
    header:SetHeight(C.VENDOR_HEADER_HEIGHT)

    local bg = header:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetTexture("Interface\\Buttons\\WHITE8x8")
    bg:SetGradient("HORIZONTAL",
        CreateColor(0.15, 0.10, 0.25, 0.9),
        CreateColor(0.05, 0.05, 0.15, 0.9)
    )

    header.icon = header:CreateFontString(nil, "OVERLAY")
    header.icon:SetFont(STANDARD_TEXT_FONT, 13)
    header.icon:SetPoint("LEFT", 10, 0)
    header.icon:SetText(collapsed and "+" or "-")

    header.text = header:CreateFontString(nil, "OVERLAY")
    header.text:SetFont(STANDARD_TEXT_FONT, 12,"OUTLINE")
    header.text:SetPoint("LEFT", 20, 0)
    header.text:SetText(string.format("%s (%d/%d completed)", groupName or "Unknown", completed, total))

    header.progress = header:CreateFontString(nil, "OVERLAY")
    header.progress:SetFont(STANDARD_TEXT_FONT, 11,"OUTLINE")
    header.progress:SetPoint("RIGHT", -8, 0)
    header.progress:SetText(string.format("%d/%d completed", completed, total))

    local color
    if total > 0 and completed == total then
        color = CreateColor(0.2, 1, 0.2, 1)
    elseif completed >= total / 2 then
        color = CreateColor(1, 0.82, 0, 1)
    else
        color = CreateColor(0.9, 0.9, 0.9, 1)
    end
    header.progress:SetTextColor(color:GetRGBA())

    header:SetScript("OnClick", function()
        dv.collapsedHeaders[collapseKey] = not dv.collapsedHeaders[collapseKey]
        BuildVendorUI()
    end)

    table.insert(dv.activeWidgets, header)
    return collapsed, y - 26
end

function dv.CreateQuestLine(parent, quest, y)

    local id = quest.id
    local isCompleted = dv.IsQuestEffectivelyCompleted(quest)

    if isCompleted and vendorSettings.hideCompletedThings and not vendorSettings.markCompletedThings then
        return y
    end

    local liveTitle = dv.questTitleCache[id] or C_QuestLog.GetTitleForQuestID(id)
    local name
    local loading = false

    if liveTitle and liveTitle ~= "" then
        name = liveTitle
        dv.questTitleCache[id] = liveTitle
    else
        name = quest.questName or "|cff888888Quest title unavailable|r"
        loading = not quest.questName
    end

    local line = CreateFrame("Button", nil, parent)
    line:SetPoint("TOPLEFT", parent, "TOPLEFT", 10, y)
    line:SetPoint("RIGHT", parent, "RIGHT", -6, 0)
    line:SetHeight(C.LINE_HEIGHT)
    line:RegisterForClicks("AnyUp")

    line.text = line:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    line.text:SetPoint("LEFT", 0, 0)
    line.text:SetFont(STANDARD_TEXT_FONT, 13, "OUTLINE")
    line.text:SetText(name)

    local function SetBaseColor()
        if isCompleted and vendorSettings.markCompletedThings then
            line.text:SetTextColor(0.62, 0.62, 0.62)
            line.text:SetAlpha(0.7)
            return
        end

        line.text:SetAlpha(1)

        if quest.faction then
            local f = string.lower(quest.faction)
            if f == "alliance" then
                line.text:SetTextColor(unpack(C.COLORS.ALLIANCE))
            elseif f == "horde" then
                line.text:SetTextColor(unpack(C.COLORS.HORDE))
            elseif f == "neutral" then
                line.text:SetTextColor(unpack(C.COLORS.NEUTRAL))
            end
        else
            line.text:SetTextColor(0.9, 0.9, 0.9)
        end
    end
    SetBaseColor()

    if loading then
        QuestEventListener:AddCallback(id, function()
            local newName = C_QuestLog.GetTitleForQuestID(id)
            if newName and line.text:IsVisible() then
                line.text:SetText(newName)
                dv.questTitleCache[id] = newName
            end
        end)
    end

local function UpdateQuestPreview(quest)
    if not quest then return end

    local ms = dv.modelScene
    local actor = dv.previewActor
    local texture = dv.texture
    if not ms or not actor then return end

    dv.contentArea._isVendorPreview = false

    -- Title
    if dv.modelTitle then
        dv.modelTitle:SetText(quest.title or "Quest Preview")
    end

    -- 1. Wowhead Wrapper Logic (EditBox for easy copying)
    if not dv.questWowheadWrapper then
        dv.questWowheadWrapper = CreateFrame("Frame", nil, dv.itemContainer, "BackdropTemplate")
        local wrapper = dv.questWowheadWrapper
        wrapper:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8x8",
            edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
            edgeSize = 12,
            insets = { left = 3, right = 3, top = 3, bottom = 3 }
        })
        wrapper:SetBackdropColor(0.08, 0.08, 0.12, 0.95)
        wrapper:SetBackdropBorderColor(1, 0.82, 0, 1)
        wrapper:SetHeight(28)

        wrapper.icon = wrapper:CreateTexture(nil, "ARTWORK")
        wrapper.icon:SetSize(18, 18)
        wrapper.icon:SetPoint("LEFT", wrapper, "LEFT", 6, 0)
        wrapper.icon:SetTexture("Interface\\Icons\\INV_Misc_Spyglass_03")

        dv.questWowheadBox = CreateFrame("EditBox", nil, wrapper, "InputBoxTemplate")
        local box = dv.questWowheadBox
        box:SetAutoFocus(false)
        box:SetHeight(22)
        box:SetPoint("LEFT", wrapper.icon, "RIGHT", 6, 0)
        box:SetPoint("RIGHT", wrapper, "RIGHT", -8, 0)
        box:SetScript("OnMouseUp", function(self) self:HighlightText() end)
    end

    -- Position Wowhead Box under the divider
    dv.questWowheadWrapper:ClearAllPoints()
    dv.questWowheadWrapper:SetPoint("TOPLEFT", dv.modelDivider, "BOTTOMLEFT", 0, -8)
    dv.questWowheadWrapper:SetPoint("TOPRIGHT", dv.modelDivider, "BOTTOMRIGHT", 0, -8)
    dv.questWowheadBox:SetText("https://www.wowhead.com/quest=" .. quest.id)
    dv.questWowheadWrapper:Show()

    -- 2. Quest Notes Logic
    if not dv.questNotes then
        dv.questNotes = CreateFrame("Frame", nil, dv.itemContainer)
        dv.questNotes.bg = dv.questNotes:CreateTexture(nil, "BACKGROUND")
        dv.questNotes.bg:SetAllPoints()
        dv.questNotes.bg:SetColorTexture(0.08, 0.08, 0.08, 0.75)
        dv.questNotes.text = dv.questNotes:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        dv.questNotes.text:SetPoint("TOPLEFT", 10, -10)
        dv.questNotes.text:SetJustifyH("LEFT")
        dv.questNotes.text:SetWordWrap(true)
        dv.questNotes.text:SetFont(STANDARD_TEXT_FONT, 12, "")
    end

    dv.questNotes:ClearAllPoints()
    dv.questNotes:SetPoint("TOPLEFT", dv.questWowheadWrapper, "BOTTOMLEFT", 0, -8)
    dv.questNotes:SetPoint("TOPRIGHT", dv.questWowheadWrapper, "BOTTOMRIGHT", 0, -8)

    if quest.note then
        dv.questNotes.text:SetText("• " .. quest.note)
        local width = dv.itemContainer:GetWidth() - 20
        dv.questNotes.text:SetWidth(width - 20)
        dv.questNotes:SetHeight(dv.questNotes.text:GetStringHeight() + 20)
        dv.questNotes:Show()
    else
        dv.questNotes:Hide()
    end

    -- 3. Model / Texture Loading Logic
if quest.vendorDisplayID then
        dv.contentArea._isVendorPreview = true
        if texture then texture:Hide() end

        ms:Show()

        -- ✅ FIXED
        dv:ShowModel(ms, nil, quest.vendorDisplayID)

        actor:SetAnimation(2)

        C_Timer.After(0.01, function()
            local camera = ms:GetActiveCamera()
            if not camera then return end

            if camera.TargetActor then
                camera:TargetActor(actor, true)
            end
        end)

    elseif quest.model3D then
        if texture then texture:Hide() end

        ms:Show()

        -- ✅ FIXED
        dv:ShowModel(ms, quest.model3D)

        C_Timer.After(0.01, function()
            local camera = ms:GetActiveCamera()
            if not camera then return end

            if camera.TargetActor then
                camera:TargetActor(actor, true)
            end

            -- slight tilt for decor
            if camera.SetPitch then
                camera:SetPitch(0.2)
            end
        end)

    elseif quest.texture then
        ms:Hide()

        if texture then
            texture:SetTexture(quest.texture)
            texture:Show()
        end
    else
        ms:Hide()
        if texture then texture:Hide() end
    end
end

    line:SetScript("OnClick", function(_, button)
        if button == "LeftButton" then
            UpdateQuestPreview(quest)

            if dv.questWowheadWrapper and dv.questWowheadBox then
                dv.questWowheadBox:SetText("https://www.wowhead.com/quest=" .. id)
                dv.questWowheadBox:HighlightText()
                dv.questWowheadWrapper:Show()
            end
        end
    end)

    line:SetScript("OnEnter", function()
        line.text:SetTextColor(unpack(C.COLORS.GOLD))

        GameTooltip:SetOwner(line, "ANCHOR_LEFT")
        GameTooltip:SetHyperlink("quest:" .. id)
        GameTooltip:AddLine("|cffff5500<Left Click>|r View Decor Item")
        GameTooltip:AddLine("|cffff5500<Left Click>|r Copy Wowhead Link")
        GameTooltip:Show()
    end)

    line:SetScript("OnLeave", function()
        GameTooltip:Hide()
        SetBaseColor()
    end)

    table.insert(dv.activeWidgets, line)

    return y - 22
end

function dv.CreateBossDropHeader(parent, group, collected, total, y)
    dv.collapsedHeaders = dv.collapsedHeaders or {}

    local key = "boss_" .. group.name

    if dv.collapsedHeaders[key] == nil then
        dv.collapsedHeaders[key] = true
    end

    local collapsed = dv.collapsedHeaders[key]

    if dv.filtersJustChanged then
        local expSel = dv.filters.bossdrops.expansions

        if not expSel[group.expansion] then
            dv.collapsedHeaders[key] = true
            collapsed = true
        end
    end

    local collected = 0
    local total = 0

    for _, boss in ipairs(group.items or {}) do
        total = total + 1

        if dv.IsItemCollected(boss.id) then
            collected = collected + 1
        end
    end

    local header = CreateFrame("Button", nil, parent)
    header:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, y)
    header:SetPoint("RIGHT", parent, "RIGHT", -6, 0)
    header:SetHeight(C.VENDOR_HEADER_HEIGHT)

    local bg = header:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetTexture("Interface\\Buttons\\WHITE8x8")
    bg:SetGradient("HORIZONTAL",
        CreateColor(.15, .10, .25, .9),
        CreateColor(.05, .05, .15, .9)
    )

    header.icon = header:CreateFontString(nil, "OVERLAY")
    header.icon:SetFont(STANDARD_TEXT_FONT, 13)
    header.icon:SetPoint("LEFT", 10, 0)
    header.icon:SetText(dv.collapsedHeaders[key] and "+" or "-")

    header.text = header:CreateFontString(nil, "OVERLAY")
    header.text:SetFont(STANDARD_TEXT_FONT, 12,"OUTLINE")
    header.text:SetPoint("LEFT", 20, 0)
    header.text:SetText(group.name)

    header.progress = header:CreateFontString(nil, "OVERLAY")
    header.progress:SetFont(STANDARD_TEXT_FONT, 11,"OUTLINE")
    header.progress:SetPoint("RIGHT", -10, 0)
    header.progress:SetText(("%d/%d collected"):format(collected, total))

    if total > 0 and collected == total then
        header.progress:SetTextColor(0.2,1,0.2)
    elseif collected >= total/2 then
        header.progress:SetTextColor(unpack(C.COLORS.GOLD))
    else
        header.progress:SetTextColor(1,1,1)
    end

    header:SetScript("OnClick", function()
        dv.collapsedHeaders[key] = not dv.collapsedHeaders[key]
        BuildVendorUI()
    end)

    table.insert(dv.activeWidgets, header)

    return dv.collapsedHeaders[key], y - 26
end

function dv.UpdateBossPreview(itemData)
    if not itemData then return end

    local ms = dv.modelScene
    local actor = dv.previewActor
    local texture = dv.texture
    if not ms or not actor then return end

    dv.contentArea._isVendorPreview = false

    -- Title
    local itemObj = Item:CreateFromItemID(itemData.id)
    itemObj:ContinueOnItemLoad(function()
        if dv.modelTitle then
            dv.modelTitle:SetText(itemObj:GetItemName() or "Preview")
        end
    end)

    -- =========================
    -- MODEL LOGIC
    -- =========================
    if itemData.model3D then
        if texture then texture:Hide() end
        ms:Show()

        -- ✅ FIXED
        dv:ShowModel(ms, itemData.model3D)

        C_Timer.After(0.01, function()
            local camera = ms:GetActiveCamera()
            if not camera then return end

            if itemData.cam then
                if camera.SetTarget then
                    camera:SetTarget(
                        itemData.cam.x or 0,
                        itemData.cam.y or 0,
                        itemData.cam.z or 0
                    )
                end

                -- ✅ Replace SetZoom
                if camera.ZoomByDistance and itemData.cam.zf then
                    camera:ZoomByDistance(itemData.cam.zf)
                end
            else
                if camera.TargetActor then
                    camera:TargetActor(actor, true)
                end

                if camera.SetPitch then
                    camera:SetPitch(0.2)
                end
            end
        end)
    else
        ms:Hide()
    end

    -- =========================
    -- BOSS NOTES (UNCHANGED 👍)
    -- =========================

    if not dv.bossNotes then
        dv.bossNotes = CreateFrame("Frame", nil, dv.itemContainer)
        dv.bossNotes:SetPoint("TOPLEFT", dv.modelDivider, "BOTTOMLEFT", 0, -8)
        dv.bossNotes:SetPoint("TOPRIGHT", dv.modelDivider, "BOTTOMRIGHT", 0, -8)
        
        dv.bossNotes.bg = dv.bossNotes:CreateTexture(nil, "BACKGROUND")
        dv.bossNotes.bg:SetAllPoints()
        dv.bossNotes.bg:SetColorTexture(0.08, 0.08, 0.08, 0.75)

        dv.bossNotes.text = dv.bossNotes:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        dv.bossNotes.text:SetPoint("TOPLEFT", 10, -10)
        dv.bossNotes.text:SetPoint("TOPRIGHT", -10, -10)
        dv.bossNotes.text:SetJustifyH("LEFT")
        dv.bossNotes.text:SetWordWrap(true)
        dv.bossNotes.text:SetFont(STANDARD_TEXT_FONT, 12, "")
    end

    local fullBossInfo = ""

    if itemData.bossencounter then
        local encounterName = EJ_GetEncounterInfo(itemData.bossencounter)
        fullBossInfo = fullBossInfo .. "|cffFFD200Boss:|r " .. (encounterName or "Unknown Boss") .. "\n"
    end

    if itemData.instance or itemData.bossevent then
        local loc = itemData.instance or itemData.bossevent
        fullBossInfo = fullBossInfo .. "|cff00fbffLocation:|r " .. loc .. "\n"
    end

    if itemData.note and itemData.note ~= "" then
        fullBossInfo = fullBossInfo .. "\n|cff00ff00Note:|r " .. itemData.note
    end

    if fullBossInfo ~= "" then
        dv.bossNotes.text:SetText(fullBossInfo)
        local usableWidth = dv.itemContainer:GetWidth() - 20
        dv.bossNotes.text:SetWidth(usableWidth)
        dv.bossNotes:SetHeight(dv.bossNotes.text:GetStringHeight() + 20)
        dv.bossNotes:Show()
    else
        dv.bossNotes:Hide()
    end
end

function dv.CreateBossDropLine(parent, itemData, y)
    local line = CreateFrame("Button", nil, parent)
    line:SetPoint("TOPLEFT", parent, "TOPLEFT", 10, y)
    line:SetPoint("RIGHT", parent, "RIGHT", -6, 0)
    line:SetHeight(C.LINE_HEIGHT)
    line:RegisterForClicks("AnyUp")

    local nameFS = line:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    nameFS:SetPoint("LEFT", 0, 0)
    
    local item = Item:CreateFromItemID(itemData.id)
    item:ContinueOnItemLoad(function()
        nameFS:SetText(item:GetItemName() or "Loading...")
    end)

    local isCollected = dv.IsItemCollected(itemData.id)
    nameFS:SetTextColor(isCollected and 0.2 or 1, isCollected and 1 or 1, isCollected and 0.2 or 1)

    line:SetScript("OnClick", function(_, button)
        if button == "LeftButton" then
            -- Use the unified preview function
            dv.UpdateBossPreview(itemData)
        elseif button == "RightButton" and itemData.mapID then
            if not InCombatLockdown() then C_Map.OpenWorldMap(itemData.mapID) end
        end
    end)

    table.insert(dv.activeWidgets, line)
    return y - 24
end

-------------------------------------------------
-- 🔹 The Lists  (all unchanged)
-------------------------------------------------
local function HasAnySelection(tbl)
    if not tbl then return false end
    for _, v in pairs(tbl) do
        if v then return true end
    end
    return false
end

function BuildProfessionList()
    dv.ClearWidgets()

    local profSel = dv.filters.professions.professions
    local hasProfessionFilter = HasAnySelection(profSel)

    local y = -6
    local hasContent = false

    local professions = dv.professions or {}
    table.sort(professions, function(a, b)
        return (a.name or "") < (b.name or "")
    end)

    for _, profession in ipairs(professions) do

        if hasProfessionFilter and not profSel[profession.name] then
            -- skip
        else
            local visible = {}
            local completedCount = 0
            local totalCount = 0

            for _, item in ipairs(profession.items or {}) do
                totalCount = totalCount + 1

                local learned = false
                if item.spell then
                    learned = IsSpellKnown(item.spell) or IsPlayerSpell(item.spell)
                end

                if learned then
                    completedCount = completedCount + 1
                end

                table.insert(visible, item)
            end

            if #visible > 0 then
                hasContent = true

                local completed, total = dv.CountProfessionItems(profession)

                local collapsed, newY =
                    dv.CreateProfessionHeader(scrollChild, profession, y, completedCount, total)
                y = newY

                if not collapsed then
                    for _, item in ipairs(visible) do
                        y = dv.CreateProfessionLine(scrollChild, item, y)
                    end
                    y = y - 10
                end
            end
        end
    end

    if not hasContent then
        local msg = scrollChild:CreateFontString(nil, "OVERLAY")
        msg:SetFont(STANDARD_TEXT_FONT, 14)
        msg:SetPoint("TOP", 0, -50)
        msg:SetText("No profession data available.")
        msg:SetTextColor(0.7, 0.7, 0.7)
        table.insert(dv.activeWidgets, msg)
    end

    scrollChild:SetHeight(math.abs(y) + 40)
end

function BuildQuestList()
    dv.ClearWidgets()

    selectedQuests   = selectedQuests   or {}
    local expSel = dv.filters.quests.expansions
    local facSel = dv.filters.quests.factions

    local hasCategoryFilter = HasAnySelection(catSel)
    local hasFactionFilter  = HasAnySelection(facSel)
    local hasExpansionFilter = HasAnySelection(expSel)

    local y = -6

    local questGroups = {}
    for _, g in ipairs(dv.quests or {}) do
        table.insert(questGroups, g)
    end

    table.sort(questGroups, function(a, b)
        return (a.name or ""):lower() < (b.name or ""):lower()
    end)

    for _, group in ipairs(questGroups) do

        if not hasExpansionFilter or expSel[group.expansion] then

            local visible = {}

            for _, quest in ipairs(group.quests or {}) do
                if quest then
                    local include = true
                    local isCompleted = dv.IsQuestEffectivelyCompleted(quest)

                    if isCompleted and vendorSettings.hideCompletedThings and not vendorSettings.markCompletedThings then
                        include = false
                    end

                    if include and hasFactionFilter then
                        local f = quest.faction and string.lower(quest.faction)
                        if f and not facSel[f] then
                            include = false
                        end
                    end

                    if include then
                        table.insert(visible, quest)
                    end
                end
            end

            if #visible > 0 then

                local total, completed = 0, 0
                for _, quest in ipairs(visible) do
                    total = total + 1
                    if dv.IsQuestEffectivelyCompleted(quest) then
                        completed = completed + 1
                    end
                end

                local collapsed, newY = dv.CreateQuestHeader(
                    scrollChild,
                    group,
                    y,
                    completed,
                    total
                )
                y = newY

                if not collapsed then
                    for _, quest in ipairs(visible) do
                        y = dv.CreateQuestLine(scrollChild, quest, y)
                    end
                    y = y - 10
                end
            end
        end
    end

    scrollChild:SetHeight(math.abs(y) + 20)
end

function BuildAchievementList()
    dv.ClearWidgets()

    local groupSel = dv.filters.achievements.groups
    local facSel   = dv.filters.achievements.factions

    local hasGroupFilter   = HasAnySelection(groupSel)
    local hasFactionFilter = HasAnySelection(facSel)

    local y = -6

    local achieveGroups = {}
    for _, g in ipairs(dv.achievements or {}) do
        achieveGroups[#achieveGroups + 1] = g
    end

    table.sort(achieveGroups, function(a, b)
        return (a.name or ""):lower() < (b.name or ""):lower()
    end)

    for _, group in ipairs(achieveGroups) do

        if hasGroupFilter and not groupSel[group.name] then
            -- skip
        else

            local visible = {}

            for _, achieve in ipairs(group.achievements or {}) do
                local include = true
                local completed = dv.IsAchievementComplete(achieve.id)
                if completed and vendorSettings.hideCompletedThings and not vendorSettings.markCompletedThings then
                    include = false
                end
                achieve.__isCompleted = completed

                if hasFactionFilter then
                    local f = achieve.faction and string.lower(achieve.faction)
                    if not facSel[f] then
                        include = false
                    end
                end

                if include then
                    table.insert(visible, achieve)
                end
            end

            if #visible > 0 then
                local total, completed = 0, 0

                for _, achieve in ipairs(visible) do
                    total = total + 1
                    if dv.IsAchievementComplete(achieve.id) then
                        completed = completed + 1
                    end
                end

                local collapsed, newY = dv.CreateAchievementHeader(
                    scrollChild,
                    group.name,
                    y,
                    completed,
                    total
                )
                y = newY

                if not collapsed then
                    for _, achieve in ipairs(visible) do
                        y = dv.CreateAchievementLine(scrollChild, achieve, y)
                    end
                    y = y - 10
                end
            end
        end
    end

    scrollChild:SetHeight(math.abs(y) + 20)
end

function BuildBossDropList()
    dv.ClearWidgets()
    local y = -6
    local hasContent = false

    local expSel = dv.filters.bossdrops.expansions
    local hasExpansionFilter = HasAnySelection(expSel)

    local groups = {}
    for _, g in ipairs(dv.bossdrops or {}) do
        groups[#groups+1] = g
    end

    table.sort(groups, function(a,b)
        return (a.name or "") < (b.name or "")
    end)

    for _, group in ipairs(groups) do
        -- 1. Expansion Filter Check
        if not (hasExpansionFilter and not dv.filters.bossdrops.expansions[group.expansion]) then

            if group.items and #group.items > 0 then
                hasContent = true
            end

            -- 2. Simplified Collection Progress
            local total = 0
            local collected = 0

            for _, boss in ipairs(group.items or {}) do
                total = total + 1
                -- Use your actual collection check here
                if dv.IsItemCollected and dv.IsItemCollected(boss.id) then
                    collected = collected + 1
                end
            end

            -- 3. Create the Header
            local collapsed, newY = dv.CreateBossDropHeader(scrollChild, group, collected, total, y)
            y = newY

            -- 4. Create the Lines
            if not collapsed then
                for _, boss in ipairs(group.items or {}) do
                    -- This function draws the name/icon/checkmark
                    y = dv.CreateBossDropLine(scrollChild, boss, y)
                end
                y = y - 10
            end
        end
    end

    if not hasContent then
        local msg = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        msg:SetPoint("TOP", 0, -40)
        msg:SetText("No boss drop data available.")
        table.insert(dv.activeWidgets, msg)
    end

    scrollChild:SetHeight(math.max(800, math.abs(y) + 40))
end

function BuildVendorList()
    dv.ClearWidgets()

    local y = 0
    local hasContent = false

    local expSel = dv.filters.vendors.expansions
    local facSel = dv.filters.vendors.factions

    local hasExpansionFilter = HasAnySelection(expSel)
    local hasFactionFilter   = HasAnySelection(facSel)

    local groups = {}
    for _, group in ipairs(dv.npcs or {}) do
        groups[#groups + 1] = group
    end

    table.sort(groups, function(a, b)
        return (a.name or ""):lower() < (b.name or ""):lower()
    end)

    for _, group in ipairs(groups) do
        local passesExpansion =
            not hasExpansionFilter or expSel[group.expansion]

        if passesExpansion then
            local visibleVendors = {}

            for _, vendor in ipairs(group.vendors or {}) do

                local includeVendor = true
                if includeVendor then
                    local passesFaction =
                        not hasFactionFilter or facSel[vendor.faction]

                    if not passesFaction then
                        includeVendor = false
                    end
                end

                if dv.searchQuery then
                    local query = dv.searchQuery
                    local matchFound = false

                    local title = string.lower(vendor.title or "")
                    if string.find(title, query, 1, true) then
                        matchFound = true
                    end

                    if not matchFound then
                        --[[local goodies = dv.vendorGoodies and dv.vendorGoodies[vendor.id]

                        if goodies then
                            for _, itemID in ipairs(goodies) do]]
							local vendorData = dv.vendorGoodies and dv.vendorGoodies[vendor.id]

-- 🔥 compatibility layer
local goodies = vendorData and (vendorData.items or vendorData)

if goodies then
    for _, itemID in ipairs(goodies) do
                                if dv.ActiveItems and dv.ActiveItems[itemID] then
                                    local itemName = C_Item.GetItemNameByID(itemID)

                                    if itemName and
                                       string.find(string.lower(itemName), query, 1, true) then
                                        matchFound = true
                                        break
                                    end
                                end
                            end
                        end
                    end

                    if not matchFound then
                        includeVendor = false
                    end
                end

                if includeVendor then
                    local isFound = false

                    if vendorSettings.visited and vendorSettings.visited[vendor.id] then
                        local playerFaction = UnitFactionGroup("player")
                        playerFaction = playerFaction and playerFaction:lower()

                        if vendor.faction == playerFaction or vendor.faction == "neutral" then
                            isFound = true
                        end
                    end

                    vendor.__isFound = isFound
                end

                if includeVendor then
                    table.insert(visibleVendors, vendor)
                end
            end

            if #visibleVendors > 0 then
                hasContent = true

                table.sort(visibleVendors, function(a, b)
                    return (a.title or ""):lower() < (b.title or ""):lower()
                end)

                local total, completed = 0, 0
                for _, vendor in ipairs(group.vendors or {}) do
                    total = total + 1
                    if vendorSettings.visited and vendorSettings.visited[vendor.id] then
                        completed = completed + 1
                    end
                end

                local _, collapsed, newY =
                    dv.CreateVendorHeader(scrollChild, group, y, completed, total)
                y = newY

                if not collapsed then
                    for _, vendor in ipairs(visibleVendors) do
                        y = dv.CreateVendorLine(scrollChild, vendor, y)
                    end
                    y = y - 10
                end
            end
        end
    end

    if not hasContent then
        local msg = scrollChild:CreateFontString(nil, "OVERLAY")
        msg:SetFont(STANDARD_TEXT_FONT, 14)
        msg:SetPoint("TOP", 0, -50)
        msg:SetText("No vendors match these filters.")
        msg:SetTextColor(0.7, 0.7, 0.7)
        table.insert(dv.activeWidgets, msg)
    end

    scrollChild:SetHeight(math.abs(y) + 20)
end
-------------------------------------------------
-- 🔹 Merchant Frame  (unchanged)
-------------------------------------------------
local merchantHooked = false

local function HideMerchantCheckmarks()
    for i = 1, MERCHANT_ITEMS_PER_PAGE do
        local button = _G["MerchantItem" .. i .. "ItemButton"]
        if button and button.dvCheckmark then
            button.dvCheckmark:Hide()
        end
    end
end

local function HookMerchantFrame()
    if merchantHooked then return end
    merchantHooked = true

    hooksecurefunc("MerchantFrame_Update", function()
        if not MerchantFrame or not MerchantFrame:IsShown() then
            HideMerchantCheckmarks()
            return
        end

        if not vendorSettings.showMerchantCheckmarks then
            HideMerchantCheckmarks()
            return
        end

        local numMerchantItems = GetMerchantNumItems()

        for i = 1, MERCHANT_ITEMS_PER_PAGE do
            local index = ((MerchantFrame.page - 1) * MERCHANT_ITEMS_PER_PAGE) + i
            local button = _G["MerchantItem" .. i .. "ItemButton"]

            if button then
                if not button.dvCheckmark then
                    local check = button:CreateTexture(nil, "OVERLAY", nil, 7)
                    check:SetSize(18, 18)
                    check:SetPoint("BOTTOM", 0, -8)
                    check:SetTexture(COLLECTED_ICON_TEXTURE)
                    button.dvCheckmark = check
                end

                local checked = false

                if button:IsShown() and index <= numMerchantItems then
                    local itemLink = GetMerchantItemLink(index)
                    if itemLink then
                        local itemID = GetItemInfoInstant(itemLink)
                        if itemID and dv.IsItemCollected(itemID) then
                            checked = true
                        end
                    end
                end

                button.dvCheckmark:SetShown(checked)
            end
        end
    end)
end
-------------------------------------------------
-- 🔹 Options Frame  (unchanged)
-------------------------------------------------
local function CreateOptionsPanel()
    local configFrame = CreateFrame("Frame", "DV_ConfigFrame", UIParent)
    configFrame.name = "Decor Vendor"

    local title = configFrame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 16, -16)
    title:SetText("Decor Vendor Settings")
    title:SetTextColor(unpack(C.COLORS.GOLD))

    local yOffset = -60
    local spacing = 24

    local displayHeader = configFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    displayHeader:SetPoint("TOPLEFT", 16, yOffset)
    displayHeader:SetText("Display")
    displayHeader:SetTextColor(unpack(C.COLORS.GOLD))

    yOffset = yOffset - 30

    local minimapCheck = CreateFrame("CheckButton", nil, configFrame, "UICheckButtonTemplate")
    minimapCheck:SetPoint("TOPLEFT", 16, yOffset)
    minimapCheck.Text:SetFontObject(GameFontHighlight)
    minimapCheck.Text:SetText("Controls the Minimap Button")
    minimapCheck:SetChecked(vendorSettings.showMinimapButton)
    minimapCheck:SetScript("OnClick", function(self)
        local show = self:GetChecked()
        vendorSettings.showMinimapButton = show
        dbDV.minimap.hide = not show

        if LibDBIcon then
            if show then
                LibDBIcon:Show("DecorVendor")
            else
                LibDBIcon:Hide("DecorVendor")
            end
        end
    end)

    yOffset = yOffset - spacing

    local escCheck = CreateFrame("CheckButton", nil, configFrame, "UICheckButtonTemplate")
    escCheck:SetPoint("TOPLEFT", 16, yOffset)
    escCheck.Text:SetFontObject(GameFontHighlight)
    escCheck.Text:SetText("Close Main Frame upon hitting Escape Key")
    escCheck:SetChecked(vendorSettings.closeOnEsc)
    escCheck:SetScript("OnClick", function(self)
        vendorSettings.closeOnEsc = self:GetChecked()
        UpdateEscBehavior()
    end)

    yOffset = yOffset - spacing

    local markFoundCheck = CreateFrame("CheckButton", nil, configFrame, "UICheckButtonTemplate")
    markFoundCheck:SetPoint("TOPLEFT", 16, yOffset)
    markFoundCheck.Text:SetFontObject(GameFontHighlight)
    markFoundCheck.Text:SetText("Mark Found Vendors upon interaction")
    markFoundCheck:SetChecked(vendorSettings.markFoundVendors)
    markFoundCheck:SetScript("OnClick", function(self)
        vendorSettings.markFoundVendors = self:GetChecked()
        BuildVendorUI()
    end)

    yOffset = yOffset - spacing

    local hideCompletedCheck = CreateFrame("CheckButton", nil, configFrame, "UICheckButtonTemplate")
    hideCompletedCheck:SetPoint("TOPLEFT", 16, yOffset)
    hideCompletedCheck.Text:SetFontObject(GameFontHighlight)
    hideCompletedCheck.Text:SetText("Hide completed Quests and Achievements")
    hideCompletedCheck:SetChecked(vendorSettings.hideCompletedThings)
    hideCompletedCheck:SetScript("OnClick", function(self)
        vendorSettings.hideCompletedThings = self:GetChecked()
        BuildVendorUI()
    end)

    yOffset = yOffset - spacing

    local markCompletedCheck = CreateFrame("CheckButton", nil, configFrame, "UICheckButtonTemplate")
    markCompletedCheck:SetPoint("TOPLEFT", 16, yOffset)
    markCompletedCheck.Text:SetFontObject(GameFontHighlight)
    markCompletedCheck.Text:SetText("Mark completed Quests and Achievements")
    markCompletedCheck:SetChecked(vendorSettings.markCompletedThings)
    markCompletedCheck:SetScript("OnClick", function(self)
        vendorSettings.markCompletedThings = self:GetChecked()
        BuildVendorUI()
    end)

    yOffset = yOffset - spacing

    local waypointButtonCheck = CreateFrame("CheckButton", nil, configFrame, "UICheckButtonTemplate")
    waypointButtonCheck:SetPoint("TOPLEFT", 16, yOffset)
    waypointButtonCheck.Text:SetFontObject(GameFontHighlight)
    waypointButtonCheck.Text:SetText("Show the Dedicated Waypoint Button under Vendor Model")
    waypointButtonCheck:SetChecked(vendorSettings.showWaypointButton)
waypointButtonCheck:SetScript("OnClick", function(self)
    local isChecked = self:GetChecked()
    vendorSettings.showWaypointButton = isChecked
    
    -- 1. Refresh the list (standard)
    BuildVendorUI()

    -- 2. ⚡ The Fix: Check if a vendor is currently being previewed
    if dv.vendorWaypointBtn and dv.selectedVendor then
        if isChecked and dv.selectedVendor.mapID then
            dv.vendorWaypointBtn:Show()
        else
            dv.vendorWaypointBtn:Hide()
        end
    end
end)

    yOffset = yOffset - spacing

    local showMerchantCheckmark = CreateFrame("CheckButton", nil, configFrame, "UICheckButtonTemplate")
    showMerchantCheckmark:SetPoint("TOPLEFT", 16, yOffset)
    showMerchantCheckmark.Text:SetFontObject(GameFontHighlight)
    showMerchantCheckmark.Text:SetText("Include Merchant Checkmarks on Merchant Frame")
    showMerchantCheckmark:SetChecked(vendorSettings.showMerchantCheckmarks)
showMerchantCheckmark:SetScript("OnClick", function(self)
    local checked = self:GetChecked()
    vendorSettings.showMerchantCheckmarks = checked

    if not checked then
        HideMerchantCheckmarks()
    end

    if MerchantFrame and MerchantFrame:IsShown() then
        MerchantFrame_Update()
    end
end)

    yOffset = yOffset - spacing
	
	local vendorCheckmarkOption = CreateFrame("CheckButton", nil, configFrame, "UICheckButtonTemplate")
vendorCheckmarkOption:SetPoint("TOPLEFT", 16, yOffset)
vendorCheckmarkOption.Text:SetFontObject(GameFontHighlight)
vendorCheckmarkOption.Text:SetText("Include Vendor Checkmarks when clicking the Vendor Line")
vendorCheckmarkOption:SetChecked(vendorSettings.showVendorCheckmarks)
vendorCheckmarkOption:SetScript("OnClick", function(self)
    local isChecked = self:GetChecked()
    vendorSettings.showVendorCheckmarks = isChecked

    for _, container in pairs(popupIconCache) do
        if container:IsShown() and container.btn and container.btn.isCollected then
            container.checkFrame:SetShown(isChecked)
        end
    end
end)

yOffset = yOffset - spacing

    local openAchievementFrameCheckbox = CreateFrame("CheckButton", nil, configFrame, "UICheckButtonTemplate")
    openAchievementFrameCheckbox:SetPoint("TOPLEFT", 16, yOffset)
    openAchievementFrameCheckbox.Text:SetFontObject(GameFontHighlight)
    openAchievementFrameCheckbox.Text:SetText("Open Achievement Frame on Click")
    openAchievementFrameCheckbox:SetChecked(vendorSettings.openAchievementFrame)
    openAchievementFrameCheckbox:SetScript("OnClick", function(self)
        local checked = self:GetChecked()
        vendorSettings.openAchievementFrame = checked
    end)

    yOffset = yOffset - 40

    local scaleLabel = configFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    scaleLabel:SetPoint("TOPLEFT", 16, yOffset)
    scaleLabel:SetText("Scale for UI")
    scaleLabel:SetTextColor(unpack(C.COLORS.GOLD))

    yOffset = yOffset - 20

    local scaleSlider = CreateFrame("Slider", nil, configFrame, "MinimalSliderWithSteppersTemplate")
    scaleSlider:SetWidth(400)
    scaleSlider:SetPoint("TOPLEFT", 16, yOffset)
    scaleSlider:Init(vendorSettings.scale or 0.7, 0.5, 1.5, 20, {
        [MinimalSliderWithSteppersMixin.Label.Right] = function(value)
            return string.format("%.2f", value)
        end
    })

    scaleSlider.Slider:SetValueStep(0.05)
    scaleSlider:RegisterCallback(MinimalSliderWithSteppersMixin.Event.OnValueChanged, function(_, value)
        local rounded = tonumber(string.format("%.2f", value))
        vendorSettings.scale = rounded
        frame:SetScale(rounded)
        vendorPopup:SetScale(rounded)
    end)

    local footerText = configFrame:CreateFontString(nil, "OVERLAY")
    footerText:SetFont(STANDARD_TEXT_FONT, 18, "OUTLINE")
    footerText:SetPoint("BOTTOM", configFrame, "BOTTOM", 0, 28)
    footerText:SetText("|cffffdd00Decor Vendor|r • developed by |cff00aaffMidniteDestiny|r\n|cffffffffFirst to introduce vendor tracking|r")
    footerText:SetJustifyH("CENTER")

    local rootFrame = CreateFrame("Frame")
    rootFrame.name = "Decor Vendor"

    local rootCategory = Settings.RegisterCanvasLayoutCategory(rootFrame, "Decor Vendor")
    Settings.RegisterAddOnCategory(rootCategory)

    local generalCategory = Settings.RegisterCanvasLayoutSubcategory(
        rootCategory,
        configFrame,
        "General"
    )

    Settings.RegisterAddOnCategory(generalCategory)

    dv_optionsCategory = generalCategory
-------------------------------------------------
-- Events Subcategory
-------------------------------------------------
local eventsFrame = CreateFrame("Frame")
eventsFrame.name = "Events"
eventsFrame.parent = "Decor Vendor"

local title = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
title:SetPoint("TOPLEFT", 16, -16)
title:SetText("Event Decor & Promotions")

local y = -50

local function AddHeader(text, spacing)
    local fs = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    fs:SetPoint("TOPLEFT", 16, y)
    fs:SetTextColor(unpack(C.COLORS.GOLD))
    fs:SetText(text)
    local height = fs:GetStringHeight()
    y = y - (height + (spacing or 10))
end

local function AddBody(text, spacing)
    local fs = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
    fs:SetPoint("TOPLEFT", 16, y)
    fs:SetPoint("TOPRIGHT", -16, y)
    fs:SetJustifyH("LEFT")
    fs:SetJustifyV("TOP")
    fs:SetWordWrap(true)
    fs:SetText(text)
    local height = fs:GetStringHeight()
    y = y - (height + (spacing or 20))
end
local function CreateCopyBox(parent, anchor, textValue)
    local box = CreateFrame("EditBox", nil, parent, "InputBoxTemplate")
    box:SetSize(260, 24)
    box:SetPoint("LEFT", anchor, "RIGHT", 6, 0)
    box:SetAutoFocus(false)
    box:SetText(textValue)
    box:SetCursorPosition(0)

    box:SetScript("OnChar", function(self)
        self:SetText(textValue)
        self:HighlightText()
    end)

    box:SetScript("OnTextChanged", function(self)
        if self:GetText() ~= textValue then
            self:SetText(textValue)
            self:HighlightText()
        end
    end)

    box:SetScript("OnMouseUp", function(self)
        self:HighlightText()
    end)

    box:SetScript("OnEditFocusGained", function(self)
        self:HighlightText()
    end)

    box:EnableMouse(true)
    box:Disable()
    box:Enable()

    return box
end
-- CONTENT
AddBody("Some decor items come from limited-time or promotional events.", 20)

--[[AddHeader("How to Access", 10)
AddBody("Visit the website below and enter the required passwords by April 6th.", 15)
local function AddWebsiteRow(labelText, url, yPos)
    local label = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    label:SetPoint("TOPLEFT", 16, yPos)

    label:SetText(labelText)
    label:SetTextColor(1, 0.82, 0) -- GOLD

    local box = CreateCopyBox(eventsFrame, label, url)

    -- OPTIONAL: make URL look clickable (blue)
    box:SetTextColor(0.2, 0.8, 1)

    return yPos - 30
end

y = AddWebsiteRow("Website:", "https://craftyourworldpromo.com/", y)


local function AddPasswordRow(labelText, password, yPos)
    local label = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    label:SetPoint("TOPLEFT", 16, yPos)

    label:SetText(labelText)
    label:SetTextColor(1, 0.82, 0) -- GOLD

    local box = CreateCopyBox(eventsFrame, label, password)

    -- OPTIONAL: make passwords green (looks actionable 👀)
    box:SetTextColor(0.3, 1, 0.3)

    return yPos - 30
end

y = AddPasswordRow("Backboard and Hoop:", "Horde Board", y)
y = AddPasswordRow("Bluebird's Golden Cage:", "Azeroth Inspiration", y)
y = AddPasswordRow("Gift of Gilneas:", "Kalimdor Collage", y)]]
-- =========================
-- TWITCH DROP EVENT
-- =========================

y = y - 10

-- Header
local eventHeader = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
eventHeader:SetPoint("TOPLEFT", 16, y)
eventHeader:SetText("Twitch Drop Event")
eventHeader:SetTextColor(unpack(C.COLORS.GOLD))

y = y - 25

-- Start Time
local startText = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
startText:SetPoint("TOPLEFT", 16, y)
startText:SetText("Start Time:")
startText:SetTextColor(1, 0.82, 0)

local startValue = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
startValue:SetPoint("LEFT", startText, "RIGHT", 6, 0)
startValue:SetText("March 26, 3:00 pm PDT")
startValue:SetTextColor(0.3, 1, 0.3)

y = y - 20

-- End Time
local endText = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
endText:SetPoint("TOPLEFT", 16, y)
endText:SetText("End Time:")
endText:SetTextColor(1, 0.82, 0)

local endValue = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
endValue:SetPoint("LEFT", endText, "RIGHT", 6, 0)
endValue:SetText("April 23, 3:00 pm PDT")
endValue:SetTextColor(0.3, 1, 0.3)

y = y - 30

-- Description
local desc = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
desc:SetPoint("TOPLEFT", 16, y)
desc:SetPoint("TOPRIGHT", -16, y)
desc:SetJustifyH("LEFT")
desc:SetJustifyV("TOP")
desc:SetWordWrap(true)

desc:SetText(
    "Watch |cff00ccff4 hours|r of eligible World of Warcraft content on Twitch while Drops are active " ..
    "to earn the |cffc89bffCuddly Void Grrgle|r housing decor item."
)

y = y - 50

-- Header
local eventHeader = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
eventHeader:SetPoint("TOPLEFT", 16, y)
eventHeader:SetText("Upcoming Twitch Drop Event")
eventHeader:SetTextColor(unpack(C.COLORS.GOLD))

y = y - 25

-- Start Time
local startText = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
startText:SetPoint("TOPLEFT", 16, y)
startText:SetText("Start Time:")
startText:SetTextColor(1, 0.82, 0)

local startValue = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
startValue:SetPoint("LEFT", startText, "RIGHT", 6, 0)
startValue:SetText("April 23, 3:00 pm PDT")
startValue:SetTextColor(0.3, 1, 0.3)

y = y - 20

-- End Time
local endText = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
endText:SetPoint("TOPLEFT", 16, y)
endText:SetText("End Time:")
endText:SetTextColor(1, 0.82, 0)

local endValue = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
endValue:SetPoint("LEFT", endText, "RIGHT", 6, 0)
endValue:SetText("May 21, 3:00 pm PDT")
endValue:SetTextColor(0.3, 1, 0.3)

y = y - 30

-- Description
local desc = eventsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
desc:SetPoint("TOPLEFT", 16, y)
desc:SetPoint("TOPRIGHT", -16, y)
desc:SetJustifyH("LEFT")
desc:SetJustifyV("TOP")
desc:SetWordWrap(true)

desc:SetText(
    "Watch |cff00ccff4 hours|r of eligible World of Warcraft content on Twitch while Drops are active " ..
    "to earn the |cff00FF98Cuddly Pearl Grrgle|r housing decor item."
)

y = y - (desc:GetStringHeight() + 25)
-- REGISTER CATEGORY
local eventsCategory = Settings.RegisterCanvasLayoutSubcategory(
    rootCategory,
    eventsFrame,
    "Events"
)

Settings.RegisterAddOnCategory(eventsCategory)
    -------------------------------------------------
    -- Tips Subcategory
    -------------------------------------------------
    local tipsFrame = CreateFrame("Frame")
    tipsFrame.name = "Helpful Information"
    tipsFrame.parent = "Decor Vendor"

    local title = tipsFrame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 16, -16)
    title:SetText("Tips & Helpful Info")

    local y = -50
    local function AddHeader(text)
        local fs = tipsFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        fs:SetPoint("TOPLEFT", 16, y)
        fs:SetTextColor(unpack(C.COLORS.GOLD))
        fs:SetText(text)
        local height = fs:GetStringHeight()
        y = y - (height + 10)
    end

    local function AddBody(text)
        local fs = tipsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
        fs:SetPoint("TOPLEFT", 16, y)
        fs:SetPoint("TOPRIGHT", -16, y)
        fs:SetJustifyH("LEFT")
        fs:SetJustifyV("TOP")
        fs:SetWordWrap(true)
        fs:SetText(text)
        local height = fs:GetStringHeight()
        y = y - (height + 20)
    end

    AddHeader("Wowhead Links")
    AddBody("When left clicking achievements or quests the link is now directly under the decor preview instead of being in line.")

    AddHeader("Promo Vendors")
    AddBody("Items still show up as being owned regardless if you bought  or connected your account currently trying to find a solution.")

    AddHeader("Achievement Decor")
    AddBody("Left Click opens the decor item and a tooltip with the achievement information under the decor. Brought back the Achievement Frame!")

    AddHeader("Profession Decor")
    AddBody("Left Click opens both Decor Preview and the reagents window underneath it. Once you learn the recipe it gets marked off. Per character!")

    AddHeader("Boss Drops")
    AddBody("Left Click opens Decor Preview. Right Click will open the map to the Boss.")

    AddHeader("Vendor Decor")
    AddBody("Left Click opens vendor Preview with items below the vendor. Left clicking each item will open the catalogue.")

    AddHeader("Faction Color Indicators|r  |cffff2020Red|r = Horde • |cff4faaffBlue|r = Alliance • |cff00ff00Green|r = Neutral")
    AddBody("|cffFFD200Line Color Indicator|r  |cff9d9d9dGrey|r = Found Vendor, Completed Quest, and Achievements", 10)

    local tipsCategory = Settings.RegisterCanvasLayoutSubcategory(
        rootCategory,
        tipsFrame,
        "Helpful Information"
    )
    Settings.RegisterAddOnCategory(tipsCategory)

    -------------------------------------------------
    -- Support Subcategory
    -------------------------------------------------
    local supportFrame = CreateFrame("Frame")
    supportFrame.name = "Support"
    supportFrame.parent = "Decor Vendor"

    local title = supportFrame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 16, -16)
    title:SetText("Support Decor Vendor")

    local y = -50

    local function AddText(text, spacing)
        local fs = supportFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
        fs:SetPoint("TOPLEFT", 16, y)
        fs:SetWidth(800)
        fs:SetJustifyH("LEFT")
        fs:SetJustifyV("TOP")
        fs:SetText(text)
        y = y - (fs:GetStringHeight() + (spacing or 20))
    end

    AddText(
        "If you enjoy using Decor Vendor and want to support its development,\n" ..
        "all support is optional and deeply appreciated.",
        20
    )

    AddText("|cff999999Tip: Buttons highlight links for copying. Use Ctrl+C to copy.|r", 15)

    AddText("|cffFFD200Preferred Support|r", 10)

    local function CreateURLBox(parent, anchor, url)
        local box = CreateFrame("EditBox", nil, parent, "InputBoxTemplate")
        box:SetSize(260, 24)
        box:SetPoint("LEFT", anchor, "RIGHT", 6, 0)
        box:SetAutoFocus(false)
        box:SetText(url)
        box:SetCursorPosition(0)

        box:SetScript("OnChar", function(self)
            self:SetText(url)
            self:HighlightText()
        end)

        box:SetScript("OnTextChanged", function(self)
            if self:GetText() ~= url then
                self:SetText(url)
                self:HighlightText()
            end
        end)

        box:SetScript("OnMouseUp", function(self)
            self:HighlightText()
        end)

        box:SetScript("OnEditFocusGained", function(self)
            self:HighlightText()
        end)

        box:EnableMouse(true)
        box:Disable()
        box:Enable()

        return box
    end

    local function AddSupportLink(parent, labelText, url, yPos)
        local btn = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
        btn:SetSize(180, 26)
        btn:SetPoint("TOPLEFT", 16, yPos)
        btn:SetText(labelText)

        local box = CreateURLBox(parent, btn, url)

        btn:SetScript("OnClick", function()
            box:SetFocus()
            box:HighlightText()
        end)

        return yPos - 40
    end

    y = AddSupportLink(
        supportFrame,
        "PayPal (Preferred)",
        "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=lizbella123@gmail.com&currency_code=USD&item_name=Decor+Vendor",
        y
    )

    AddText("If the PayPal link above does not work, you can also use:", 5)

    y = AddSupportLink(
        supportFrame,
        "PayPal.me (Fallback)",
        "https://paypal.me/midnitedestiny",
        y
    )

    AddText("|cffFFD200Thank You|r", 10)
    AddText("Thank you to those who have supported me.", 15)
    AddText("|cffFFD200Contact Me|r", 10)
    AddText("If you truly need to reach me please use CurseForge messaging.", 10)
    AddText("|cffFFD200CurseForge Page|r", 10)

    y = AddSupportLink(
        supportFrame,
        "CurseForge",
        "https://www.curseforge.com/wow/addons/decor-vendor",
        y
    )

    AddText(
        "Thank you for supporting the addon!\n" ..
        "Your support helps ongoing updates, fixes, and new features.",
        20
    )


-- =========================
-- SUPPORTERS SECTION
-- =========================

-- BOX
local supportBox = CreateFrame("Frame", nil, supportFrame, "BackdropTemplate")
supportBox:SetSize(100, 80)
supportBox:SetPoint("BOTTOM", supportFrame, "BOTTOM", -120, 40)

supportBox:SetBackdrop({
    bgFile = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 12,
    insets = { left = 2, right = 2, top = 2, bottom = 2 }
})

supportBox:SetBackdropColor(0.05, 0.05, 0.08, 0.9)
supportBox:SetBackdropBorderColor(0.4, 0.3, 0.6, 1)

-- TEXT
local text = supportBox:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
text:SetPoint("TOP", supportBox, "TOP", 0, -6)
text:SetText("|cffc89bffSupporters|r\n\nAmy G.\nKim R.\nDanya O.\nIstvan K.\nDavid B.")
text:SetJustifyH("CENTER")

-- =========================
-- LEFT HEART
-- =========================
local leftHeart = CreateFrame("Frame", nil, supportFrame)
leftHeart:SetSize(24, 24)
leftHeart:SetPoint("RIGHT", supportBox, "LEFT", -8, -2)

local leftShadow = leftHeart:CreateTexture(nil, "BACKGROUND")
leftShadow:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\heart")
leftShadow:SetAllPoints()
leftShadow:SetPoint("CENTER", 1, -1)
leftShadow:SetVertexColor(0, 0, 0, 1)

local leftIcon = leftHeart:CreateTexture(nil, "ARTWORK")
leftIcon:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\heart")
leftIcon:SetVertexColor(0.2, 0.6, 1)
leftIcon:SetAllPoints()

-- =========================
-- RIGHT HEART
-- =========================
local rightHeart = CreateFrame("Frame", nil, supportFrame)
rightHeart:SetSize(24, 24)
rightHeart:SetPoint("LEFT", supportBox, "RIGHT", 8, -2)

local rightShadow = rightHeart:CreateTexture(nil, "BACKGROUND")
rightShadow:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\heart")
rightShadow:SetAllPoints()
rightShadow:SetPoint("CENTER", 1, -1)
rightShadow:SetVertexColor(0, 0, 0, 1)

local rightIcon = rightHeart:CreateTexture(nil, "ARTWORK")
rightIcon:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\heart")
rightIcon:SetVertexColor(0.2, 0.6, 1)
rightIcon:SetAllPoints()

    local supportCategory = Settings.RegisterCanvasLayoutSubcategory(
        rootCategory,
        supportFrame,
        "Support"
    )
    Settings.RegisterAddOnCategory(supportCategory)

    -------------------------------------------------
    -- Credits and Inpiration Subcategory
    -------------------------------------------------
	local creditsFrame = CreateFrame("Frame")
    creditsFrame.name = "Credits & Inspiration"
	creditsFrame.parent = "Decor Vendor"

	local title = creditsFrame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
	title:SetPoint("TOPLEFT", 16, -16)
	title:SetText("Credits & Inspiration")

	local y = -50

	local function AddHeader(text, spacing)
    local fs = creditsFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    fs:SetPoint("TOPLEFT", 16, y)
    fs:SetTextColor(1, 0.82, 0) -- gold
    fs:SetText(text)

    y = y - (fs:GetStringHeight() + (spacing or 10))
end

	local function AddBody(text, spacing)
    local fs = creditsFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
    fs:SetPoint("TOPLEFT", 16, y)
    fs:SetPoint("TOPRIGHT", -16, y)
    fs:SetJustifyH("LEFT")
    fs:SetWordWrap(true)
    fs:SetText(text)

    y = y - (fs:GetStringHeight() + (spacing or 20))
end

	-- CONTENT
	AddHeader("Acknowledgements", 10)

	AddBody(
    "Decor Vendor was developed independently, with inspiration from other addons in the housing and collection space.",
    15
)

	AddBody(
    "Some features were inspired by addons in the housing space, including |cffFFD200Homestead|r, and |cffFFD200Housing Codex|r.",
    20
)

	AddBody(
    "All functionality and implementation within Decor Vendor has been created from the ground up to provide a focused and streamlined experience.",
    20
)
local art = creditsFrame:CreateTexture(nil, "ARTWORK")
    art:SetSize(400, 400)
    art:SetPoint("TOPLEFT", 16, -180)
    art:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\about")
	
	-- REGISTER
	Settings.RegisterCanvasLayoutSubcategory(
    rootCategory,
    creditsFrame,
    "Credits & Inspiration"
)
    -------------------------------------------------
    -- About Subcategory
    -------------------------------------------------
    local aboutFrame = CreateFrame("Frame")
    aboutFrame.name = "About"
    aboutFrame.parent = "Decor Vendor"

    local title = aboutFrame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 16, -16)
    title:SetTextColor(unpack(C.COLORS.GOLD))
    title:SetText("Decor Vendor")

    local desc = aboutFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
    desc:SetWidth(800)
    desc:SetPoint("TOPLEFT", 16, -50)
    desc:SetJustifyH("LEFT")
    desc:SetJustifyV("TOP")
    desc:SetText(
"Created by MidniteDestiny\n\n" ..
"Decor Vendor was built out of a love for collecting everything Azeroth has to offer.\n\n" ..

"What started as a simple idea turned into a full system for tracking vendors, rewards, and hidden treasures .\n\n" ..

"Inside you'll find:\n" ..
"• Vendors and where to find them\n" ..
"• Quest & achievement rewards\n" ..
"• Boss drop previews\n" ..
"• Profession unlocks\n" ..
"• Live 3D previews of decor and NPCs\n\n" ..

"Everything is designed to be easy to use, accurate, and constantly evolving.\n\n" ..

"Made by a gamer, for gamers.\n\n"
    )

    local art = aboutFrame:CreateTexture(nil, "ARTWORK")
    art:SetSize(280, 280)
    art:SetPoint("TOPLEFT", 16, -270)
    art:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\cutie")

    local footer = aboutFrame:CreateFontString(nil, "ARTWORK", "GameFontNormal")
    footer:SetPoint("TOP", art, "BOTTOM", 0, -20)
    footer:SetWidth(800)
    footer:SetJustifyH("CENTER")
    footer:SetText(
        "|cffffdd00Decor Vendor|r • developed by |cff00aaffMidniteDestiny|r\n" ..
        "|cffffffffFirst to introduce vendor tracking|r"
    )

    local aboutCategory = Settings.RegisterCanvasLayoutSubcategory(
        rootCategory,
        aboutFrame,
        "About"
    )
    Settings.RegisterAddOnCategory(aboutCategory)
end
-------------------------------------------------
-- 🔹 UI Builder  (unchanged)
-------------------------------------------------
function BuildVendorUI()
    if not dv.catalogReady then
        return
    end

    if dv.currentTab == "vendors" then
        BuildVendorList()

    elseif dv.currentTab == "professions" then
        BuildProfessionList()

    elseif dv.currentTab == "quests" then
        BuildQuestList()

    elseif dv.currentTab == "achievements" then
        BuildAchievementList()
	
	--[[elseif dv.currentTab == "currency" then
		BuildCurrencyTab()]]

    elseif dv.currentTab == "bossdrops" then
        BuildBossDropList()
    end
	

    dv.filtersJustChanged = false
end


--[[
local function HookMerchantFrame()
    hooksecurefunc("MerchantFrame_Update", function()
        if not vendorSettings.showMerchantCheckmarks then return end

        local guid = UnitGUID("npc")
        if not guid then return end
        local vendorID = select(6, strsplit("-", guid))
        vendorID = tonumber(vendorID)
        --if not vendorID or not dv.vendorGoodies[vendorID] then
		local vendorData = dv.vendorGoodies[vendorID]
local goodies = vendorData and (vendorData.items or vendorData)

if not vendorID or not goodies then
            HideMerchantCheckmarks()
            return
        end

        local numMerchantItems = GetMerchantNumItems()

        for i = 1, MERCHANT_ITEMS_PER_PAGE do
            local index = (((MerchantFrame.page - 1) * MERCHANT_ITEMS_PER_PAGE) + i)
            local button = _G["MerchantItem"..i.."ItemButton"]

            if button and button:IsShown() then
                if not button.dvCheckmark then
                    local check = button:CreateTexture(nil, "OVERLAY", nil, 7)
                    check:SetSize(18, 18)
                    check:SetPoint("BOTTOM", 0, -8)
                    check:SetTexture(COLLECTED_ICON_TEXTURE)
                    button.dvCheckmark = check
                end

                local checked = false
                if index <= numMerchantItems then
                    local itemLink = GetMerchantItemLink(index)
                    if itemLink then
                        local itemID = GetItemInfoInstant(itemLink)
                        if itemID and dv.IsItemCollected(itemID) then
                            checked = true
                        end
                    end
                end

                button.dvCheckmark:SetShown(checked)
            end
        end
    end)
end
]]
function dv.OpenMainUI()
    if C_HousingCatalog and C_HousingCatalog.RequestHousingMarketInfoRefresh then
        dv.waitingForMarketData = true
        C_HousingCatalog.RequestHousingMarketInfoRefresh()
    end

    BuildVendorUI()
end
-------------------------------------------------
-- 🔹 Addon Loaded Init  (unchanged)
-------------------------------------------------
local init = CreateFrame("Frame")
init:RegisterEvent("ADDON_LOADED")
init:RegisterEvent("PLAYER_ENTERING_WORLD")
init:RegisterEvent("MERCHANT_SHOW")
init:RegisterEvent("HOUSING_MARKET_AVAILABILITY_UPDATED")
init:RegisterEvent("ACHIEVEMENT_EARNED")
init:RegisterEvent("QUEST_TURNED_IN")
init:RegisterEvent("HOUSE_DECOR_ADDED_TO_CHEST")

local function InitDefaults()
    vendorSettings = vendorSettings or {}

    vendorSettings.completedDrop         = vendorSettings.completedDrop or {}
	vendorSettings.completedDropNoXP = vendorSettings.completedDropNoXP or {}
    vendorSettings.visited               = vendorSettings.visited or {}
    vendorSettings.showWaypointButton    = vendorSettings.showWaypointButton or false
    if vendorSettings.showMinimapButton  == nil then vendorSettings.showMinimapButton  = true end
    vendorSettings.completedAchievs      = vendorSettings.completedAchievs or {}
    if vendorSettings.closeOnEsc          == nil then vendorSettings.closeOnEsc          = true end
    if vendorSettings.scale               == nil then vendorSettings.scale               = 1.0 end
    if vendorSettings.hideCompletedThings == nil then vendorSettings.hideCompletedThings = false end
    if vendorSettings.markCompletedThings == nil then vendorSettings.markCompletedThings = false end
    if vendorSettings.showMerchantCheckmarks == nil then vendorSettings.showMerchantCheckmarks = false end
    if vendorSettings.showVendorCheckmarks == nil then vendorSettings.showVendorCheckmarks = false end
    vendorSettings.openAchievementFrame  = vendorSettings.openAchievementFrame ~= false

    dbDV = dbDV or {}
    dbDV.minimap = dbDV.minimap or {}
    dbDV.minimap.hide = not vendorSettings.showMinimapButton

    dv.currentTab = dv.currentTab or "vendors"
end

init:SetScript("OnEvent", function(self, event, loadedAddon, ...)

    if event == "ADDON_LOADED" and loadedAddon == addonName then
        InitDefaults()
        UpdateEscBehavior()
        dv.BuildProfessionLookup()
        local ldb = LibStub:GetLibrary("LibDataBroker-1.1", true)
        if ldb then
            local dataobj = ldb:NewDataObject("DecorVendor", {
                type  = "launcher",
                icon  = "Interface\\AddOns\\Decorvendor\\Assets\\decorvendoricon.tga",
                label = "DecorVendor",
                text  = "DecorVendor",
                name  = "DecorVendor",

                OnClick = function(_, button)
                    if button == "LeftButton" then
                        if not frame:IsShown() then
                            dv.OpenMainUI()
                        end
                        frame:SetShown(not frame:IsShown())
                    elseif button == "RightButton" then
                        if dv_optionsCategory then
                            Settings.OpenToCategory(dv_optionsCategory:GetID())
                        end
                    end
                end
            })

            function dataobj:OnTooltipShow()
                self:AddLine("|cffffffffDecor Vendor|r")
                self:AddLine("|cff00ff00<Left Click>|r Toggle window")
                self:AddLine("|cff00ff00<Right Click>|r Options")
            end

            LibDBIcon:Register("DecorVendor", dataobj, dbDV.minimap)

            if vendorSettings.showMinimapButton then
                LibDBIcon:Show("DecorVendor")
            else
                LibDBIcon:Hide("DecorVendor")
            end
        end

        frame:SetScale(vendorSettings.scale or .70)
        CreateOptionsPanel()
        UpdateSidebarForTab()
        return
    end

    if event == "PLAYER_ENTERING_WORLD" then
        dv.catalogReady = false

        C_Timer.After(1, function()
            dv.catalogReady = true

            dv.decorIdToItemId = {}
            for itemID, data in pairs(dv.ActiveItems or {}) do
                dv.decorIdToItemId[data.decorID] = itemID
            end

            wipe(vendorSessionCache)
            dv.collectionCache = {}

            BuildVendorUI()
            HookMerchantFrame()
        end)
    end

    if event == "MERCHANT_SHOW" then
        dv.waitingForMarketData = true

        if C_HousingCatalog and C_HousingCatalog.RequestHousingMarketInfoRefresh then
            C_HousingCatalog.RequestHousingMarketInfoRefresh()
        end
        return
    end

    if event == "HOUSING_MARKET_AVAILABILITY_UPDATED" then
        if dv.waitingForMarketData then
            dv.waitingForMarketData = false
            dv.catalogReady = true

            wipe(vendorSessionCache)
            dv.collectionCache = {}

            BuildVendorUI()
        end
        return
    end

    if event == "ACHIEVEMENT_EARNED" or event == "QUEST_TURNED_IN" then
        C_Timer.After(0.5, BuildVendorUI)
        return
    end

    if event == "HOUSE_DECOR_ADDED_TO_CHEST" then
        if not dv.catalogReady then return end

        local decorID = ...
        local itemID = dv.decorIdToItemId and dv.decorIdToItemId[decorID]
        if not itemID then return end

        local itemData = dv.ActiveItems[itemID]
local noxp = itemData and itemData.noxp

if noxp then
    vendorSettings.completedDropNoXP[itemID] = true
else
    vendorSettings.completedDrop[itemID] = true
end

        wipe(vendorSessionCache)

        BuildVendorUI()
    end
end)

-------------------------------------------------
-- 🔹 Slash Commands  (unchanged)
-------------------------------------------------
SLASH_DECORVENDOR1 = "/decor"
SLASH_DECORVENDOR2 = "/dv"
SLASH_DECORVENDOR3 = "/decorvendor"
SlashCmdList["DECORVENDOR"] = function()
    if not frame:IsShown() then
        BuildVendorUI()
        frame:Show()
    else
        frame:Hide()
    end
end

-------------------------------------------------
-- 🔹 Addon Compartment  (unchanged)
-------------------------------------------------
function DecorVendor_OnAddonCompartmentClick(addonName, button)
    if button == "LeftButton" then
        if frame:IsShown() then
            frame:Hide()
        else
            frame:Show()
        end
    elseif button == "RightButton" then
        if Settings and dv_optionsCategory then
            Settings.OpenToCategory(dv_optionsCategory:GetID())
        end
    end
end

function DecorVendor_OnAddonCompartmentEnter(addonName, button)
    GameTooltip:SetOwner(button, "ANCHOR_LEFT")
    GameTooltip:SetText("Decor Vendor", 1, 0.82, 0)
    GameTooltip:AddLine("Left-click: Open Decor Vendor", 1, 1, 1)
    GameTooltip:AddLine("Right-click: Open Settings", 1, 1, 1)
    GameTooltip:Show()
end

function DecorVendor_OnAddonCompartmentLeave(addonName, button)
    GameTooltip:Hide()
end
