-- compendium.lua (v1.6.5 - The "Era Badge" Edition)
local ADDON_NAME, WDIGI = ...
WDIGI.Compendium = WDIGI.Compendium or {}

local SIDEBAR_WIDTH = 220 
local ROW_HEIGHT = 36 

local COLORS = {
    BG_MAIN         = {0.07, 0.07, 0.08, 0.98},
    BG_SIDEBAR      = {0.10, 0.10, 0.12, 1.00},
    BG_HDR_ZONE     = {0.11, 0.15, 0.24, 1.00},
    BG_HDR_SUBTYPE  = {0.15, 0.15, 0.17, 1.00},
    BG_HDR_COMMON   = {0.22, 0.19, 0.12, 0.85}, -- Muted Charcoal Gold
    BG_HDR_RARE     = {0.10, 0.16, 0.28, 0.85}, -- Elegant Slate Blue
    BG_HDR_EXTREME  = {0.20, 0.10, 0.24, 0.85}, -- Deep Twilight Void Purple
    AMBER_TEXT      = {1.00, 0.70, 0.00, 1.00},
    TEXT_DIM        = {0.50, 0.50, 0.55, 1.00},
    BORDER          = {0.20, 0.20, 0.22, 1.00},
    CARD_BG         = {0.05, 0.05, 0.06, 1.00},    
}

local ENTITY_ICONS = {
    ITEM = 133633,
    NPC  = 135911,
    ZONE = 132320,
}

WDIGI.Data.VENDOR_TYPE_NAMES = {
    [1] = "Sold by", [2] = "Source", [3] = "Contained in",
    [4] = "Disenchanted from", [5] = "Gathered from", [6] = "Crafted by",
    [7] = "Learned from", [8] = "Quest Reward", [99] = "Source",
}

local VT = WDIGI.Data.VENDOR_TYPE
WDIGI.Data.VENDOR_ICONS = {
    [VT.SOLD_BY]           = 133633, [VT.DROPPED_BY]        = 132318,
    [VT.CRAFTED_BY]        = 136240, [VT.LEARNED_FROM]      = 135894,
    [VT.CONTAINED_IN]      = 132594, [VT.DISENCHANTED_FROM] = 236207,
    [VT.GATHERED_FROM]     = 134187, [VT.QUEST_REWARD]      = 132320,
    [VT.UNKNOWN]           = 134400,
}

------------------------------------------------------------
-- MINIMAP BUTTON
------------------------------------------------------------
function WDIGI.Compendium:CreateMinimapButton()
    if not WDIGI.Settings.Get("WDIGI_EnableCompendium_Experimental") then return end
    if WDIGI_MinimapButton then return end
    
    local btn = CreateFrame("Button", "WDIGI_MinimapButton", Minimap)
    btn:SetSize(31, 31); btn:SetFrameLevel(8); btn:SetToplevel(true)
    btn:RegisterForClicks("LeftButtonUp")
    
    local icon = btn:CreateTexture(nil, "BACKGROUND")
    icon:SetTexture(ENTITY_ICONS.ITEM) 
    icon:SetSize(20, 20); icon:SetPoint("CENTER", 0, 0)
    
    local border = btn:CreateTexture(nil, "OVERLAY")
    border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
    border:SetSize(53, 53); border:SetPoint("TOPLEFT", 0, 0)
    
    btn:SetPoint("TOPLEFT", Minimap, "TOPLEFT", -5, -5)
    btn:SetScript("OnClick", function() self:Toggle() end)
    
    btn:SetMovable(true); btn:RegisterForDrag("LeftButton")
    btn:SetScript("OnDragStart", btn.StartMoving); btn:SetScript("OnDragStop", btn.StopMovingOrSizing)
end

------------------------------------------------------------
-- WINDOW CREATION
------------------------------------------------------------
function WDIGI.Compendium:Create()
    if self.Frame then return end
    local scale = 1.4 
    self.Scale = scale

    local f = CreateFrame("Frame", "WDIGI_Compendium", UIParent, "BackdropTemplate")
    f:SetSize(765, 520); f:SetPoint("LEFT"); f:SetMovable(true); f:EnableMouse(true); f:SetScale(scale)
    f:SetFrameStrata("HIGH"); f:SetToplevel(true)
    f:RegisterForDrag("LeftButton"); f:SetScript("OnDragStart", f.StartMoving); f:SetScript("OnDragStop", f.StopMovingOrSizing)
    
    f:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1})
    f:SetBackdropColor(unpack(COLORS.BG_MAIN)); f:SetBackdropBorderColor(unpack(COLORS.BORDER))
    self.Frame = f

    -- TOOLBAR
    local toolbar = CreateFrame("Frame", nil, f, "BackdropTemplate")
    toolbar:SetSize(f:GetWidth() - 10, 40); toolbar:SetPoint("TOPLEFT", 5, -5)
    toolbar:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8"})
    toolbar:SetBackdropColor(0.05, 0.05, 0.06, 1)

    -- ICON: Using the defined constant and explicit parenting
    local titleIcon = toolbar:CreateTexture(nil, "OVERLAY")
    titleIcon:SetSize(32, 32)
    titleIcon:SetPoint("LEFT", 5, 0)
    titleIcon:SetTexture(ENTITY_ICONS.ITEM) -- Ensure this is 133633
    titleIcon:SetDesaturated(true)
    titleIcon:SetAlpha(0.7)

    -- TITLE: Centered with Amber Yellow text
    local titleText = toolbar:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    titleText:SetPoint("RIGHT", -35, 2)
    titleText:SetText("COMPENDIUM")
    titleText:SetTextColor(unpack(COLORS.AMBER_TEXT)) -- Amber Yellow {1.0, 0.7, 0.0}

    -- SEARCH FIELD: Re-anchored to the RIGHT of the Icon
    local eb = CreateFrame("EditBox", nil, toolbar, "BackdropTemplate")
    eb:SetSize(SIDEBAR_WIDTH - 55, 24)
    eb:SetPoint("LEFT", titleIcon, "RIGHT", 10, 0) -- This prevents the overlap
    eb:SetAutoFocus(false)
    eb:SetFontObject("ChatFontNormal")
    eb:SetTextInsets(10, 10, 0, 0)
    eb:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1})
    eb:SetBackdropColor(0.02, 0.02, 0.02, 1); eb:SetBackdropBorderColor(0.3, 0.3, 0.3, 1)   
    
    eb:SetScript("OnTextChanged", function(this, userInput)
        if not userInput then return end
        local query = this:GetText():trim()
        if #query < 3 then return end
        if this.timer then this.timer:Cancel() end
        this.timer = C_Timer.NewTimer(0.4, function() WDIGI.Compendium:PerformSearch(query) end)
    end)
    self.SearchBox = eb

    -- 2. Action Buttons
    local resetBtn = CreateFrame("Button", nil, toolbar, "BackdropTemplate")
    resetBtn:SetSize(55, 24); resetBtn:SetPoint("LEFT", eb, "RIGHT", 6, 0)
    resetBtn:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1})
    resetBtn:SetBackdropColor(0.15, 0.15, 0.18, 1); resetBtn:SetBackdropBorderColor(0.3, 0.3, 0.3, 1)
    local rt = resetBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    rt:SetText("CLEAR"); rt:SetPoint("CENTER", 0, 0); rt:SetTextColor(0.8, 0.8, 0.8)
    resetBtn:SetScript("OnClick", function() 
        eb:SetText(""); WDIGI.Compendium.LastQuery = ""
        WDIGI.Compendium:SetDetailLayout(false)
        
        -- Fix: Check if rows exist before iterating
        if WDIGI.Compendium.ListRows then
            for _, row in ipairs(WDIGI.Compendium.ListRows) do row:Hide() end
        end
        if WDIGI.Compendium.DetailRows then
            for _, row in ipairs(WDIGI.Compendium.DetailRows) do row:Hide() end
        end
        
        if WDIGI.Compendium.SplashFrame then WDIGI.Compendium.SplashFrame:Show() end
    end)

    -- REFRESH BUTTON
    local refreshBtn = CreateFrame("Button", nil, toolbar, "BackdropTemplate")
    refreshBtn:SetSize(24, 24); refreshBtn:SetPoint("LEFT", resetBtn, "RIGHT", 4, 0)
    refreshBtn:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1})
    refreshBtn:SetBackdropColor(0.15, 0.15, 0.18, 1); refreshBtn:SetBackdropBorderColor(0.3, 0.3, 0.3, 1)

    -- New Texture Logic
    local rfIcon = refreshBtn:CreateTexture(nil, "OVERLAY")
    rfIcon:SetTexture("Interface\\Buttons\\UI-RefreshButton")
    rfIcon:SetSize(16, 16)
    rfIcon:SetPoint("CENTER", 0, 0)
    rfIcon:SetVertexColor(0, 0.8, 1.0) 

    refreshBtn:SetScript("OnClick", function() 
        local query = eb:GetText()
        if #query >= 3 then WDIGI.Compendium.LastQuery = ""; WDIGI.Compendium:PerformSearch(query) end
    end)

    refreshBtn:SetScript("OnEnter", function(self) 
        self:SetBackdropColor(0.2, 0.2, 0.2, 1) 
        rfIcon:SetVertexColor(1, 1, 1)
    end)

    refreshBtn:SetScript("OnLeave", function(self) 
        self:SetBackdropColor(0.15, 0.15, 0.18, 1) 
        rfIcon:SetVertexColor(0, 0.8, 1.0) 
    end)

    local close = CreateFrame("Button", nil, f, "BackdropTemplate")
    close:SetSize(24, 24); close:SetPoint("TOPRIGHT", -10, -10)
    close:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8"})
    close:SetBackdropColor(0.6, 0.2, 0.2, 0.8)
    local closeText = close:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    closeText:SetText("X"); closeText:SetPoint("CENTER", 0, 0); closeText:SetTextColor(1, 1, 1)
    close:SetScript("OnClick", function() f:Hide() end)

    local count = toolbar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    count:SetPoint("RIGHT", -10, 0); count:SetTextColor(0, 0.8, 1.0)
    self.ResultCount = count

    -- 3. CONTENT PANELS
    local leftScroll = CreateFrame("ScrollFrame", nil, f, "UIPanelScrollFrameTemplate")
    leftScroll:SetPoint("TOPLEFT", 5, -50); leftScroll:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", -5, 5)
    leftScroll:SetWidth(SIDEBAR_WIDTH); leftScroll.ScrollBar:Hide(); leftScroll.ScrollBar:SetAlpha(0)
    self.LeftScroll = leftScroll

    local leftContent = CreateFrame("Frame", nil, leftScroll)
    leftContent:SetSize(SIDEBAR_WIDTH - 2, 1); leftScroll:SetScrollChild(leftContent)
    self.LeftContent = leftContent

    local rightScroll = CreateFrame("ScrollFrame", "WDIGIRightScroll", f, "UIPanelScrollFrameTemplate")
    rightScroll:SetPoint("TOPLEFT", f, "TOPLEFT", SIDEBAR_WIDTH + 5, -50)
    rightScroll:SetPoint("BOTTOMRIGHT", -5, 5) 
    self.RightScrollFrame = rightScroll
    rightScroll.ScrollBar:Hide(); rightScroll.ScrollBar:SetAlpha(0)

    local rightContent = CreateFrame("Frame", nil, rightScroll)
    rightContent:SetSize(f:GetWidth() - SIDEBAR_WIDTH - 15, 1)
    rightScroll:SetScrollChild(rightContent)
    self.RightContent = rightContent

    -- 4. EXPERIMENTAL SPLASH SCREEN
    local sf = CreateFrame("Frame", nil, f)
    sf:SetPoint("TOPLEFT", SIDEBAR_WIDTH + 15, -60)
    sf:SetPoint("BOTTOMRIGHT", -15, 15)
    
    local st = sf:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    st:SetPoint("TOPLEFT", -10, 0); st:SetText("Compendium |cffff0000(Experimental)|r")
    
    local sd = sf:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    sd:SetPoint("TOPLEFT", st, "BOTTOMLEFT", 0, -20); sd:SetJustifyH("LEFT")
    sd:SetText("This feature is currently in active development.\n\n" ..
            "|cff00ccffHow to use:|r\n" ..
            "1. Enter an Item or NPC name in the search box.\n" ..
            "2. Wait for the list to populate (requires 3+ characters).\n" ..
            "3. Select a result to view sources and costs.\n\n" ..
            "|cffffcc00Data Note:|r\n" ..
            "We are still refning out data, so likely there are inaccurate or missing items.\n\n" ..
            "|cffffcc00Performance Note:|r\n" ..
            "- Searching uses a coroutine-based engine to prevent game stuttering.\n" ..
            "- Large result sets are capped for speed.\n\n" .. 
            "Find this addon useful? Buy me a coffee!\n"  .. 
            "|cff00ccffhttps://buymeacoffee.com/slashzero|r")
    
    self.SplashFrame = sf
end

function WDIGI.Compendium:PerformSearch(query)
    query = query:trim():lower()
    if #query < 3 then return end
    if self.LastQuery == query then return end

    if self.SplashFrame then self.SplashFrame:Hide() end

    self.LastQuery = query
    
    if self.SearchThread then self.SearchThread = nil end

    self.ListRows = self.ListRows or {}
    self.DetailRows = self.DetailRows or {}

    for _, row in ipairs(self.ListRows) do if row then row:Hide() end end
    for _, row in ipairs(self.DetailRows) do if row then row:Hide() end end
    
    self.LeftScroll:SetVerticalScroll(0)
    self.RightScrollFrame:SetVerticalScroll(0)

    self.SearchThread = coroutine.create(function()
        -- 1. Call our high-speed UniversalSearch
        local searchResult = WDIGI.Data.UniversalSearch(query)
        
        local itemResults = searchResult.items or {}
        local vendorResults = {}

        -- 1. FORCE WIDE LAYOUT FOR RESULTS
        WDIGI.Compendium:SetDetailLayout(false)
        
        -- 2. BRIDGE: Map the 'creatures' found in search to the sidebar format
        -- results.creatures contains the vendor names (strings)
        if searchResult.creatures then
            for _, vendorName in ipairs(searchResult.creatures) do
                -- Compendium expects a table with a 'name' field
                table.insert(vendorResults, { name = vendorName })
            end
        end

        -- 3. RENDER: This puts Vendors on top, Items below
        WDIGI.Compendium:RenderList(itemResults, vendorResults)

        -- 4. AUTO-SELECT: If the search matches an item exactly, show it
        if #itemResults > 0 then
            local itemName = GetItemInfo(itemResults[1])
            if itemName and itemName:lower() == query then
                WDIGI.Compendium:SetDetailLayout(true)
                WDIGI.Compendium:RenderItemDetails(itemResults[1])
            end
        end
    end)

    local ticker
    ticker = C_Timer.NewTicker(0.01, function()
        if not self.SearchThread or coroutine.status(self.SearchThread) == "dead" then
            ticker:Cancel()
        else
            local success, err = coroutine.resume(self.SearchThread)
            if not success then
                print("|cffff0000WDIGI Search Error:|r", err)
                ticker:Cancel()
            end
        end
    end)
end

------------------------------------------------------------
-- RENDER LISTS
------------------------------------------------------------
function WDIGI.Compendium:SetDetailLayout(showDetail)
    if not self.Frame then return end
    if showDetail then
        -- Split View (Detail Mode)
        self.RightScrollFrame:Show()
        self.LeftScroll:SetWidth(SIDEBAR_WIDTH)
        self.LeftContent:SetWidth(SIDEBAR_WIDTH - 2)
    else
        -- Wide View (Search Mode)
        self.RightScrollFrame:Hide()
        self.LeftScroll:SetWidth(self.Frame:GetWidth() - 10)
        self.LeftContent:SetWidth(self.Frame:GetWidth() - 25)
    end
end

function WDIGI.Compendium:RenderList(items, vendors)
    local offset, count = 0, 0
    local currentWidth = self.LeftContent:GetWidth()
    local isWide = not self.RightScrollFrame:IsShown() -- Check if we are in wide mode

    for _, v in ipairs(vendors) do
        count = count + 1
        local row = self:GetOrCreateListRow(count)
        self:StyleListRow(row, "LIST")
        row:SetWidth(currentWidth)
        
        -- Fix Alignment for Wide Mode
        if isWide then
            row.name:SetJustifyH("CENTER")
            row.name:ClearAllPoints()
            row.name:SetPoint("CENTER", row, "CENTER", 0, 0)
        else
            row.name:SetJustifyH("LEFT")
            row.name:ClearAllPoints()
            row.name:SetPoint("LEFT", 34, 0)
        end

        row.id = v.name; row.mode = "VENDOR"
        row.name:SetText("|cff00ff00" .. v.name .. "|r")
        row.icon:SetTexture(ENTITY_ICONS.NPC)
        
        -- 🚀 RESET EXPLICIT CLICK ROUTINE
        row:SetScript("OnClick", function(s) self:RenderVendorDetails(s.id) end)
        
        row:SetPoint("TOPLEFT", self.LeftContent, "TOPLEFT", 0, -offset)
        row:Show(); offset = offset + 34
    end

    for _, itemID in ipairs(items) do
        count = count + 1; if count > 30 then break end
        local row = self:GetOrCreateListRow(count)
        self:StyleListRow(row, "LIST")
        row:SetWidth(currentWidth)
        
        -- Fix Alignment for Wide Mode
        if isWide then
            row.name:SetJustifyH("CENTER")
            row.name:ClearAllPoints()
            row.name:SetPoint("CENTER", row, "CENTER", 0, 0)
        else
            row.name:SetJustifyH("LEFT")
            row.name:ClearAllPoints()
            row.name:SetPoint("LEFT", 34, 0)
        end

        row.id = itemID; row.mode = "ITEM"
        
        -- 🚀 Central utility injection handles fallbacks and async lazy-loads safely
        WDIGI.Data.SafePopulateItemRow(row, itemID, true)
        
        -- 🚀 RESET EXPLICIT CLICK ROUTINE
        row:SetScript("OnClick", function(s) self:RenderItemDetails(s.id) end)
        
        row:SetPoint("TOPLEFT", self.LeftContent, "TOPLEFT", 0, -offset)
        row:Show(); offset = offset + 34
    end
    self.LeftContent:SetHeight(offset)
end

------------------------------------------------------------
-- DETAIL RENDERING - BULLETPROOF EDITION
------------------------------------------------------------
function WDIGI.Compendium:RenderVendorDetails(vendorName)
    self:SetDetailLayout(true)
    if not vendorName then return end

    if self.SplashFrame then self.SplashFrame:Hide() end
    for _, row in ipairs(self.DetailRows) do row:Hide() end
    for _, row in ipairs(self.ListRows) do row:Hide() end
    
    local inventory = VENDOR_INDEX_LIST[vendorName] or {}
    local inventoryString = "Inventory"
    local leftOffset = 0
    local rightOffset, rightCount = 0, 0 

    -- 1. FEATURED NPC CARD (Left Sidebar)
    local feat = self:GetOrCreateListRow(100)
    self:StyleListRow(feat, "FEATURED")
    
    local formattedVendorName = tostring(vendorName or ""):lower():gsub("(%a)([%w_']*)", function(first, rest)
        return first:upper() .. rest
    end)
    
    feat.name:SetText("|cff00ff00" .. formattedVendorName .. "|r")
    feat.icon:SetTexture(135911) 
    feat.icon:SetTexCoord(0.2, 0.8, 0.15, 0.85)
    
    -- 2. PEEK AT NPC DATA (Safe namespace routing)
    local mapID, tx, ty, vType = 0, 0, 0, 99
    local localVT = WDIGI.Data.VT or {} -- Safely handle your local enum configuration
    
    if inventory[1] then
        local firstItemData = WDIGI.Data.ParseItemData(inventory[1])
        for _, s in ipairs(firstItemData) do
            if s.vendorName:lower() == vendorName:lower() then 
                mapID, tx, ty, vType = s.mapID, s.tx, s.ty, s.vendorType
                
                if s.vendorType == localVT.DROPPED_BY then inventoryString = "Loot Table Items" 
                elseif s.vendorType == localVT.SOLD_BY then inventoryString = "Items Sold"
                elseif s.vendorType == localVT.CONTAINED_IN then inventoryString = "Containers Holding"
                elseif s.vendorType == localVT.DISENCHANTED_FROM then inventoryString = "Disenchanting Sources"
                elseif s.vendorType == localVT.GATHERED_FROM then inventoryString = "Gathering Nodes"
                elseif s.vendorType == localVT.CRAFTED_BY then inventoryString = "Crafting Recipes"
                elseif s.vendorType == localVT.LEARNED_FROM then inventoryString = "Learn Sources"
                elseif s.vendorType == localVT.QUEST_REWARD then inventoryString = "Quest Rewards"
                end
                break 
            end
        end
    end

    local mapInfo = C_Map.GetMapInfo(mapID)
    local zoneName = mapInfo and mapInfo.name or "Unknown Zone"
    feat.meta:SetText(string.format("|cffffffff%s|r\n%0.1f, %0.1f\n\n%s: %d Items", zoneName, tx, ty, inventoryString, #inventory))
    feat:SetPoint("TOPLEFT", self.LeftContent, "TOPLEFT", 0, 0)
    feat:Show(); leftOffset = 180

    -- 3. DYNAMIC HEADER (Right Pane)
    rightCount = rightCount + 1
    local header = self:GetOrCreateDetailRow(rightCount, "ZONE")
    header.name:SetText(vendorName:upper())
    header:SetPoint("TOPLEFT", self.RightContent, "TOPLEFT", 0, 0)
    header:Show(); rightOffset = 30

    -- 4. THE NAVIGATION ROW
    if tx > 0 then
        rightCount = rightCount + 1
        local locRow = self:GetOrCreateDetailRow(rightCount, "VENDOR")
        locRow.name:SetText("|cff00ff00[GO TO LOCATION]|r - " .. zoneName)
        locRow.price:SetText("") 
        locRow.coords:SetText(string.format("%.1f, %.1f", tx, ty))
        locRow.icon:SetTexture(135911)
        locRow.pin:Show()
        
        locRow.data = { mapID = mapID, tx = tx, ty = ty, vendorName = vendorName, vendorType = vType }
        locRow.itemID = 0 
        
        locRow:SetPoint("TOPLEFT", self.RightContent, "TOPLEFT", 0, -rightOffset)
        locRow:Show(); rightOffset = rightOffset + 38
    end

    -- 5. INVENTORY SUB-HEADER
    local isDropVendor = (vType ~= 1) 
    if not isDropVendor then
        local invHeaderText = (vType == 1) and "ITEMS SOLD" or "KNOWN DROPS"
        rightCount = rightCount + 1
        local invHeader = self:GetOrCreateDetailRow(rightCount, "SUBTYPE")
        invHeader.name:SetText(invHeaderText)
        invHeader:SetPoint("TOPLEFT", self.RightContent, "TOPLEFT", 0, -rightOffset)
        invHeader:Show(); rightOffset = rightOffset + 24
    end

    -- 6. INVENTORY LIST
    local rowWidth = self.Frame:GetWidth() - (self.SIDEBAR_WIDTH or 180) - 15
    local processedInventory = WDIGI.Data.ProcessAndSortSources(inventory, vendorName)

    local showedKnownHeader = false
    local showedPossibleHeader = false
    local showedRareHeader = false

    for _, itemObj in ipairs(processedInventory) do
        local itemID = itemObj.itemID
        local sortWeight = itemObj.sortWeight
        local isDrop = itemObj.isDrop

        -- 🏷️ DYNAMIC HEADER INJECTION FOR DROPS
        if isDropVendor and isDrop then
            if sortWeight >= 5 and not showedKnownHeader then
                rightCount = rightCount + 1
                local subHeader = self:GetOrCreateDetailRow(rightCount, "SUBTYPE_COMMON")
                subHeader.name:SetText("|Tinterface/icons/inv_misc_coin_01:14:14:0:1|t  |cffffffffCOMMON DROPS (>= 5%)|r")
                subHeader:SetPoint("TOPLEFT", self.RightContent, "TOPLEFT", 0, -rightOffset)
                subHeader:Show(); rightOffset = rightOffset + 26
                showedKnownHeader = true
            elseif sortWeight < 5 and sortWeight >= 1 and not showedPossibleHeader then
                rightCount = rightCount + 1
                local subHeader = self:GetOrCreateDetailRow(rightCount, "SUBTYPE_RARE")
                subHeader.name:SetText("|Tinterface/icons/inv_misc_gem_sapphire_02:14:14:0:1|t  |cff3399ffRARE DROPS (1% - 5%)|r")
                subHeader:SetPoint("TOPLEFT", self.RightContent, "TOPLEFT", 0, -rightOffset)
                subHeader:Show(); rightOffset = rightOffset + 26
                showedPossibleHeader = true
            elseif sortWeight < 1 and not showedRareHeader then
                rightCount = rightCount + 1
                local subHeader = self:GetOrCreateDetailRow(rightCount, "SUBTYPE_EXTREME")
                subHeader.name:SetText("|Tinterface/icons/inv_misc_gem_amethyst_03:14:14:0:1|t  |cffdf66ffEXTREMELY RARE DROPS (< 1%)|r")
                subHeader:SetPoint("TOPLEFT", self.RightContent, "TOPLEFT", 0, -rightOffset)
                subHeader:Show(); rightOffset = rightOffset + 26
                showedRareHeader = true
            end
        end

        -- Render Item Row Component
        rightCount = rightCount + 1
        local row = self:GetOrCreateDetailRow(rightCount, "VENDOR")
        row:SetSize(rowWidth, 32)
        
        -- Route through your async population engine
        WDIGI.Data.SafePopulateItemRow(row, itemID, true)
        
        local displayCost = WDIGI.Data.GetFormattedCost(itemObj.rawCost, itemObj.vendorType)
        row.price:SetText(displayCost or "")
        
        -- Clean layout alignments to ensure anchors don't leak during frame recycling
        row.coords:SetText("")
        row.pin:Hide()
        
        -- 🌟 FIX 1: Anchor the price string to the far right edge of the row container
        -- Using an explicit right-to-right anchor forces it back on screen cleanly.
        row.price:ClearAllPoints()
        row.price:SetPoint("RIGHT", row, "RIGHT", -45, 0) 
        row.price:SetJustifyH("RIGHT")
        row.price:SetWidth(60) -- Give the percentage text a fixed width footprint so it never clips
        
        -- 🌟 FIX 2: Constrain the item name and explicitly force left justification
        if row.name then
            row.name:ClearAllPoints()
            row.name:SetPoint("LEFT", row.icon, "RIGHT", 10, 0)
            row.name:SetPoint("RIGHT", row.price, "LEFT", -10, 0)
            row.name:SetJustifyH("LEFT") -- 🎯 This stops WoW from auto-centering the text!
        end

        row:SetPoint("TOPLEFT", self.RightContent, "TOPLEFT", 0, -rightOffset)
        row:Show()
        rightOffset = rightOffset + 34
    end

    self.LeftContent:SetHeight(leftOffset + 20)
    self.RightContent:SetHeight(rightOffset)
end

-- 🌟 SHARED UTILITY: Static event click handler blocks execution leaks completely
local function OnTargetNearestClick(self)
    if not self.zoneID or self.zoneID <= 0 then return end
    
    -- Dynamically resolve the live text info if it wasn't fully cached on click loop instantiation
    local liveName = GetItemInfo(self.itemID) or ITEM_NAMES_LIST[self.itemID] or "Unknown Item"
    local _, _, _, _, _, _, _, _, _, liveTex = GetItemInfo(self.itemID)
    
    WDIGI.Navigation.SetPin(
        self.zoneID, 
        self.x, 
        self.y, 
        self.vendorName, 
        liveName, 
        self.cost, 
        "NEAREST SOURCE", 
        liveTex or 134400
    )
end

---------------------------------------------------------------------------------------
--- comment
--- @param itemID any
--- @return any
---------------------------------------------------------------------------------------
function WDIGI.Compendium:RenderItemDetails(itemID)
    self:SetDetailLayout(true)
    if self.SplashFrame then self.SplashFrame:Hide() end
    for _, row in ipairs(self.ListRows) do row:Hide() end
    for _, row in ipairs(self.DetailRows) do row:Hide() end
    
    local itemName, itemLink, _, _, _, _, _, _, _, itemTexture = GetItemInfo(itemID)
    if itemName then 
        self.SearchBox:SetText(itemName) 
    else
        -- Fallback to your database dictionary tracker line if the item is uncached
        self.SearchBox:SetText(ITEM_NAMES_LIST[itemID] or "Loading...")
    end
    
    local rawEntries = WDIGI.Data.ParseItemData(itemID)
    local leftOffset = 0
    
    local feat = self:GetOrCreateListRow(100)
    self:StyleListRow(feat, "FEATURED")
    feat.name:SetText(itemLink or itemName or ITEM_NAMES_LIST[itemID] or "Loading...")
    feat.icon:SetTexture(itemTexture or 134400)
    
    -- Bind the standard item tooltip script execution to the Featured Card.
    -- This will add the standard in-game tooltip on hover for the feature card.
    feat.itemID = itemID
    feat:SetScript("OnEnter", function(self)
        
        if not self.itemID then return end
        WDIGI.Tooltip.Suppress = true -- hide extra location info, already disapled in item list rows.
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        if C_TooltipInfo and C_TooltipInfo.GetItemByID then
            GameTooltip:SetItemByID(self.itemID)
        else
            GameTooltip:SetHyperlink("item:" .. self.itemID)
        end
        GameTooltip:Show()
    end)
    feat:SetScript("OnLeave", function(self)
        GameTooltip:Hide()
        WDIGI.Tooltip.Suppress = false -- user is no longer hovering, re-enable any tooltip info that was suppressed
    end)

    local locations, maps, drops, vendors = 0, {}, 0, 0
    for _, e in ipairs(rawEntries) do
        locations = locations + 1
        maps[e.mapID] = true
        if e.vendorType ~= 1 then drops = drops + 1 end
        if e.vendorType == 1 then vendors = vendors + 1 end
    end
    local mapCount = 0; for _ in pairs(maps) do mapCount = mapCount + 1 end
    
    feat.meta:SetText(string.format("Total Sources: %d\nMap Areas: %d\nTotal Drops/Sources: %d\nTotal Vendors: %d", locations, mapCount, drops, vendors))
    feat:SetPoint("TOPLEFT", self.LeftContent, "TOPLEFT", 0, 0)
    feat:Show(); leftOffset = 180

    local best = WDIGI.Data.GetBestLocation(itemID)
    if best and best.zoneID and best.zoneID > 0 then
        local btn = self:GetOrCreateListRow(101)
        self:StyleListRow(btn, "ACTION")
        btn.name:SetText("TARGET NEAREST")
        
        -- 🌟 FIX: Store the raw values safely straight onto the widget element nodes
        btn.itemID = itemID
        btn.zoneID = best.zoneID
        btn.x = best.x
        btn.y = best.y
        btn.vendorName = best.vendorName
        btn.cost = best.cost
        
        -- Bind to the static shared event runner instead of allocating a dynamic inner function frame
        btn:SetScript("OnClick", OnTargetNearestClick)
        btn:Show(); leftOffset = leftOffset + 45
    end

    local rightOffset, rightCount = 0, 0
    rightCount = rightCount + 1
    local relHeader = self:GetOrCreateDetailRow(rightCount, "ZONE")
    relHeader.name:SetText("ALL KNOWN SOURCES (SORTED BY CLOSEST)")
    relHeader:SetPoint("TOPLEFT", self.RightContent, "TOPLEFT", 0, 0)
    relHeader:Show(); rightOffset = 30

    local myMapID, px, py = WDIGI.MapUtils.GetCurrentPlayerData()
    local processed = {}
    
    for _, e in ipairs(rawEntries) do
        local matchLevel = WDIGI.MapUtils.GetZoneMatchLevel(myMapID, e.mapID)
        local dist = 999999 

        if matchLevel == 0 and px and py and e.tx > 0 and e.ty > 0 then
            local dx = (e.tx / 100) - px
            local dy = (e.ty / 100) - py
            local mult = WDIGI.MapUtils.GetZoneMultiplier(e.mapID) or 1000
            dist = math.sqrt(dx * dx + dy * dy) * mult
        else
            dist = 1000000 * (matchLevel + 1)
        end

        local mapInfo = C_Map.GetMapInfo(e.mapID)
        table.insert(processed, { 
            entry = e, 
            matchLevel = matchLevel, 
            dist = dist, 
            zoneName = mapInfo and mapInfo.name or "Unknown Location" 
        })
    end

    table.sort(processed, function(a, b)
        if a.matchLevel ~= b.matchLevel then return a.matchLevel < b.matchLevel end
        if a.entry.vendorType ~= b.entry.vendorType then return a.entry.vendorType < b.entry.vendorType end
        return a.dist < b.dist
    end)

    local lastZone, lastType = "", -1
    for _, data in ipairs(processed) do
        local e = data.entry
        if data.zoneName ~= lastZone then
            rightCount = rightCount + 1; local zRow = self:GetOrCreateDetailRow(rightCount, "ZONE")
            zRow.name:SetText(data.zoneName:upper()); zRow:SetPoint("TOPLEFT", self.RightContent, "TOPLEFT", 0, -rightOffset)
            zRow:Show(); rightOffset = rightOffset + 26; lastZone, lastType = data.zoneName, -1
        end
        if e.vendorType ~= lastType then
            rightCount = rightCount + 1; local tRow = self:GetOrCreateDetailRow(rightCount, "SUBTYPE")
            tRow.name:SetText((WDIGI.Data.VENDOR_TYPE_NAMES[e.vendorType] or "Source"):upper())
            tRow:SetPoint("TOPLEFT", self.RightContent, "TOPLEFT", 0, -rightOffset)
            tRow:Show(); rightOffset = rightOffset + 20; lastType = e.vendorType
        end
        rightCount = rightCount + 1; local row = self:GetOrCreateDetailRow(rightCount, "VENDOR")
        row.data, row.itemID, row.itemIcon = e, itemID, itemTexture

        local displayCost = WDIGI.Data.GetFormattedCost(e.cost, e.vendorType)

        if e.sourceType == "creature" or (e.npcID and e.npcID > 0) then
            row.icon:SetTexture(135911)
            row.icon:SetTexCoord(0.15, 0.85, 0.15, 0.85)
        elseif e.sourceType == "gameobject" then
            row.icon:SetTexture(132594)
            row.icon:SetTexCoord(0, 1, 0, 1)
        else
            row.icon:SetTexture(WDIGI.Data.VENDOR_ICONS[e.vendorType] or 134400)
            row.icon:SetTexCoord(0, 1, 0, 1) 
        end

        row.name:SetText(e.vendorName)
        row.price:SetText(displayCost or "")
        row.coords:SetText(e.tx > 0 and string.format("%.1f, %.1f", e.tx, e.ty) or "")
        row.pin:SetShown(e.tx > 0)
        
        row:SetPoint("TOPLEFT", self.RightContent, "TOPLEFT", 0, -rightOffset)
        row:Show()
        rightOffset = rightOffset + 34
    end

    self.LeftContent:SetHeight(leftOffset + 200); self.RightContent:SetHeight(rightOffset)
end

------------------------------------------------------------
-- FACTORIES & STYLING
------------------------------------------------------------
function WDIGI.Compendium:GetOrCreateListRow(index)
    if self.ListRows[index] then return self.ListRows[index] end

    local row = CreateFrame("Button", nil, self.LeftContent, "BackdropTemplate")
    row:SetSize(SIDEBAR_WIDTH - 5, 32)
    row:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8"})
    
    row.icon = row:CreateTexture(nil, "ARTWORK")
    row.icon:SetSize(22, 22)
    row.icon:SetPoint("LEFT", 6, 0)
    
    row.name = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    row.name:SetPoint("LEFT", 34, 0)
    row.name:SetPoint("RIGHT", -4, 0)
    row.name:SetJustifyH("LEFT")
    row.name:SetWordWrap(false)
    
    row.meta = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    row.meta:SetPoint("TOP", row.name, "BOTTOM", 0, -10)
    row.meta:Hide()
    
    table.insert(self.ListRows, row)
    return row
end

function WDIGI.Compendium:StyleListRow(row, type)
    row:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8"})
    row:SetBackdropColor(0, 0, 0, 0); row:SetBackdropBorderColor(0, 0, 0, 0)
    row.icon:Show(); row.icon:ClearAllPoints(); row.icon:SetTexCoord(0, 1, 0, 1) 
    row.name:ClearAllPoints(); row.name:SetFontObject("GameFontNormalSmall"); row.meta:Hide()

    if type == "FEATURED" then
        row:SetSize(SIDEBAR_WIDTH - 10, 170)
        row:SetBackdropColor(unpack(COLORS.CARD_BG))
        row.icon:SetSize(64, 64); row.icon:SetPoint("TOP", 0, -15)
        row.name:SetPoint("TOP", row.icon, "BOTTOM", 0, -10); row.name:SetPoint("LEFT", 5, 0); row.name:SetPoint("RIGHT", -5, 0)
        row.name:SetJustifyH("CENTER"); row.name:SetFontObject("GameFontNormalLarge")
        row.meta:Show(); row.meta:SetJustifyH("CENTER"); row.meta:SetTextColor(unpack(COLORS.TEXT_DIM))
        row:SetScript("OnClick", nil)
    elseif type == "ACTION" then
        row:SetSize(SIDEBAR_WIDTH - 30, 30); row:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1})
        row:SetBackdropColor(0.1, 0.4, 0.1, 0.9); row:SetBackdropBorderColor(0.2, 0.6, 0.2, 1)
        row.icon:Hide(); row.name:SetPoint("LEFT", 0, 0); row.name:SetPoint("RIGHT", 0, 0); row.name:SetJustifyH("CENTER")
    elseif type == "LIST" then
        row:SetSize(SIDEBAR_WIDTH - 5, 32); row.icon:SetSize(22, 22); row.icon:SetPoint("LEFT", 6, 0)
        row.name:SetPoint("LEFT", 34, 0); row.name:SetPoint("RIGHT", -4, 0); row:SetBackdropColor(0.15, 0.15, 0.15, 0.5)
    end
end

function WDIGI.Compendium:GetOrCreateDetailRow(index, type)
    if self.DetailRows[index] then 
        local r = self.DetailRows[index]
        r:Hide()
        self:StyleDetailRow(r, type)
        return r 
    end

    local row = CreateFrame("Button", nil, self.RightContent, "BackdropTemplate")
    row.icon = row:CreateTexture(nil, "ARTWORK")
    row.name = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.price = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    row.coords = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    row.coords:SetTextColor(unpack(COLORS.AMBER_TEXT))
    
    row.pin = row:CreateTexture(nil, "OVERLAY")
    row.pin:SetTexture("Interface\\Minimap\\POIIcons")
    row.pin:SetTexCoord(0.4, 0.45, 0.4, 0.45)
    row.pin:SetSize(16, 16)

    row:SetScript("OnClick", function(s) 
        if s.data then 
            local e = s.data
            WDIGI.Navigation.SetPin(
                e.mapID, 
                e.tx, 
                e.ty, 
                e.vendorName, 
                GetItemInfo(s.itemID) or "Item", 
                e.cost,       
                WDIGI.Data.VENDOR_TYPE_NAMES[e.vendorType], 
                s.itemIcon, 
                WDIGI.Data.VENDOR_ICONS[e.vendorType],
                e.npcID,      
                e.sourceType  
            ) 
        end 
    end)

    self:StyleDetailRow(row, type)
    table.insert(self.DetailRows, row)
    
    return row
end

function WDIGI.Compendium:StyleDetailRow(row, type)
    local availableWidth = self.Frame:GetWidth() - SIDEBAR_WIDTH - 15
    row:SetBackdrop({bgFile = "Interface\\Buttons\\WHITE8X8"})
    row.icon:Hide(); row.price:Hide(); row.pin:Hide(); row.coords:Hide()
    row.icon:SetTexCoord(0, 1, 0, 1) 
    
    if type == "ZONE" then
        row:SetSize(availableWidth, 24)
        row:SetBackdropColor(unpack(COLORS.BG_HDR_ZONE))
        row.name:ClearAllPoints(); row.name:SetPoint("LEFT", 10, 0); row.name:SetFontObject("GameFontNormalSmall")
    elseif type == "SUBTYPE" then
        row:SetSize(availableWidth, 18)
        row:SetBackdropColor(unpack(COLORS.BG_HDR_SUBTYPE)) 
        row.name:ClearAllPoints(); row.name:SetPoint("LEFT", 10, 0); row.name:SetFontObject("GameFontHighlightSmall"); row.name:SetTextColor(unpack(COLORS.AMBER_TEXT))
        
    elseif type == "SUBTYPE_COMMON" then
        row:SetSize(availableWidth, 20)
        row:SetBackdropColor(unpack(COLORS.BG_HDR_COMMON))
        row.name:ClearAllPoints(); row.name:SetPoint("LEFT", 10, 0); row.name:SetFontObject("GameFontHighlightSmall")
        row.name:SetTextColor(1, 1, 1) 
        
    elseif type == "SUBTYPE_RARE" then
        row:SetSize(availableWidth, 20)
        row:SetBackdropColor(unpack(COLORS.BG_HDR_RARE))
        row.name:ClearAllPoints(); row.name:SetPoint("LEFT", 10, 0); row.name:SetFontObject("GameFontHighlightSmall")
        row.name:SetTextColor(1, 1, 1)
        
    elseif type == "SUBTYPE_EXTREME" then
        row:SetSize(availableWidth, 20)
        row:SetBackdropColor(unpack(COLORS.BG_HDR_EXTREME))
        row.name:ClearAllPoints(); row.name:SetPoint("LEFT", 10, 0); row.name:SetFontObject("GameFontHighlightSmall")
        row.name:SetTextColor(1, 1, 1)
        
    else 
        row:SetSize(availableWidth, 32)
        row:SetBackdropColor(0, 0, 0, 0.2)
        row.icon:Show(); row.icon:SetSize(26, 26); row.icon:SetPoint("LEFT", 10, 0)
        row.name:ClearAllPoints(); row.name:SetPoint("LEFT", 44, 0)
        row.price:Show(); row.price:SetPoint("RIGHT", -110, 0)
        row.coords:Show(); row.coords:SetPoint("RIGHT", -32, 0)
        row.pin:Show(); row.pin:SetPoint("RIGHT", -10, 0)
    end
end

function WDIGI.Compendium:Toggle()
    if not WDIGI.Settings.Get("WDIGI_EnableCompendium_Experimental") then return end
    if not self.Frame then self:Create(); self.Frame:Show() else
        if self.Frame:IsShown() then self.Frame:Hide() else self.Frame:Show(); if self.SearchBox then self.SearchBox:SetFocus() end end
    end
end

local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:SetScript("OnEvent", function(f, event, arg1)
    if event == "ADDON_LOADED" and arg1 == "WhereDoIGetIt" then 
        WDIGI.Compendium:CreateMinimapButton()
    end
end)