--[[
============================================================
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 = .90,
    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,
	hideCollectedBossDrops = 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 = 30
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()

-------------------------------------------------
-- 🔹 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(format(C.VERSION_TEXT, C.VERSION), 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()

local settingsBtn = CreateFrame("Button", nil, DV_MainFrame, "UIPanelButtonTemplate")
settingsBtn:SetSize(90, 22)
settingsBtn:SetPoint("BOTTOMRIGHT", DV_MainFrame, "BOTTOMRIGHT", -10, 10)
settingsBtn:SetText("Options")
settingsBtn:SetScript("OnEnter", function(self)
    GameTooltip:SetOwner(self, "ANCHOR_TOP")
    GameTooltip:AddLine("Open Settings", 1, 1, 1)
    GameTooltip:AddLine("Configure filters, display options, and more.", 0.8, 0.8, 0.8, true)
    GameTooltip:Show()
end)

settingsBtn:SetScript("OnLeave", GameTooltip_Hide)
settingsBtn:SetScript("OnClick", function()
    Settings.OpenToCategory(dv_optionsCategory:GetID())
end)
-------------------------------------------------
-- 🔹 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 dv.GetFactionColor(faction)
    faction = faction and string.lower(faction)

    if faction == "alliance" then
        return unpack(C.COLORS.ALLIANCE)
    elseif faction == "horde" then
        return unpack(C.COLORS.HORDE)
    else
        return unpack(C.COLORS.NEUTRAL)
    end
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

function dv.GetPlayerRenownLevel(factionID)
    local data = C_MajorFactions.GetMajorFactionData(factionID)
    if data then
        return data.renownLevel or 0
    end
    return 0
end

function dv.GetSubfactionTier(factionID)
    local data = C_MajorFactions.GetMajorFactionData(factionID)
    if data then
        return data.renownLevel or 0 
    end
    return 0
end

function dv.GetVendorRequirementType(vendorID)
    local vendorData = dv.vendorGoodies and dv.vendorGoodies[vendorID]
    local items = vendorData and (vendorData.items or vendorData)

    if not items then return "none" end

    local foundType = nil

    for _, itemID in ipairs(items) do
        local itemData = dv.ActiveItems and dv.ActiveItems[itemID]

        if itemData then
            local req = itemData.requirement

            if req and req.type then
                local t = string.lower(req.type)

                -- 🔥 PRIORITY: subfaction wins immediately
                if t == "subfaction" then
                    return "subfaction"
                end

                -- store fallback
                foundType = t
            end
        end
    end

    return foundType or "none"
end

function dv.DebugItemRequirement(itemID)
    local itemData = dv.ActiveItems[itemID]

    if not itemData then
        print("❌ Item not found:", itemID)
        return
    end

    local req = itemData.requirement

    if not req then
        print("ℹ️ No requirement for item:", itemID)
        return
    end

    if req.type == "renown" then
        local factionID = dv.renownFactionIDs[req.faction]
        local playerRenown = dv.GetPlayerRenownLevel(factionID)

        print("===== RENOWN DEBUG =====")
        print("Item:", itemID)
        print("Faction:", req.faction)
        print("Required:", req.level)
        print("Player:", playerRenown)

        if not factionID then
            print("❌ Missing factionID mapping!")
        end

    elseif req.type == "reputation" then
        print("===== REPUTATION DEBUG =====")
        print("Item:", itemID)
        print("Faction:", req.faction)
        print("Required Rank:", dv.reputationRanks[req.rank] or "UNKNOWN")
		
	elseif req.type == "subfaction" then
    local name = dv.subfactionRanks[req.faction] and dv.subfactionRanks[req.faction][req.tier]

    print("===== SUBFACTION DEBUG =====")
    print("Item:", itemID)
    print("Faction:", req.faction)
    print("Required Tier:", name or "UNKNOWN")	
    end
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 or issecretvalue(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.vendorNotes then dv.vendorNotes:Hide() end
    if dv.achievementPanel then dv.achievementPanel: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:ClearAllPoints()
    dv.modelTitle:SetPoint("TOPLEFT", dv.modelDivider, "BOTTOMLEFT", 0, -6)
    dv.modelTitle:SetPoint("TOPRIGHT", dv.modelDivider, "BOTTOMRIGHT", 0, -6)

    dv.modelTitle:SetText("Select an item")
    dv.modelTitle:Show()
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)
-- =========================
-- CREATE ONCE
-- =========================
if not dv.modelTitle then
    dv.modelTitle = dv.itemContainer:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    dv.modelTitle:SetFont(STANDARD_TEXT_FONT, 15, "OUTLINE")
    dv.modelTitle:SetJustifyH("CENTER")
    dv.modelTitle:SetDrawLayer("OVERLAY", 7)
end

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 = {}

local vendorZoneText = vendorPopup:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
vendorZoneText:ClearAllPoints()
vendorZoneText:SetPoint("TOPLEFT", dv.itemContainer, "TOPLEFT", 10, -10)
vendorZoneText:SetWidth(190)
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", vendorZoneText, "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 = 40
    local margin   = 10
    local columns  = 10

    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

	    
    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 .90)
    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, -260)
    dv.vendorNotes:SetPoint("TOPRIGHT", dv.modelDivider, "BOTTOMRIGHT", -10, -260)

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

	-- 🔥 STATUS ICON (Found / Not Found)
	local icon = line:CreateTexture(nil, "OVERLAY")
	icon:SetSize(14, 14)
	icon:SetPoint("LEFT", 0, 0)

    -- 🔥 FACTION ICON
    line.factionIcon = line:CreateTexture(nil, "OVERLAY")
    line.factionIcon:SetSize(16, 16)
    line.factionIcon:SetPoint("LEFT", icon, "RIGHT", 2, 0)
	
    local text = line:CreateFontString(nil, "OVERLAY")
    text:SetFont(STANDARD_TEXT_FONT, 12,"OUTLINE")
    text:SetPoint("LEFT", line.factionIcon, "RIGHT", 4, 0)
    text:SetText(vendor.title or "Unknown Vendor")

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

	-- 🔥 ICON LOGIC
	if isFound then
    icon:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
    icon:SetVertexColor(0.2, 1, 0.2)
    icon:SetAlpha(0.9)
	else
    icon:SetTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up")
    icon:SetVertexColor(1, 0.2, 0.2)
    icon:SetAlpha(0.75)
	end

    -- 🔥 FACTION ICON LOGIC
    local f = vendor.faction and string.lower(vendor.faction)

    if f == "alliance" then
        line.factionIcon:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\blue")
    elseif f == "horde" then
        line.factionIcon:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\red")
    elseif f == "neutral" then
        line.factionIcon:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\neutral")
    else
        line.factionIcon:Hide()
    end

    line.factionIcon:SetAlpha(0.9)
    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 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 and vendor then
    dv.modelTitle:SetText(vendor.title or "Vendor")

    local faction = vendor.faction or "neutral"
    dv.modelTitle:SetTextColor(dv.GetFactionColor(faction))
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(profession.name or "Unknown", completed, total))

    header.progress = header:CreateFontString(nil, "OVERLAY")
    header.progress:SetFont(STANDARD_TEXT_FONT, 12,"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")
				dv.modelTitle:SetTextColor(1, 1, 1)
            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)
    -- 🔥 determine states (prefer precomputed flags)
    local learned = profItem.__learned
    local collected = profItem.__collected

    -- fallback (safety if flags weren’t set)
    if learned == nil then
        if profItem.spell then
            learned = IsSpellKnown(profItem.spell) or IsPlayerSpell(profItem.spell)
        else
            learned = false
        end
    end

    if collected == nil then
        if profItem.id and dv.IsItemCollected then
            collected = dv.IsItemCollected(profItem.id)
        else
            collected = false
        end
    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")
	
	-- 🔥 STATUS ICON
	local icon = line:CreateTexture(nil, "OVERLAY")
	icon:SetSize(14, 14)
	icon:SetPoint("LEFT", 0, 0)

    local nameText = line:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    nameText:SetFont(STANDARD_TEXT_FONT, 13, "OUTLINE")
    nameText:SetPoint("LEFT", icon, "RIGHT", 4, -2)
    nameText:SetJustifyH("LEFT")
    nameText:SetText("• Loading item...")

	-- 🔥 ICON + COLOR LOGIC
	if collected then
    -- 🟢 OWNED
    icon:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
    icon:SetVertexColor(0.2, 1, 0.2)
    icon:SetAlpha(0.9)

    nameText:SetTextColor(0.2, 1, 0.2)

	elseif learned then
    -- 🟠 LEARNED ONLY
    icon:SetTexture("Interface\\Icons\\INV_Scroll_03")
    icon:SetVertexColor(1, 0.6, 0.1)
    icon:SetAlpha(0.9)

    nameText:SetTextColor(1, 0.5, 0)

	else
    -- 🔴 NOT LEARNED
    icon:SetTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up")
    icon:SetVertexColor(1, 0.2, 0.2)
    icon:SetAlpha(0.75)

    nameText:SetTextColor(0.95, 0.95, 0.95)
	end

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

    -- click
    line:SetScript("OnClick", function(self, button)
        if button == "LeftButton" then
            dv.UpdateProfessionPreview(profItem)
            dv.ShowReagentsPopup(profItem)
        end
    end)

    -- tooltip
    line:SetScript("OnEnter", function(self)
        SetCursor("INSPECT_CURSOR")
        GameTooltip:SetOwner(self, "ANCHOR_LEFT")
        GameTooltip:SetItemByID(profItem.id)

        GameTooltip:AddLine(" ")

        if collected then
            GameTooltip:AddLine("|cff00ff00Owned Decor|r")
        elseif learned then
            GameTooltip:AddLine("|cffff9900Recipe Learned|r")
        else
            GameTooltip:AddLine("|cffaaaaaaNot Learned|r")
        end

        GameTooltip:AddLine("|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(headerName or "Unknown", completed, total))

    header.progress = header:CreateFontString(nil, "OVERLAY")
    header.progress:SetFont(STANDARD_TEXT_FONT, 12,"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
	dv.modelTitle:ClearAllPoints()
	dv.modelTitle:SetPoint("TOPLEFT", dv.modelDivider, "BOTTOMLEFT", 0, -6)
	dv.modelTitle:SetPoint("TOPRIGHT", dv.modelDivider, "BOTTOMRIGHT", 0, -6)

	dv.modelTitle:SetText(achievement.title or "Achievement Preview")
	dv.modelTitle:SetTextColor(1, 1, 1)
	dv.modelTitle:Show()

    -- =========================
    -- 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.modelTitle, "BOTTOMLEFT", 0, -6)
	dv.achievementWowheadWrapper:SetPoint("TOPRIGHT", dv.modelTitle, "BOTTOMRIGHT", 0, -6)
    dv.achievementWowheadBox:SetText("https://www.wowhead.com/achievement=" .. achievement.id)
    dv.achievementWowheadWrapper:Show()
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")

	-- 🔥 STATUS ICON (✔ / ❌)
	line.statusIcon = line:CreateTexture(nil, "OVERLAY")
	line.statusIcon:SetSize(16, 16)
	line.statusIcon:SetPoint("LEFT", 0, 0)

	-- 🔥 FACTION ICON
	line.factionIcon = line:CreateTexture(nil, "OVERLAY")
	line.factionIcon:SetSize(16, 16)
	line.factionIcon:SetPoint("LEFT", line.statusIcon, "RIGHT", 2, 0)

    line.text = line:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    line.text:SetPoint("LEFT", line.factionIcon, "RIGHT", 4, 0)
    line.text:SetFont(STANDARD_TEXT_FONT, 14)
    line.text:SetText(name)
	
	-- 🔥 STATUS ICON LOGIC
	if isCompleted then
    line.statusIcon:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
    line.statusIcon:SetVertexColor(0.2, 1, 0.2)
    line.statusIcon:SetAlpha(0.9)
    line.statusIcon:Show()
	else
    line.statusIcon:SetTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up")
    line.statusIcon:SetVertexColor(1, 0.2, 0.2)
    line.statusIcon:SetAlpha(0.75)
    line.statusIcon:Show()
	end

	-- 🔥 FACTION ICON LOGIC
	local f = achievement.faction and string.lower(achievement.faction)

	if f == "alliance" then
    line.factionIcon:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\blue")
	elseif f == "horde" then
    line.factionIcon:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\red")
	elseif f == "neutral" then
    line.factionIcon:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\neutral")
	else
    line.factionIcon:Hide()
	end

	line.factionIcon:SetAlpha(0.9)

	local function SetBaseColor()
    if isCompleted then
        if vendorSettings.markCompletedThings then
            -- ✅ greyed out
            line.text:SetTextColor(0.62, 0.62, 0.62)
            line.text:SetAlpha(0.7)
        else
            -- ✅ use faction color even if completed
            if 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}))
            else
                line.text:SetTextColor(0.9, 0.9, 0.9)
            end

            line.text:SetAlpha(1)
        end
    else
        -- ❌ not completed → normal faction coloring
        if 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}))
        else
            line.text:SetTextColor(0.9, 0.9, 0.9)
        end

        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(groupName or "Unknown", completed, total))

    header.progress = header:CreateFontString(nil, "OVERLAY")
    header.progress:SetFont(STANDARD_TEXT_FONT, 12,"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")

    -- 🔥 STATUS ICON
    line.statusIcon = line:CreateTexture(nil, "OVERLAY")
    line.statusIcon:SetSize(16, 16)
    line.statusIcon:SetPoint("LEFT", 0, 0)

    -- 🔥 FACTION ICON
    line.factionIcon = line:CreateTexture(nil, "OVERLAY")
    line.factionIcon:SetSize(16, 16)
    line.factionIcon:SetPoint("LEFT", line.statusIcon, "RIGHT", 2, 0)

    -- 🔥 STATUS ICON LOGIC
    if isCompleted then
        line.statusIcon:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
        line.statusIcon:SetVertexColor(0.2, 1, 0.2)
        line.statusIcon:SetAlpha(0.9)
        line.statusIcon:Show()
    else
        line.statusIcon:SetTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up")
        line.statusIcon:SetVertexColor(1, 0.2, 0.2)
        line.statusIcon:SetAlpha(0.75)
        line.statusIcon:Show()
    end

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

    -- 🔥 FACTION ICON LOGIC
    local f = quest.faction and string.lower(quest.faction)

    if f == "alliance" then
        line.factionIcon:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\blue")
    elseif f == "horde" then
        line.factionIcon:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\red")
    elseif f == "neutral" then
        line.factionIcon:SetTexture("Interface\\AddOns\\DecorVendor\\Assets\\neutral")
    else
        line.factionIcon:Hide()
    end

    line.factionIcon:SetAlpha(0.9)

    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")
            dv.modelTitle:SetTextColor(1, 1, 1)
        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.modelTitle, "BOTTOMLEFT", 0, -6)
        dv.questWowheadWrapper:SetPoint("TOPRIGHT", dv.modelTitle, "BOTTOMRIGHT", 0, -6)
        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, -6)
        dv.questNotes:SetPoint("TOPRIGHT", dv.questWowheadWrapper, "BOTTOMRIGHT", 0, -6)

        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")
			dv.modelTitle:SetTextColor(1, 1, 1)
        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

			
        local cam = dv.DecorCameraOverrides[itemData.model3D]
		
            if camera.TargetActor then
                camera:TargetActor(actor, true)
            end

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

    if cam and cam.zoom and camera.SetZoomDistance then
        camera:SetZoomDistance(cam.zoom)
    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.modelTitle, "BOTTOMLEFT", 0, -6)
        dv.bossNotes:SetPoint("TOPRIGHT", dv.modelTitle, "BOTTOMRIGHT", 0, -6)
        
        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 .. "|cff00fbffEvent:|r " .. loc .. "\n"
    end
	
	if itemData.zone then
        local zone = itemData.zone
        fullBossInfo = fullBossInfo .. "\n|cff00fbffLocation:|r " .. zone .. "\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")

    -- 🔥 Status icon
	local icon = line:CreateTexture(nil, "OVERLAY")
	icon:SetSize(14, 14)
	icon:SetPoint("LEFT", 0, 0)

    -- Item name
    local nameFS = line:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    nameFS:SetPoint("LEFT", icon, "RIGHT", 4, 0) -- 🔥 shifted right

    local item = Item:CreateFromItemID(itemData.id)
    item:ContinueOnItemLoad(function()
        nameFS:SetText(item:GetItemName() or "Loading...")
    end)

	local isCollected = dv.IsItemCollected(itemData.id)

	if isCollected then
    icon:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
    icon:SetVertexColor(0.2, 1, 0.2) -- green
    icon:SetAlpha(0.9)
    nameFS:SetTextColor(0.2, 1, 0.2)
	else
    icon:SetTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up")
    icon:SetVertexColor(1, 0.2, 0.2) -- red
    icon:SetAlpha(0.85)
    nameFS:SetTextColor(1, 1, 1) -- keep text white for readability
	end

    -- Click logic (unchanged)
    line:SetScript("OnClick", function(_, button)
        if button == "LeftButton" then
            dv.UpdateBossPreview(itemData)

        elseif button == "RightButton" then
            if InCombatLockdown() then return end

            local rares = dv.rareEvents and dv.rareEvents[itemData.bossevent]

            if rares and #rares > 0 then
                if TomTom then
                    for _, rare in ipairs(rares) do
                        if rare.x and rare.y then
                            TomTom:AddWaypoint(
                                itemData.mapID,
                                rare.x / 100,
                                rare.y / 100,
                                { title = rare.name }
                            )
                        end
                    end
                else
                    local first = rares[1]
                    if first and first.x and first.y then
                        local waypoint = UiMapPoint.CreateFromCoordinates(
                            itemData.mapID,
                            first.x / 100,
                            first.y / 100
                        )
                        C_Map.SetUserWaypoint(waypoint)
                        C_SuperTrack.SetSuperTrackedUserWaypoint(true)
                    end
                end

                if itemData.mapID then
                    C_Map.OpenWorldMap(itemData.mapID)
                end

                return
            end

            if itemData.mapID 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 catSel = dv.filters.professions.professions 
	local hasCategoryFilter = HasAnySelection(catSel)

	local expSel = dv.filters.professions.expansions
	local hasExpFilter = HasAnySelection(expSel)
	
    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
        do
            local visible = {}
            local completedCount = 0
            local totalCount = 0

for _, item in ipairs(profession.items or {}) do
    local include = true

    -- 🔥 EXPANSION FILTER
    if hasExpFilter then
        if not item.expansion or not expSel[item.expansion] then
            include = false
        end
    end

    -- 🔥 CATEGORY FILTER (ADD RIGHT HERE)
    if include and hasCategoryFilter then
        local cat = item.category or profession.name

        if not catSel[cat] then
            include = false
        end
    end

    if not include then
        -- skip this item
    else
                totalCount = totalCount + 1

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

local collected = false
if item.id and dv.IsItemCollected then
    collected = dv.IsItemCollected(item.id)
end

-- 🔥 mark completion
if learned or collected then
    completedCount = completedCount + 1
end

-- 🔥 store flags ON the item for later use
item.__learned = learned
item.__collected = collected

table.insert(visible, item)
				end
            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 catSel = dv.filters.quests.categories

    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
        local visible = {}

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

                -- 🔥 EXPANSION FILTER (NEW)
                if hasExpansionFilter then
                    if not quest.expansion or not expSel[quest.expansion] then
                        include = false
                    end
                end

                -- existing logic
                if
                    include and 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
                -- 🔥 CATEGORY FILTER (ADD THIS RIGHT HERE)
                if include and hasCategoryFilter then
                    local cat = quest.category or group.name

                    if not catSel[cat] 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

    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 catSel = dv.filters.bossdrops.categories
    local hasExpansionFilter = HasAnySelection(expSel)
    local hasCategoryFilter = HasAnySelection(catSel)

    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
        -- 🔹 Expansion filter
        if not (hasExpansionFilter and not dv.filters.bossdrops.expansions[group.expansion]) then
            -- 🔥 STEP 1: Count ONLY visible items
            local total = 0
            local collected = 0

            for _, boss in ipairs(group.items or {}) do
                local include = true
                local isCollected = dv.IsItemCollected and dv.IsItemCollected(boss.id)

                -- 🔥 CATEGORY FILTER
                if include and hasCategoryFilter then
                    local cat = boss.category or group.name
                    if not catSel[cat] then
                        include = false
                    end
                end

                -- 🔥 HIDE COLLECTED
                if include and vendorSettings.hideCollectedBossDrops and isCollected then
                    include = false
                end

                if include then
                    total = total + 1
                    if isCollected then
                        collected = collected + 1
                    end
                end
            end

            -- 🔥 STEP 2: HIDE EMPTY GROUPS
            if total > 0 then
                hasContent = true

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

                -- 🔥 STEP 3: Create visible lines only
                if not collapsed then
                    for _, boss in ipairs(group.items or {}) do
                        local include = true
                        local isCollected = dv.IsItemCollected and dv.IsItemCollected(boss.id)

                        -- 🔥 CATEGORY FILTER
                        if include and hasCategoryFilter then
                            local cat = boss.category or group.name
                            if not catSel[cat] then
                                include = false
                            end
                        end

                        -- 🔥 HIDE COLLECTED
                        if include and vendorSettings.hideCollectedBossDrops and isCollected then
                            include = false
                        end

                        if include then
                            y = dv.CreateBossDropLine(scrollChild, boss, y)
                        end
                    end
                    y = y - 10
                end
            end
        end
    end

    -- 🔹 No content message
    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 catSel = dv.filters.vendors.categories

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

    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

                -- 🔹 FACTION FILTER
                local passesFaction = not hasFactionFilter or facSel[vendor.faction]

                if not passesFaction then
                    includeVendor = false
                end

-- 🔥 CATEGORY + REQUIREMENT FILTER (final fix)
if includeVendor and hasCategoryFilter then
    local matches = false

    local cat = vendor.category or "none"

    local vendorData = dv.vendorGoodies and dv.vendorGoodies[vendor.id]
    local vendorItems = vendorData and (vendorData.items or vendorData)

    local hasRequirement = false

    -- 🔹 Check requirements
    if vendorItems then
        for _, itemID in ipairs(vendorItems) do
            local itemData = dv.ActiveItems and dv.ActiveItems[itemID]
            local req = itemData and itemData.requirement

            if req then
                hasRequirement = true

                if catSel[req.type] then
                    matches = true
                    break
                end
            end
        end
    end

    -- 🔥 NO REQUIREMENT ONLY WORKS IF vendor truly has none
    if not hasRequirement and catSel["none"] then
        matches = true
    end

    -- 🔥 IMPORTANT: ONLY apply vendor category if NO requirement filters are selected
    local usingRequirementFilter =
        catSel["none"] or
        catSel["reputation"] or
        catSel["renown"] or
        catSel["subfaction"] or
        catSel["friendship"]

    if not usingRequirementFilter then
        if catSel[cat] then
            matches = true
        end
    end

    if not matches then
        includeVendor = false
    end
end
                -- 🔍 SEARCH FILTER (unchanged)
                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 vendorData = dv.vendorGoodies and dv.vendorGoodies[vendor.id]
                        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

                -- 🔹 FOUND STATUS (unchanged)
                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 hidebosscheck = CreateFrame("CheckButton", nil, configFrame, "UICheckButtonTemplate")
    hidebosscheck:SetPoint("TOPLEFT", 16, yOffset)
    hidebosscheck.Text:SetFontObject(GameFontHighlight)
    hidebosscheck.Text:SetText("Hide completed Boss Drops")
    hidebosscheck:SetChecked(vendorSettings.hideCollectedBossDrops )
    hidebosscheck:SetScript("OnClick", function(self)
        vendorSettings.hideCollectedBossDrops  = 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("Current 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("Some Twitch Promo items are currently taking forever to show up to the promo vendors please be patient as Blizzard has to hotfix them in at times.")

    AddHeader("Achievement Decor")
    AddBody("Left Click opens the decor item and also opens the Achievement Frame if option is enabled!")

    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 but once you learn the decor it is warband marked off!")

    AddHeader("Boss Drops")
    AddBody("Left Click opens Decor Preview. Right Click will open the map to the Boss or pins the map for certain drops.")

    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)

    -------------------------------------------------
    -- 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 == "bossdrops" then
        BuildBossDropList()
    end
	

    dv.filtersJustChanged = false
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
	if vendorSettings.hideCollectedBossDrops == nil then vendorSettings.hideCollectedBossDrops = false end

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

SLASH_DVDEBUG1 = "/dvdebug"

SlashCmdList["DVDEBUG"] = function(msg)
    local itemID = tonumber(msg)

    if not itemID then
        print("Usage: /dvdebug 248333")
        return
    end

    dv.DebugItemRequirement(itemID)
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
