--[[
============================================================================
Enhanced Toy Tracker  -  EToy/UI/ToyBrowser.lua
============================================================================
The toy collection browser. Brought to parity with EMT/EPT/ERT: a two-pane
window. Left: every toy grouped into collapsible source categories, each row
showing an icon, a collected mark, the (quality-coloured) name, and a short
source tag. Right: a detail panel that shows how to collect the selected toy
(category how-to, plus a specific "Drops from <Rare> in <Zone>" line when the
rare roster knows it). A segmented All / Collected / Missing filter and a
search box sit in the header, alongside live owned/total/% progress.

Names and icons resolve at runtime: the icon is synchronous
(GetItemInfoInstant); the name streams in via GetItemInfo and the list
redraws on GET_ITEM_INFO_RECEIVED.

Opened from the unified ETB launcher (UI/LauncherPanel.lua -> openToys) and
via /etoy. Namespace: NS.ETT.Browser.
============================================================================
]]--

local ADDON_NAME, NS = ...
local ETT = NS.ETT
if not ETT then return end

ETT.Browser = ETT.Browser or {}
local UI = ETT.Browser

-- ----------------------------------------------------------------------------
-- CONSTANTS
-- ----------------------------------------------------------------------------
local PANEL_W     = 720
local PANEL_H     = 620
local DETAIL_W    = 270
local ROW_H       = 26
local HEADER_H    = 24
local LIST_TOP    = 96
local LIST_BOTTOM = 16
local INSET       = 14

-- Stable source-group display order (includes C — crafted — which the old
-- list-only browser silently dropped).
local SOURCE_ORDER = { "V", "D", "C", "P", "Q", "A", "H", "E", "X", "B", "F", "M", "I", "U" }

-- ----------------------------------------------------------------------------
-- STATE
-- ----------------------------------------------------------------------------
UI.frame        = nil
UI._collapsed   = {}
UI._filterText  = ""
UI._collFilter  = "all"       -- "all" | "collected" | "missing"
UI._selectedID  = nil
UI._headerPool  = {}
UI._toyPool     = {}
UI._headerUsed  = 0
UI._toyUsed     = 0
UI._display     = {}

-- ----------------------------------------------------------------------------
-- DISPLAY-LIST BUILD
-- ----------------------------------------------------------------------------
function UI:BuildDisplayList()
    wipe(self._display)
    local data = ETT.ToyData
    if type(data) ~= "table" then return end

    local needle = self._filterText:lower()

    local buckets, ownedCount, totalCount = {}, {}, {}
    for _, code in ipairs(SOURCE_ORDER) do
        buckets[code], ownedCount[code], totalCount[code] = {}, 0, 0
    end

    for id, info in pairs(data) do
        local code = (info and info.src) or "U"
        if not buckets[code] then code = "U" end

        local owned = ETT:IsCollected(id)
        local name  = ETT:GetName(id)

        totalCount[code] = totalCount[code] + 1
        if owned then ownedCount[code] = ownedCount[code] + 1 end

        local pass = true
        if self._collFilter == "collected" and not owned then pass = false end
        if self._collFilter == "missing"   and owned     then pass = false end
        if pass and needle ~= "" and not name:lower():find(needle, 1, true) then
            pass = false
        end

        if pass then
            buckets[code][#buckets[code] + 1] =
                { id = id, info = info, name = name, owned = owned }
        end
    end

    for _, code in ipairs(SOURCE_ORDER) do
        local list = buckets[code]
        if #list > 0 then
            self._display[#self._display + 1] = {
                kind  = "header", src = code,
                label = (ETT:SourceLabel(code)),
                owned = ownedCount[code], total = totalCount[code],
                shown = #list,
            }
            if not self._collapsed[code] then
                table.sort(list, function(a, b)
                    if a.owned ~= b.owned then return b.owned end
                    return tostring(a.name) < tostring(b.name)
                end)
                for _, t in ipairs(list) do
                    t.kind = "toy"
                    self._display[#self._display + 1] = t
                end
            end
        end
    end
end

-- ----------------------------------------------------------------------------
-- ROW POOLS
-- ----------------------------------------------------------------------------
function UI:_acquireHeaderRow()
    self._headerUsed = self._headerUsed + 1
    local row = self._headerPool[self._headerUsed]
    if row then row:Show(); return row end

    row = CreateFrame("Button", nil, self.frame.scrollChild)
    row:SetHeight(HEADER_H)

    local bg = row:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(0.16, 0.16, 0.22, 0.9)
    row.bg = bg

    row.glyph = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.glyph:SetPoint("LEFT", 6, 0)
    row.glyph:SetWidth(14)

    row.label = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.label:SetPoint("LEFT", row.glyph, "RIGHT", 4, 0)
    row.label:SetTextColor(1, 0.82, 0)

    row.count = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    row.count:SetPoint("RIGHT", -8, 0)

    row:SetScript("OnEnter", function(s) s.bg:SetColorTexture(0.24, 0.24, 0.32, 0.95) end)
    row:SetScript("OnLeave", function(s) s.bg:SetColorTexture(0.16, 0.16, 0.22, 0.9) end)
    row:SetScript("OnClick", function(s)
        UI._collapsed[s._src] = not UI._collapsed[s._src]
        UI:RefreshList()
    end)

    self._headerPool[self._headerUsed] = row
    return row
end

function UI:_acquireToyRow()
    self._toyUsed = self._toyUsed + 1
    local row = self._toyPool[self._toyUsed]
    if row then row:Show(); return row end

    row = CreateFrame("Button", nil, self.frame.scrollChild)
    row:SetHeight(ROW_H)

    row.sel = row:CreateTexture(nil, "BACKGROUND")
    row.sel:SetAllPoints()
    row.sel:SetColorTexture(0.20, 0.40, 0.60, 0.55)
    row.sel:Hide()

    local hl = row:CreateTexture(nil, "HIGHLIGHT")
    hl:SetAllPoints()
    hl:SetColorTexture(1, 1, 1, 0.08)

    row.icon = row:CreateTexture(nil, "ARTWORK")
    row.icon:SetSize(20, 20)
    row.icon:SetPoint("LEFT", 24, 0)
    row.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

    row.mark = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.mark:SetPoint("LEFT", 4, 0)
    row.mark:SetWidth(16)

    row.name = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    row.name:SetPoint("LEFT", row.icon, "RIGHT", 8, 0)
    row.name:SetJustifyH("LEFT")
    row.name:SetWidth(210)

    row.extra = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    row.extra:SetPoint("RIGHT", -8, 0)
    row.extra:SetJustifyH("RIGHT")

    row:SetScript("OnEnter", function(s)
        if not s._id then return end
        GameTooltip:SetOwner(s, "ANCHOR_RIGHT")
        local set = false
        if GameTooltip.SetToyByItemID then
            set = pcall(GameTooltip.SetToyByItemID, GameTooltip, s._id)
        end
        if not set then pcall(GameTooltip.SetItemByID, GameTooltip, s._id) end
        local d = ETT.SourceInfo and ETT.SourceInfo:Describe(s._id, s._info)
        if d then
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine(s._owned and "|cff44ff44Collected|r"
                or "|cffff5555Not collected|r")
            GameTooltip:AddLine("|cffffd100" .. d.headline .. "|r: " .. d.howto, 1, 1, 1, true)
            GameTooltip:AddLine("|cff666666Click for full details|r")
        end
        GameTooltip:Show()
    end)
    row:SetScript("OnLeave", function() GameTooltip:Hide() end)
    row:SetScript("OnClick", function(s)
        UI:SelectToy(s._id)
    end)

    self._toyPool[self._toyUsed] = row
    return row
end

function UI:_releaseAllRows()
    for i = 1, self._headerUsed do self._headerPool[i]:Hide() end
    for i = 1, self._toyUsed    do self._toyPool[i]:Hide()    end
    self._headerUsed = 0
    self._toyUsed    = 0
end

-- ----------------------------------------------------------------------------
-- LIST RENDER
-- ----------------------------------------------------------------------------
function UI:RefreshList()
    if not self.frame then return end
    self:BuildDisplayList()
    self:_releaseAllRows()

    local rowW = self.frame.scrollChild:GetWidth()
    local y    = 0
    for _, entry in ipairs(self._display) do
        if entry.kind == "header" then
            local _, colourHex = ETT:SourceLabel(entry.src)
            local row = self:_acquireHeaderRow()
            row._src = entry.src
            row:SetWidth(rowW)
            row.glyph:SetText(self._collapsed[entry.src] and "+" or "-")
            row.label:SetText("|c" .. colourHex .. entry.label .. "|r")
            row.count:SetText(string.format("|cff44ff44%d|r/%d  (%d shown)",
                entry.owned, entry.total, entry.shown))
            row:ClearAllPoints()
            row:SetPoint("TOPLEFT", 0, -y)
            y = y + HEADER_H + 2
        else
            local row = self:_acquireToyRow()
            row._id    = entry.id
            row._info  = entry.info
            row._owned = entry.owned
            row:SetWidth(rowW - 12)
            row.icon:SetTexture(ETT:GetIcon(entry.id))
            row.mark:SetText(entry.owned and "|cff44ff44+|r" or "|cffff5555-|r")
            row.name:SetText(ETT:QualityColor(entry.id, entry.owned) .. entry.name .. "|r")
            row.extra:SetText(ETT.SourceInfo and ETT.SourceInfo:SourceTag(entry.info) or "")
            if self._selectedID == entry.id then row.sel:Show() else row.sel:Hide() end
            row:ClearAllPoints()
            row:SetPoint("TOPLEFT", 12, -y)
            y = y + ROW_H
        end
    end

    self.frame.scrollChild:SetHeight(math.max(y, 1))
    self:UpdateProgress()
end

function UI:UpdateProgress()
    if not self.frame then return end
    local owned, total = ETT:GetProgress()
    local pct = total > 0 and (owned / total * 100) or 0
    self.frame.progress:SetText(string.format(
        "|cff44ff44%d|r / %d  (%.0f%%)", owned, total, pct))
end

-- ----------------------------------------------------------------------------
-- SELECTION + DETAIL PANEL
-- ----------------------------------------------------------------------------
function UI:SelectToy(toyID)
    self._selectedID = toyID
    self:ShowDetail(toyID)
    self:RefreshList()
end

function UI:ShowDetail(toyID)
    local d = self.frame and self.frame.detail
    if not d then return end

    local info = toyID and ETT.ToyData and ETT.ToyData[toyID]
    if not info then
        d.hint:Show()
        d.icon:Hide(); d.name:SetText(""); d.status:SetText("")
        d.body:SetText(""); d.use:Hide()
        return
    end
    d.hint:Hide()

    local owned = ETT:IsCollected(toyID)
    local desc  = ETT.SourceInfo and ETT.SourceInfo:Describe(toyID, info)

    d.icon:Show()
    d.icon:SetTexture(ETT:GetIcon(toyID))
    d.name:SetText(ETT:QualityColor(toyID, owned) .. ETT:GetName(toyID) .. "|r")
    d.status:SetText(owned and "|cff44ff44Collected|r" or "|cffff5555Not collected|r")

    local lines = {}
    if desc then
        lines[#lines + 1] = "|cffffd100" .. desc.headline .. "|r"
        lines[#lines + 1] = desc.howto
        if desc.dropSource then
            lines[#lines + 1] = " "
            lines[#lines + 1] = "|cffcccccc" .. desc.dropSource .. "|r"
        end
        if desc.skill then
            lines[#lines + 1] = "|cffd0a060Profession:|r " .. desc.skill
        end
        if desc.event then
            lines[#lines + 1] = "|cffff60ffEvent:|r " .. desc.event
        end
    end
    d.body:SetText(table.concat(lines, "\n\n"))

    if owned then
        d.use:Show()
        d.use._id = toyID
    else
        d.use:Hide()
    end
end

-- ----------------------------------------------------------------------------
-- SEGMENTED FILTER CONTROL  (All / Collected / Missing)
-- ----------------------------------------------------------------------------
function UI:_buildSegmented(parent, anchor)
    local options = { { "all", "All" }, { "collected", "Collected" },
                       { "missing", "Missing" } }
    local seg = { buttons = {} }
    local prev
    for _, opt in ipairs(options) do
        local b = CreateFrame("Button", nil, parent)
        b:SetSize(74, 22)
        if prev then
            b:SetPoint("LEFT", prev, "RIGHT", 2, 0)
        else
            b:SetPoint("LEFT", anchor, "RIGHT", 14, 0)
        end
        local bg = b:CreateTexture(nil, "BACKGROUND")
        bg:SetAllPoints()
        bg:SetColorTexture(0.18, 0.18, 0.24, 0.9)
        b.bg = bg
        b.txt = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        b.txt:SetPoint("CENTER")
        b.txt:SetText(opt[2])
        b._value = opt[1]
        b:SetScript("OnClick", function(self)
            UI._collFilter = self._value
            UI:_paintSegmented(seg)
            UI:RefreshList()
        end)
        seg.buttons[#seg.buttons + 1] = b
        prev = b
    end
    return seg
end

function UI:_paintSegmented(seg)
    for _, b in ipairs(seg.buttons) do
        if b._value == self._collFilter then
            b.bg:SetColorTexture(0.25, 0.45, 0.65, 0.95)
            b.txt:SetTextColor(1, 1, 1)
        else
            b.bg:SetColorTexture(0.18, 0.18, 0.24, 0.9)
            b.txt:SetTextColor(0.7, 0.7, 0.7)
        end
    end
end

-- ----------------------------------------------------------------------------
-- FRAME CONSTRUCTION
-- ----------------------------------------------------------------------------
function UI:Create()
    if self.frame then return self.frame end

    local f = CreateFrame("Frame", "ETTBrowserFrame", UIParent, "BackdropTemplate")
    f:SetSize(PANEL_W, PANEL_H)
    f:SetPoint("CENTER")
    f:SetFrameStrata("HIGH")
    f:SetBackdrop({
        bgFile   = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
        tile = true, tileSize = 32, edgeSize = 24,
        insets = { left = 6, right = 6, top = 6, bottom = 6 },
    })
    f:EnableMouse(true)
    f:SetMovable(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    f:Hide()
    tinsert(UISpecialFrames, "ETTBrowserFrame")

    f.title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    f.title:SetPoint("TOPLEFT", INSET, -14)
    f.title:SetText("|cffffcc33Enhanced Toy Tracker|r")

    f.progress = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    f.progress:SetPoint("TOPRIGHT", -36, -16)

    f.closeBtn = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    f.closeBtn:SetPoint("TOPRIGHT", -6, -6)

    -- Filter bar -------------------------------------------------------------
    f.search = CreateFrame("EditBox", nil, f, "InputBoxTemplate")
    f.search:SetSize(150, 20)
    f.search:SetPoint("TOPLEFT", INSET + 6, -46)
    f.search:SetAutoFocus(false)
    f.search:SetScript("OnTextChanged", function(box)
        UI._filterText = box:GetText() or ""
        if not UI._searchDebounce then
            UI._searchDebounce = true
            C_Timer.After(0.18, function()
                UI._searchDebounce = false
                if UI.frame and UI.frame:IsShown() then UI:RefreshList() end
            end)
        end
    end)
    f.search:SetScript("OnEscapePressed", function(box) box:ClearFocus() end)

    f.searchLabel = f:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    f.searchLabel:SetPoint("BOTTOMLEFT", f.search, "TOPLEFT", -2, 1)
    f.searchLabel:SetText("Search")

    f.segmented = self:_buildSegmented(f, f.search)

    local div = f:CreateTexture(nil, "ARTWORK")
    div:SetColorTexture(0.4, 0.4, 0.4, 0.6)
    div:SetHeight(1)
    div:SetPoint("TOPLEFT", INSET, -(LIST_TOP - 6))
    div:SetPoint("TOPRIGHT", -INSET, -(LIST_TOP - 6))

    -- Scroll list (left) -----------------------------------------------------
    f.scrollFrame = CreateFrame("ScrollFrame", nil, f, "UIPanelScrollFrameTemplate")
    f.scrollFrame:SetPoint("TOPLEFT", INSET, -LIST_TOP)
    f.scrollFrame:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT",
        -(INSET + 22 + DETAIL_W + 10), LIST_BOTTOM)

    f.scrollChild = CreateFrame("Frame", nil, f.scrollFrame)
    f.scrollChild:SetSize(PANEL_W - INSET * 2 - 22 - DETAIL_W - 10, 1)
    f.scrollFrame:SetScrollChild(f.scrollChild)

    -- Detail panel (right) ---------------------------------------------------
    self:_buildDetailPanel(f)

    self.frame = f
    self:_paintSegmented(f.segmented)

    -- live refresh on collection change
    ETT:On("ETT_COLLECTION_CHANGED", function()
        if f:IsShown() then
            UI:RefreshList()
            UI:ShowDetail(UI._selectedID)
        end
    end)

    -- async name resolution: redraw as item names stream in
    local nameWatcher = CreateFrame("Frame")
    nameWatcher:RegisterEvent("GET_ITEM_INFO_RECEIVED")
    nameWatcher._pending = false
    nameWatcher:SetScript("OnEvent", function(self2)
        if not f:IsShown() then return end
        if self2._pending then return end
        self2._pending = true
        C_Timer.After(0.2, function()
            self2._pending = false
            if f:IsShown() then
                UI:RefreshList()
                UI:ShowDetail(UI._selectedID)
            end
        end)
    end)

    return f
end

function UI:_buildDetailPanel(f)
    local d = CreateFrame("Frame", nil, f, "BackdropTemplate")
    d:SetWidth(DETAIL_W)
    d:SetPoint("TOPRIGHT", -INSET, -LIST_TOP)
    d:SetPoint("BOTTOMRIGHT", -INSET, LIST_BOTTOM)
    d:SetBackdrop({
        bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 14,
        insets = { left = 4, right = 4, top = 4, bottom = 4 },
    })
    d:SetBackdropColor(0.05, 0.05, 0.08, 0.85)
    d:SetBackdropBorderColor(0.4, 0.4, 0.4)

    d.icon = d:CreateTexture(nil, "ARTWORK")
    d.icon:SetSize(46, 46)
    d.icon:SetPoint("TOPLEFT", 14, -14)
    d.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

    d.name = d:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    d.name:SetPoint("TOPLEFT", d.icon, "TOPRIGHT", 10, -2)
    d.name:SetPoint("RIGHT", d, "RIGHT", -12, 0)
    d.name:SetJustifyH("LEFT")
    d.name:SetJustifyV("TOP")

    d.status = d:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    d.status:SetPoint("TOPLEFT", d.name, "BOTTOMLEFT", 0, -6)

    d.divider = d:CreateTexture(nil, "ARTWORK")
    d.divider:SetColorTexture(0.35, 0.35, 0.35, 0.8)
    d.divider:SetHeight(1)
    d.divider:SetPoint("TOPLEFT", 12, -72)
    d.divider:SetPoint("TOPRIGHT", -12, -72)

    d.body = d:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    d.body:SetPoint("TOPLEFT", 14, -84)
    d.body:SetPoint("RIGHT", d, "RIGHT", -14, 0)
    d.body:SetJustifyH("LEFT")
    d.body:SetJustifyV("TOP")
    d.body:SetSpacing(2)

    d.hint = d:CreateFontString(nil, "OVERLAY", "GameFontDisableLarge")
    d.hint:SetPoint("CENTER", 0, 20)
    d.hint:SetWidth(DETAIL_W - 40)
    d.hint:SetText("Select a toy to see how to collect it.")

    d.use = CreateFrame("Button", nil, d, "UIPanelButtonTemplate")
    d.use:SetSize(140, 24)
    d.use:SetPoint("BOTTOM", 0, 14)
    d.use:SetText("Use Toy")
    d.use:SetScript("OnClick", function(self)
        if self._id and UseToy then pcall(UseToy, self._id) end
    end)
    d.use:Hide()

    f.detail = d
    d.icon:Hide()
end

-- ----------------------------------------------------------------------------
-- SHOW / HIDE / TOGGLE
-- ----------------------------------------------------------------------------
function UI:Show()
    self:Create()
    -- warm the item cache so names resolve quickly
    for _, t in ipairs(ETT:GetToys()) do GetItemInfo(t.id) end
    self.frame:Show()
    self:RefreshList()
    self:ShowDetail(self._selectedID)
end

function UI:Hide()
    if self.frame then self.frame:Hide() end
end

function UI:Toggle()
    self:Create()
    if self.frame:IsShown() then self:Hide() else self:Show() end
end
