--[[
============================================================================
Enhanced Mount Tracker  -  UI/MountBrowser.lua
============================================================================
The main window. Left side: every mount grouped into collapsible source
categories. Right side: a detail panel showing how to collect the selected
mount. Both the panel and the row hover-tooltip are fed by SourceInfo.

Filters:
  - Collected: a 3-way segmented control  All / Collected / Missing
  - Hide removed: drops mounts flagged as no longer obtainable
  - Search: substring match on mount name

UI patterns follow ETB's PlannerUI (pooled rows, a flat display list sized
to the scroll child).
============================================================================
]]--

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

EMT.Browser = EMT.Browser or {}
local UI = EMT.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

local SOURCE_ORDER = { "B", "R", "V", "P", "C", "F", "H", "W", "K", "L", "M", "?" }

-- ----------------------------------------------------------------------------
-- STATE
-- ----------------------------------------------------------------------------

UI.frame         = nil
UI._collapsed    = {}
UI._filterText   = ""
UI._collFilter   = "all"      -- "all" | "collected" | "missing"
UI._hideRemoved  = false
UI._selectedSpell = nil
UI._headerPool   = {}
UI._mountPool    = {}
UI._headerUsed   = 0
UI._mountUsed    = 0
UI._display      = {}

-- ----------------------------------------------------------------------------
-- DISPLAY-LIST BUILD
-- ----------------------------------------------------------------------------

function UI:BuildDisplayList()
    wipe(self._display)
    local data = EMT.MountData
    local API  = EMT.MountAPI
    if not data then return end

    local srcNames = EMT.MountSourceNames or {}
    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 spellID, rec in pairs(data) do
        -- v15.41.0: only list mounts the Mount Journal recognises (permanent,
        -- usable-indefinitely mounts). Drops temp/zone vehicle entries like the
        -- loaded gryphon that aren't real collectibles. Removed/unobtainable
        -- (rec.u) and already-collected mounts are kept regardless.
        if API:IsJournalMount(spellID) or rec.u or API:IsCollected(spellID) then
            local code = rec.src or "?"
            if not buckets[code] then code = "?" end

            local owned = API:IsCollected(spellID)
            local name  = API:GetName(spellID, rec.i)

            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 self._hideRemoved and rec.u 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] =
                    { spellID = spellID, rec = rec, name = name, owned = owned }
            end
        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 = srcNames[code] or 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 _, m in ipairs(list) do
                    m.kind = "mount"
                    self._display[#self._display + 1] = m
                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:SetTexture(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:SetTexture(0.24, 0.24, 0.32, 0.95) end)
    row:SetScript("OnLeave", function(s) s.bg:SetTexture(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:_acquireMountRow()
    self._mountUsed = self._mountUsed + 1
    local row = self._mountPool[self._mountUsed]
    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:SetTexture(0.20, 0.40, 0.60, 0.55)
    row.sel:Hide()

    local hl = row:CreateTexture(nil, "HIGHLIGHT")
    hl:SetAllPoints()
    hl:SetTexture(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._spellID then return end
        GameTooltip:SetOwner(s, "ANCHOR_RIGHT")
        local itemID = s._rec and s._rec.i
        -- v15.38.0: only show the item tooltip when it's a valid, already-cached
        -- itemID. Setting an uncached item leaves an async window where a hooking
        -- addon (e.g. LoonBestInSlot) reads a nil itemID off the still-loading
        -- tooltip and errors inside Blizzard's item-load callback. The spell
        -- tooltip is always safe and is the natural representation for a mount.
        if type(itemID) == "number" and itemID > 0 and GetItemInfo(itemID) then
            GameTooltip:SetItemByID(itemID)
        else
            GameTooltip:SetSpellByID(s._spellID)
        end
        local d = EMT.SourceInfo:Describe(s._spellID, s._rec)
        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)
            if d.dropRate then
                GameTooltip:AddLine("|cffaaaaaa" .. d.dropRate .. "|r")
            end
            if d.requirement then
                GameTooltip:AddLine("|cff88bbff" .. d.requirement .. "|r")
            end
            if d.removed then
                GameTooltip:AddLine("|cffff8800" .. d.removedText .. "|r")
            end
            GameTooltip:AddLine("|cff666666Click for full details|r")
        end
        GameTooltip:Show()
    end)
    row:SetScript("OnLeave", function() GameTooltip:Hide() end)
    row:SetScript("OnClick", function(s)
        UI:SelectMount(s._spellID)
    end)

    self._mountPool[self._mountUsed] = row
    return row
end

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

-- ----------------------------------------------------------------------------
-- LIST RENDER
-- ----------------------------------------------------------------------------

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

    local API   = EMT.MountAPI
    local rowW  = self.frame.scrollChild:GetWidth()
    local y     = 0
    for _, entry in ipairs(self._display) do
        if entry.kind == "header" then
            local row = self:_acquireHeaderRow()
            row._src = entry.src
            row:SetWidth(rowW)
            row.glyph:SetText(self._collapsed[entry.src] and "+" or "-")
            row.label:SetText(entry.label)
            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:_acquireMountRow()
            row._spellID = entry.spellID
            row._rec     = entry.rec
            row._owned   = entry.owned
            row:SetWidth(rowW - 12)
            row.icon:SetTexture(API:GetIcon(entry.spellID, entry.rec.i))
            row.mark:SetText(entry.owned and "|cff44ff44+|r" or "|cffff5555-|r")
            local qhex = API:QualityColor(entry.rec.q)
            row.name:SetText(qhex .. entry.name .. "|r")
            row.extra:SetText(EMT.SourceInfo:ShortLine(entry.spellID, entry.rec))
            if self._selectedSpell == entry.spellID 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 = EMT.MountAPI: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:SelectMount(spellID)
    self._selectedSpell = spellID
    self:ShowDetail(spellID)
    self:RefreshList()
end

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

    local rec = spellID and EMT.MountData and EMT.MountData[spellID]
    if not rec then
        d.hint:Show()
        d.icon:Hide(); d.name:SetText(""); d.status:SetText("")
        d.body:SetText(""); d.summon:Hide()
        return
    end
    d.hint:Hide()

    local API  = EMT.MountAPI
    local info = EMT.SourceInfo:Describe(spellID, rec)
    local owned = API:IsCollected(spellID)

    d.icon:Show()
    d.icon:SetTexture(API:GetIcon(spellID, rec.i))
    d.name:SetText(API:QualityColor(rec.q) .. API:GetName(spellID, rec.i) .. "|r")
    d.status:SetText(owned and "|cff44ff44Collected|r"
        or "|cffff5555Not collected|r")

    -- Compose the body text.
    local lines = {}
    lines[#lines + 1] = "|cffffd100" .. info.headline .. "|r"
    lines[#lines + 1] = info.howto
    if info.dropRate then
        lines[#lines + 1] = "|cffaaaaaaDrop rate:|r " .. info.dropRate
    elseif info.headline == "Boss Drop" or info.headline == "Rare Creature" then
        lines[#lines + 1] = "|cff777777Drop rate: not recorded|r"
    end
    if info.requirement then
        lines[#lines + 1] = "|cff88bbffRequires:|r " .. info.requirement
    end
    if info.removed then
        lines[#lines + 1] = "|cffff8800\226\154\160 " .. info.removedText
            .. " — you cannot obtain this currently.|r"
    end
    if info.detail then
        lines[#lines + 1] = " "
        lines[#lines + 1] = "|cffccccccHow:|r " .. info.detail
    end
    -- ETB enrichment note
    if not (rec.n and EMT.NPCNames and EMT.NPCNames:Get(rec.n))
       and (rec.src == "B" or rec.src == "R") then
        lines[#lines + 1] = " "
        if EMT.NPCNames and EMT.NPCNames:IsETBDetected() then
            lines[#lines + 1] = "|cff666666Exact creature name will appear "
                .. "once encountered in-game.|r"
        else
            lines[#lines + 1] = "|cff666666Install Enhanced Transmog Browser "
                .. "for richer creature names.|r"
        end
    end
    d.body:SetText(table.concat(lines, "\n\n"))

    if owned then
        d.summon:Show()
        d.summon._spell = spellID
    else
        d.summon:Hide()
    end
end

-- ----------------------------------------------------------------------------
-- SEGMENTED FILTER CONTROL
-- ----------------------------------------------------------------------------

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:SetTexture(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:SetTexture(0.25, 0.45, 0.65, 0.95)
            b.txt:SetTextColor(1, 1, 1)
        else
            b.bg:SetTexture(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", "EMTBrowserFrame", 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, "EMTBrowserFrame")

    f.title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    f.title:SetPoint("TOPLEFT", INSET, -14)
    f.title:SetText("Enhanced Mount Tracker")

    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 ""
        -- v15.43.0: debounce so typing rebuilds once, not per keystroke.
        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)

    f.hideRemoved = CreateFrame("CheckButton", nil, f, "UICheckButtonTemplate")
    f.hideRemoved:SetSize(22, 22)
    f.hideRemoved:SetPoint("LEFT", f.segmented.buttons[3], "RIGHT", 16, 0)
    f.hideRemoved.text = f.hideRemoved:CreateFontString(nil, "OVERLAY",
        "GameFontHighlightSmall")
    f.hideRemoved.text:SetPoint("LEFT", f.hideRemoved, "RIGHT", 2, 0)
    f.hideRemoved.text:SetText("Hide removed mounts")
    f.hideRemoved:SetScript("OnClick", function(c)
        UI._hideRemoved = c:GetChecked() and true or false
        UI:RefreshList()
    end)

    local div = f:CreateTexture(nil, "ARTWORK")
    div:SetTexture(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)
    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:SetTexture(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 mount to see how to collect it.")

    d.summon = CreateFrame("Button", nil, d, "UIPanelButtonTemplate")
    d.summon:SetSize(140, 24)
    d.summon:SetPoint("BOTTOM", 0, 14)
    d.summon:SetText("Summon Mount")
    d.summon:SetScript("OnClick", function(self)
        if self._spell then EMT.MountAPI:Summon(self._spell) end
    end)
    d.summon:Hide()

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

-- ----------------------------------------------------------------------------
-- SHOW / HIDE
-- ----------------------------------------------------------------------------

function UI:Show()
    self:Create()
    EMT.MountAPI:Refresh()
    self:RefreshList()
    self:ShowDetail(self._selectedSpell)
    self.frame:Show()
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

EMT:On("EMT_COLLECTION_CHANGED", function()
    if UI.frame and UI.frame:IsShown() then
        UI:RefreshList()
        UI:ShowDetail(UI._selectedSpell)
    end
end)
