local addonName, ns = ...

------------------------------------------------------------
-- Shopping List tab: header actions + scrollable entry rows.
------------------------------------------------------------

local ROW_HEIGHT = 26
local rows = {}
ns._shoppingRows = rows

-- Shown in the (otherwise blank) list area when the shopping list is empty,
-- so the space tells a new user how to start rather than sitting empty.
local EMPTY_TEXT = table.concat({
    ns.Colorize(ns.Theme.HEADING, "Your shopping list is empty"),
    "",
    "Track the decor and dyes you still need:",
    "- Ctrl+Alt+Click a decor in the housing catalog",
    "- Ctrl+Alt+Click a decor or dye item in chat or your bags",
    "- Click Add Dye above to browse every housing dye",
    "- Import a list with the Import button above",
}, "\n")

local initialized = false
local listContainer

local function CreateHeader(parent)
    -- Add Dye leads the cluster: add → manage → destructive.
    local addDyeButton = ns.CreateThemedButton(parent, "Add Dye")
    addDyeButton:SetSize(90, 22)
    addDyeButton:SetPoint("TOPLEFT", 0, 0)
    addDyeButton:SetScript("OnClick", function()
        ns.TogglePopup(ns._dyePickerFrame, function() ns:ShowDyePicker() end)
    end)
    ns._shoppingAddDyeButton = addDyeButton

    local importButton = ns.CreateThemedButton(parent, "Import")
    importButton:SetSize(90, 22)
    importButton:SetPoint("LEFT", addDyeButton, "RIGHT", 6, 0)
    importButton:SetScript("OnClick", function()
        ns.TogglePopup(ns._importPopup, function() ns:ShowImportPopup() end)
    end)
    ns._shoppingImportButton = importButton

    local exportButton = ns.CreateThemedButton(parent, "Export")
    exportButton:SetSize(90, 22)
    exportButton:SetPoint("LEFT", importButton, "RIGHT", 6, 0)
    exportButton:SetScript("OnClick", function()
        ns.TogglePopup(ns._copyFrame, function()
            ns:ShowCopyPopup(ns.ShoppingList.Export())
        end)
    end)
    ns._shoppingExportButton = exportButton

    local clearButton = ns.CreateThemedButton(parent, "Clear")
    clearButton:SetSize(90, 22)
    clearButton:SetPoint("LEFT", exportButton, "RIGHT", 6, 0)
    clearButton:SetScript("OnClick", function() ns.ShoppingList.Clear() end)

    -- Help sits on the far right, apart from the Import/Export/Clear cluster.
    local helpButton = ns.CreateThemedButton(parent, "Help")
    helpButton:SetSize(90, 22)
    helpButton:SetPoint("TOPRIGHT", 0, 0)
    helpButton:SetScript("OnClick", function()
        ns.TogglePopup(ns._helpFrame, function()
            if ns.ShowShoppingHelp then ns:ShowShoppingHelp() end
        end)
    end)
    ns._shoppingHelpButton = helpButton
end

local function CreateRow(parent, index)
    local row = CreateFrame("Button", nil, parent)
    row:SetHeight(ROW_HEIGHT)
    row:SetPoint("TOPLEFT", 0, -(index - 1) * ROW_HEIGHT)
    row:SetPoint("RIGHT", -4, 0)

    row.highlight = row:CreateTexture(nil, "BACKGROUND")
    row.highlight:SetAllPoints()
    row.highlight:SetColorTexture(ns.Theme.HOVER:GetRGBA())
    row.highlight:Hide()
    row:SetScript("OnEnter", function() row.highlight:Show() end)
    row:SetScript("OnLeave", function() row.highlight:Hide() end)

    row.icon = row:CreateTexture(nil, "ARTWORK")
    row.icon:SetSize(20, 20)
    row.icon:SetPoint("LEFT", 2, 0)

    row.text = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    row.text:SetPoint("LEFT", row.icon, "RIGHT", 6, 0)
    row.text:SetJustifyH("LEFT")
    row.text:SetWordWrap(false)

    row.qtyBox = CreateFrame("EditBox", nil, row)
    row.qtyBox:SetSize(34, 18)
    row.qtyBox:SetAutoFocus(false)
    row.qtyBox:SetNumeric(true)
    row.qtyBox:SetFontObject("GameFontHighlightSmall")
    row.qtyBox:SetJustifyH("CENTER")
    row.qtyBox:SetScript("OnEnterPressed", function(box)
        local entry = row.entry
        if entry then
            ns.ShoppingList.SetQty(entry.type, entry.id, box:GetNumber())
        end
        box:ClearFocus()
    end)
    row.qtyBox:SetScript("OnEscapePressed", function(box) box:ClearFocus() end)

    -- × rather than the letter X, matching the popup close buttons: the
    -- multiplication glyph is optically centred where the letter reads as
    -- text. All three glyph buttons take the factory's 1px down-right
    -- label nudge (see SetLabelOffset) -- lone glyphs sat visibly
    -- high-left of centre in buttons this small.
    row.removeButton = ns.CreateThemedButton(row, "×")
    row.removeButton:SetSize(22, 18)
    row.removeButton:SetPoint("RIGHT", -2, 0)
    row.removeButton:SetLabelOffset(1, -1)
    row.removeButton:SetScript("OnClick", function()
        local entry = row.entry
        if entry then
            ns.ShoppingList.Remove(entry.type, entry.id)
        end
    end)

    -- Right-side cluster, built right-to-left so each control anchors to
    -- its neighbor: [-] [qty] [+] [X]. The minus clamps at 1 (data layer);
    -- removal stays exclusively on the X button.
    row.plusButton = ns.CreateThemedButton(row, "+")
    row.plusButton:SetSize(18, 18)
    row.plusButton:SetPoint("RIGHT", row.removeButton, "LEFT", -4, 0)
    row.plusButton:SetLabelOffset(1, -1)
    row.plusButton:SetScript("OnClick", function()
        local entry = row.entry
        if entry then
            ns.ShoppingList.Increment(entry.type, entry.id, 1)
        end
    end)

    row.qtyBox:SetPoint("RIGHT", row.plusButton, "LEFT", -2, 0)

    row.minusButton = ns.CreateThemedButton(row, "-")
    row.minusButton:SetSize(18, 18)
    row.minusButton:SetPoint("RIGHT", row.qtyBox, "LEFT", -2, 0)
    row.minusButton:SetLabelOffset(1, -1)
    row.minusButton:SetScript("OnClick", function()
        local entry = row.entry
        if entry then
            ns.ShoppingList.Increment(entry.type, entry.id, -1)
        end
    end)

    row.text:SetPoint("RIGHT", row.minusButton, "LEFT", -6, 0)

    row:SetScript("OnClick", function(self)
        local entry = self.entry
        if not entry then return end
        if entry.type == "DECOR" then
            ns:OpenCatalogTo(entry)
        else
            local dye = ns.DYES and ns.DYES[entry.id]
            if dye and dye.itemID and GameTooltip then
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                pcall(GameTooltip.SetItemByID, GameTooltip, dye.itemID)
                GameTooltip:Show()
            end
        end
    end)

    return row
end

local function EnsureInitialized()
    if initialized then return end
    local parent = ns.shoppingPanel
    if not parent then return end
    initialized = true

    local header = CreateFrame("Frame", nil, parent)
    header:SetPoint("TOPLEFT", 0, 0)
    header:SetPoint("TOPRIGHT", 0, 0)
    header:SetHeight(26)
    CreateHeader(header)

    local scroll = CreateFrame("ScrollFrame", nil, parent, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 0, -32)
    scroll:SetPoint("BOTTOMRIGHT", -22, 0)
    ns._shoppingScroll = scroll

    listContainer = CreateFrame("Frame", nil, scroll)
    listContainer:SetSize(1, 1)
    scroll:SetScrollChild(listContainer)
    ns._shoppingListContainer = listContainer

    -- Empty-state hint, parented to the panel (not the scroll child) so it
    -- sits statically in the list area. Toggled by RefreshShoppingTab.
    local emptyState = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    emptyState:SetPoint("TOPLEFT", scroll, "TOPLEFT", 6, -6)
    emptyState:SetPoint("RIGHT", scroll, "RIGHT", -6, 0)
    emptyState:SetJustifyH("LEFT")
    emptyState:SetJustifyV("TOP")
    emptyState:SetWordWrap(true)
    emptyState:SetSpacing(3)
    emptyState:SetText(EMPTY_TEXT)
    emptyState:Hide()
    ns._shoppingEmptyState = emptyState

    -- The scroll child must match the scroll frame's width, otherwise rows
    -- (which anchor their RIGHT edge to the child) collapse to ~1px: icons
    -- render but the text, quantity box and remove button have no room.
    -- OnSizeChanged fires on the initial layout pass and on any resize.
    scroll:SetScript("OnSizeChanged", function(_, width)
        listContainer:SetWidth(width)
    end)
    local initialWidth = scroll:GetWidth()
    if initialWidth and initialWidth > 0 then
        listContainer:SetWidth(initialWidth)
    end
end

function ns:RefreshShoppingTab()
    EnsureInitialized()
    if not listContainer then return end

    local entries = ns.ShoppingList.GetEntries()
    for i, entry in ipairs(entries) do
        local row = rows[i]
        if not row then
            row = CreateRow(listContainer, i)
            rows[i] = row
        end
        row.entry = entry

        local available, inUse = ns.Availability.Get(entry.type, entry.id)
        local fulfilled = available ~= nil and available >= entry.qty
        row.text:SetText(ns.FormatEntryLine(entry, available, inUse))
        if fulfilled then
            row.text:SetTextColor(ns.Theme.TEXT_MUTED:GetRGBA())
        else
            row.text:SetTextColor(ns.Theme.LABEL:GetRGBA())
        end

        if entry.type == "DYE" then
            local dye = ns.DYES and ns.DYES[entry.id]
            local r, g, b = ns.HexToRGB(dye and dye.colorEnd)
            row.icon:SetColorTexture(r or 0.4, g or 0.4, b or 0.4, 1)
        else
            local icon = ns.ResolveIcon(entry.type, entry.id)
            if icon then
                row.icon:SetTexture(icon)
            else
                row.icon:SetColorTexture(ns.Theme.PANEL:GetRGBA())
            end
        end

        row.qtyBox:SetText(tostring(entry.qty))
        row:Show()
    end

    for i = #entries + 1, #rows do
        rows[i]:Hide()
        rows[i].entry = nil
    end

    if ns._shoppingEmptyState then
        if #entries == 0 then
            ns._shoppingEmptyState:Show()
        else
            ns._shoppingEmptyState:Hide()
        end
    end

    listContainer:SetHeight(math.max(1, #entries * ROW_HEIGHT))
end

------------------------------------------------------------
-- Open the Blizzard housing catalog at an entry.
--
-- /hh catalogprobe on the live client showed: the catalog UI ships in
-- the load-on-demand Blizzard_HousingDashboard addon (no frame exists
-- until loaded), the C_ tables expose no open/toggle function, and
-- HousingMicroButton is the user-facing way in. So: load the addon,
-- try toggle-function candidates, then fall back to clicking the
-- micro button — and only claim success if a dashboard frame is
-- actually shown afterwards.
------------------------------------------------------------

local DASHBOARD_TOGGLE_FNS = {
    "ToggleHousingDashboard", "ShowHousingDashboard", "OpenHousingDashboard",
}
local DASHBOARD_FRAMES = { "HousingDashboardFrame", "HousingCatalogFrame" }

-- Switch the dashboard to its catalog tab: the purpose-built
-- OnOpenToCatalogFrame entry point first (confirmed on live via
-- /hh catalogprobe), then clicking the tab button.
local function SelectCatalogTab(frame)
    if type(frame.OnOpenToCatalogFrame) == "function" then
        pcall(frame.OnOpenToCatalogFrame, frame)
    end
    local content = frame.CatalogContent
    if content and type(content.IsShown) == "function" then
        local ok, shown = pcall(content.IsShown, content)
        if ok and shown then return end
    end
    local button = frame.CatalogTabButton
    if button and type(button.Click) == "function" then
        pcall(button.Click, button)
    end
end

-- The dashboard's search editbox is unnamed (SearchBoxTemplate); probe
-- the likely parentKeys, preferring the catalog panel's own box.
local function FindSearchBox(frame)
    local content = frame.CatalogContent
    local candidates = {
        content and content.SearchBox or false,
        content and content.SearchBar or false,
        content and content.Search or false,
        frame.SearchBox or false,
    }
    for _, box in ipairs(candidates) do
        if type(box) == "table" and type(box.SetText) == "function" then
            return box
        end
    end
    return nil
end

local function shownDashboardFrame()
    for _, frameName in ipairs(DASHBOARD_FRAMES) do
        local frame = _G[frameName]
        if type(frame) == "table" and type(frame.IsShown) == "function" then
            local ok, shown = pcall(frame.IsShown, frame)
            if ok and shown then return frame end
        end
    end
    return nil
end

function ns:OpenCatalogTo(entry)
    local name = entry.name or ns.ResolveName(entry.type, entry.id)

    if C_AddOns and C_AddOns.LoadAddOn then
        pcall(C_AddOns.LoadAddOn, "Blizzard_HousingDashboard")
    end

    local frame = shownDashboardFrame()
    if not frame then
        for _, fnName in ipairs(DASHBOARD_TOGGLE_FNS) do
            if type(_G[fnName]) == "function" then
                pcall(_G[fnName])
                break
            end
        end
        frame = shownDashboardFrame()
    end
    if not frame then
        local micro = _G.HousingMicroButton
        if micro and type(micro.Click) == "function" then
            pcall(micro.Click, micro)
        end
        frame = shownDashboardFrame()
    end

    if frame then
        SelectCatalogTab(frame)
        local content = frame.CatalogContent

        -- Precise navigation: the catalog panel exposes OnOpenToDecorID
        -- (confirmed live), and our DECOR id is that decor record ID.
        if entry.type == "DECOR" and content
            and type(content.OnOpenToDecorID) == "function" then
            if pcall(content.OnOpenToDecorID, content, entry.id) then
                return true
            end
        end

        -- Fallback for dyes or older clients: fill the search box by name.
        local search = FindSearchBox(frame)
        if search and name then
            pcall(search.SetText, search, name)
            -- SetText may not fire the panel's search on its own; nudge it.
            if content and type(content.OnSearchTextUpdated) == "function" then
                pcall(content.OnSearchTextUpdated, content)
            end
        end
        return true
    end

    ns.Print(("Find %s in the housing catalog (open Housing mode to browse).")
        :format(name or ("decor #" .. entry.id)))
    return false
end

------------------------------------------------------------
-- Keep the tab and tracker in sync with data changes.
------------------------------------------------------------

ns.ShoppingList.OnChanged(function()
    ns:RefreshShoppingTab()
end)
ns.Availability.OnChanged(function()
    ns:RefreshShoppingTab()
end)
