local addonName, ns = ...

------------------------------------------------------------
-- Add Dye picker: a searchable, categorised list of every housing dye.
-- Clicking a row adds that dye to the shopping list and bumps its quantity
-- on repeat clicks, so several dyes can be added without closing the dialog.
------------------------------------------------------------

local ROW_HEIGHT = 20
local SWATCH_SIZE = 14

-- pairs() over ns.DYES is unordered, so the display order is fixed here.
local CATEGORY_ORDER = { "General", "Wood", "Metal" }

local frame = CreateFrame("Frame", "HHDyePicker", UIParent, "BackdropTemplate")
frame:SetSize(420, 480)
frame:SetPoint("CENTER")
frame:SetFrameStrata("FULLSCREEN_DIALOG")
frame:SetMovable(true)
frame:EnableMouse(true)
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnDragStart", frame.StartMoving)
frame:SetScript("OnDragStop", frame.StopMovingOrSizing)
frame:SetClampedToScreen(true)
frame:Hide()
frame:SetBackdrop({
    bgFile = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 14,
    insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
frame:SetBackdropColor(ns.Theme.BG.r, ns.Theme.BG.g, ns.Theme.BG.b, 0.97)
frame:SetBackdropBorderColor(ns.Theme.BORDER:GetRGBA())
ns._dyePickerFrame = frame
ns.RegisterPopup(frame)

local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
title:SetPoint("TOP", 0, -10)
title:SetText("Add Dye")
title:SetTextColor(ns.Theme.HEADING:GetRGBA())

ns.CreateThemedCloseButton(frame)

local searchBox = CreateFrame("EditBox", nil, frame, "BackdropTemplate")
searchBox:SetPoint("TOPLEFT", 14, -34)
searchBox:SetPoint("TOPRIGHT", -14, -34)
searchBox:SetHeight(20)
searchBox:SetAutoFocus(false)
searchBox:SetFontObject("GameFontHighlightSmall")
searchBox:SetTextInsets(6, 6, 0, 0)
searchBox:SetBackdrop({
    bgFile = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
    edgeSize = 10,
    insets = { left = 2, right = 2, top = 2, bottom = 2 },
})
searchBox:SetBackdropColor(ns.Theme.PANEL:GetRGBA())
searchBox:SetBackdropBorderColor(ns.Theme.BORDER:GetRGBA())
searchBox:SetScript("OnTextChanged", function() ns:RefreshDyePicker() end)
searchBox:SetScript("OnEscapePressed", function(box) box:ClearFocus() end)
ns._dyePickerSearch = searchBox

local scroll = CreateFrame("ScrollFrame", nil, frame, "UIPanelScrollFrameTemplate")
scroll:SetPoint("TOPLEFT", 14, -62)
scroll:SetPoint("BOTTOMRIGHT", -32, 14)

local content = CreateFrame("Frame", nil, scroll)
content:SetSize(1, 1)
scroll:SetScrollChild(content)

-- The scroll child must track the scroll frame's width, or rows (which anchor
-- their RIGHT edge to the child) collapse to ~1px.
scroll:SetScript("OnSizeChanged", function(_, width)
    content:SetWidth(width)
end)
local initialWidth = scroll:GetWidth()
if initialWidth and initialWidth > 0 then
    content:SetWidth(initialWidth)
end

local emptyText = frame:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
emptyText:SetPoint("TOPLEFT", scroll, "TOPLEFT", 6, -6)
emptyText:SetText("No dyes match.")
emptyText:SetTextColor(ns.Theme.TEXT_MUTED:GetRGBA())
emptyText:Hide()
ns._dyePickerEmpty = emptyText

local rows = {}
ns._dyePickerRows = rows

-- Green tick, sized to the line height, matching the tracker's fulfilled mark.
local CHECK_MARKUP = "|TInterface\\RaidFrame\\ReadyCheck-Ready:0|t"

-- Ordered, grouped, filtered view of ns.DYES: a header row per category with
-- its dyes (alphabetical) beneath. Categories absent from CATEGORY_ORDER are
-- appended alphabetically, so a new category from the data feed still renders.
local function BuildRows(query)
    local byCategory = {}
    for id, dye in pairs(ns.DYES or {}) do
        if query == "" or (dye.name or ""):lower():find(query, 1, true) then
            local cat = dye.category or "Other"
            byCategory[cat] = byCategory[cat] or {}
            table.insert(byCategory[cat], { id = id, dye = dye })
        end
    end

    local order, seen = {}, {}
    for _, cat in ipairs(CATEGORY_ORDER) do
        if byCategory[cat] then
            table.insert(order, cat)
            seen[cat] = true
        end
    end
    local extras = {}
    for cat in pairs(byCategory) do
        if not seen[cat] then table.insert(extras, cat) end
    end
    table.sort(extras)
    for _, cat in ipairs(extras) do table.insert(order, cat) end

    local out = {}
    for _, cat in ipairs(order) do
        local group = byCategory[cat]
        table.sort(group, function(a, b) return a.dye.name < b.dye.name end)
        table.insert(out, { header = cat })
        for _, item in ipairs(group) do
            table.insert(out, { id = item.id, dye = item.dye })
        end
    end
    return out
end

-- Dyes have a start and end colour, so the swatch is a gradient. The solid
-- fill is set first: it tints the texture for the gradient and is the fallback
-- if SetGradient is unavailable.
local function SetSwatch(tex, dye)
    local r1, g1, b1 = ns.HexToRGB(dye.colorStart)
    local r2, g2, b2 = ns.HexToRGB(dye.colorEnd)

    -- SetGradient modulates the texture's existing vertex colour, so the base
    -- must be white: painting colorEnd first would square it, rendering the
    -- many dyes with a near-black colorEnd (#0E0505 and friends) as black.
    if r1 and r2 and tex.SetGradient and CreateColor then
        tex:SetColorTexture(1, 1, 1, 1)
        local ok = pcall(tex.SetGradient, tex, "VERTICAL",
            CreateColor(r2, g2, b2, 1), CreateColor(r1, g1, b1, 1))
        if ok then return end
    end

    -- No gradient available (or it failed): a solid swatch of the end colour.
    tex:SetColorTexture(r2 or r1 or 0.4, g2 or g1 or 0.4, b2 or b1 or 0.4, 1)
end

local function EnsureRow(index)
    local row = rows[index]
    if row then return row end

    row = CreateFrame("Button", nil, content)
    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()
    -- Header rows carry no dyeID and must not look clickable.
    row:SetScript("OnEnter", function(self)
        if self.dyeID then self.highlight:Show() end
    end)
    row:SetScript("OnLeave", function(self) self.highlight:Hide() end)

    row.swatch = row:CreateTexture(nil, "ARTWORK")
    row.swatch:SetSize(SWATCH_SIZE, SWATCH_SIZE)
    row.swatch:SetPoint("LEFT", 4, 0)

    row.text = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    row.text:SetPoint("LEFT", row.swatch, "RIGHT", 8, 0)
    row.text:SetPoint("RIGHT", -44, 0)
    row.text:SetJustifyH("LEFT")
    row.text:SetWordWrap(false)

    row.badge = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    row.badge:SetPoint("RIGHT", -6, 0)
    row.badge:SetTextColor(ns.Theme.HEADING:GetRGBA())

    row:SetScript("OnClick", function(self)
        if not self.dyeID then return end
        local dye = ns.DYES and ns.DYES[self.dyeID]
        ns.ShoppingList.Increment("DYE", self.dyeID, 1, dye and dye.name or nil)
    end)

    rows[index] = row
    return row
end

function ns:RefreshDyePicker()
    local query = (searchBox:GetText() or ""):lower():match("^%s*(.-)%s*$")
    local list = BuildRows(query)

    local previousCount = ns._dyePickerRowCount or 0
    if #list ~= previousCount then
        -- The rendered result set changed size (e.g. the user is filtering);
        -- snap back to the top so a still-scrolled-down view doesn't land on
        -- an empty-looking list.
        scroll:SetVerticalScroll(0)
    end
    ns._dyePickerRowCount = #list

    for i, item in ipairs(list) do
        local row = EnsureRow(i)
        if item.header then
            row.dyeID = nil
            row.highlight:Hide()
            row.swatch:Hide()
            row.badge:SetText("")
            row.text:SetText(item.header)
            row.text:SetTextColor(ns.Theme.HEADING:GetRGBA())
        else
            row.dyeID = item.id
            row.swatch:Show()
            SetSwatch(row.swatch, item.dye)
            row.text:SetText(item.dye.name)
            row.text:SetTextColor(1, 1, 1, 1)
            local existing = ns.ShoppingList.Find("DYE", item.id)
            row.badge:SetText(existing and (CHECK_MARKUP .. existing.qty) or "")
        end
        row:Show()
    end

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

    if #list == 0 then
        emptyText:Show()
    else
        emptyText:Hide()
    end

    content:SetHeight(math.max(1, #list * ROW_HEIGHT))
end

function ns:ShowDyePicker()
    ns.ShowPopup(frame)
    searchBox:SetText("")
    ns:RefreshDyePicker()
    if searchBox.SetFocus then searchBox:SetFocus() end
end

-- Keep the quantity badges live while the picker is open.
ns.ShoppingList.OnChanged(function()
    if frame:IsShown() then ns:RefreshDyePicker() end
end)
