-- VDS_Selectors_Detail
-- ============================================================================
-- Footer detail strip + palette editing strip selectors.
-- ADR-024: every selector returns data; string composition is in widget kinds.

local Selectors = VDS.Selectors
local C         = VDS.Constants.COLOR_CODE  -- brand color escapes

-- Name from catalog.infos (invariant §1 + §9); returns nil when no dye selected.

Selectors:Register("detail.dye", {
    reads = { "session.ui.selectedDyeID", "session.dyes.catalog" },
    fn    = function(state)
        local id = state.session.ui.selectedDyeID
        if not id then return nil end
        local info = state.session.dyes.catalog.infos[id]
        return {
            dyeColorID = id,
            name       = info and info.name,  -- exception(nullable): catalog info can miss pre-DYE_DATA_INIT
        }
    end,
})

Selectors:Register("detail.dye.name", {
    reads = { "session.ui.selectedDyeID" },
    calls = { "detail.dye" },
    fn    = function(state)
        local d = Selectors:Call("detail.dye", state, {})
        return d and d.name  -- exception(nullable): no selected dye = no detail
    end,
})

-- Scalar label: name or "Dye <id>" placeholder.
Selectors:Register("detail.dye.label", {
    reads = { "session.ui.selectedDyeID" },
    calls = { "detail.dye" },
    fn    = function(state)
        local d = Selectors:Call("detail.dye", state, {})
        if not d then return "" end
        return d.name or ("Dye " .. d.dyeColorID)
    end,
})

-- "Color: <group>   Type: <category>" (donor parity).
-- catID resolved at paint time; colorGroup via valueKind -- avoids storing strings in state.
Selectors:Register("detail.line1.parts", {
    reads = { "session.ui.selectedDyeID", "session.dyes.catalog" },
    fn    = function(state)
        local id = state.session.ui.selectedDyeID
        if not id then return {} end
        local catalog = state.session.dyes.catalog
        local info    = catalog.infos[id]
        return {
            { label = "Color", value = nil, valueKind = "colorGroup", dyeColorID = id },
            { label = "Type",  value = nil, valueKind = "dyeCategory", catID = info and info.catID },  -- exception(nullable): catalog info can miss
        }
    end,
})

-- "Owned: <count>   Cost: ~Ng" (donor parity); cost segment dropped when no Auctionator listing.
Selectors:Register("detail.line2.parts", {
    reads = { "session.ui.selectedDyeID", "session.dyes.ownership", "session.dyes.catalog" },
    fn    = function(state)
        local id = state.session.ui.selectedDyeID
        if not id then return {} end
        local owned   = state.session.dyes.ownership.perDye[id] or 0  -- exception(nullable): sparse ownership map; miss = own 0
        local catalog = state.session.dyes.catalog
        local info    = catalog.infos[id]
        local parts   = { { label = "Owned", value = owned } }
        if info and info.itemID then  -- exception(nullable): catalog info/itemID can miss for unmapped dyes
            local copper = VDS.StaticData.GetAuctionPrice(info.itemID)
            if copper then
                local pretty = VDS.StaticData.FormatGold(copper)
                if pretty then
                    parts[#parts + 1] = { label = "Cost", value = "~" .. pretty }
                end
            end
        end
        return parts
    end,
})

-- Donor default "Expac"; lives in byView[filterMode].herbSortMode.
Selectors:Register("detail.herbSort.mode", {
    reads = { "session.ui.filterMode", "session.ui.byView" },
    fn    = function(state)
        local ui = state.session.ui
        return "Sort: " .. (ui.byView[ui.filterMode].herbSortMode or "Expac")
    end,
})

-- Herb detail (donor VDS_DyeDetail.lua:284-393); visible in herbs tab with a herb selected.

Selectors:Register("detail.has.herb", {
    reads = { "session.ui.filterMode", "session.ui.selectedHerbID" },
    fn    = function(state)
        return state.session.ui.filterMode == "herbs"
               and state.session.ui.selectedHerbID ~= nil
    end,
})

-- boundary: selectors don't read _G.*DB directly (invariant §1).
local function herbToPigment(herbID)
    return VDS.StaticData.GetHerbPigmentEntry(herbID)
end

Selectors:Register("detail.herb.line1", {
    reads = { "session.ui.selectedHerbID", "session.items.names", "session.items.tick" },
    fn    = function(state)
        local id = state.session.ui.selectedHerbID
        if not id then return "" end
        local name = state.session.items.names[id] or RETRIEVING_DATA
        local entry = herbToPigment(id)
        local parts = { C.brandTeal .. name .. C.reset }
        if entry then
            local exp = VDS.StaticData.GetHerbExpansion(entry.expIdx)
            if exp then
                local c = exp.color
                parts[#parts + 1] = ("|cff%02x%02x%02x%s|r"):format(
                    math.floor(c[1] * 255), math.floor(c[2] * 255), math.floor(c[3] * 255),
                    exp.abbr or "")
            end
            if entry.pigment.category then
                parts[#parts + 1] = entry.pigment.category .. " Pigment"
            end
        end
        return table.concat(parts, C.dim .. "/" .. C.reset)
    end,
})

Selectors:Register("detail.herb.line2", {
    -- session.bag.tick: BagObserver bumps on BAG_UPDATE_DELAYED (HDGR pattern).
    reads = { "session.ui.selectedHerbID", "session.bag.tick" },
    fn    = function(state)
        local id = state.session.ui.selectedHerbID
        if not id then return "" end
        local entry = herbToPigment(id)
        local SD = VDS.StaticData
        local stock = VDS.BagObserver:GetTotal(id)
        local herbQty = entry and entry.pigment.herbQty or 10  -- exception(nullable): herb-pigment lookup can miss; estimate 10/craft
        local craftable = math.floor(stock / herbQty)
        local craftStr = craftable > 0
            and (" " .. C.dim .. "(" .. C.reset .. C.success .. craftable
                 .. C.reset .. " " .. C.dim .. "crafts)" .. C.reset)
            or ""
        local stockColor = stock >= 10 and C.success
                            or (stock > 0 and C.warn or C.placeholder)
        local price = SD.GetAuctionPrice(id)
        local priceStr
        if price then
            local pretty = SD.FormatGold(price)
            priceStr = C.warn .. (pretty or "?") .. C.reset .. " " .. C.dim .. "per herb" .. C.reset
        else
            priceStr = C.dim .. "No price data" .. C.reset
        end
        return C.placeholder .. "Stock:" .. C.reset .. " " .. stockColor .. stock .. C.reset .. craftStr
               .. "   " .. C.placeholder .. "AH:" .. C.reset .. " " .. priceStr
    end,
})

Selectors:Register("detail.herb.line3", {
    reads = { "session.ui.selectedHerbID" },
    fn    = function(state)
        local id = state.session.ui.selectedHerbID
        if not id then return "" end
        local entry = herbToPigment(id)
        if not entry then return "" end
        local SD = VDS.StaticData
        local price = SD.GetAuctionPrice(id)
        local pigment = entry.pigment
        local herbQty = pigment.herbQty or 10  -- exception(optional): herbQty optional in pigment DB; estimate 10/craft
        local parts = {}
        if price then
            local pretty = SD.FormatGold(price * herbQty)
            parts[#parts + 1] = C.placeholder .. "Cost/Dye:" .. C.reset .. " "
                                .. C.warn .. (pretty or "?") .. C.reset
                                .. " " .. C.dim .. "(" .. herbQty .. " herbs)" .. C.reset
        end
        local dyeCount = SD.GetDyeCountForPigment(entry.pigItemID)
        if dyeCount > 0 then
            parts[#parts + 1] = C.placeholder .. "Dyes:" .. C.reset .. " " .. dyeCount .. " "
                                .. (pigment.category or "") .. " dyes"
        end
        return table.concat(parts, "   ")
    end,
})

-- Hide swatch + meta cluster when nothing selected (donor parity: empty strip in herbs/canvas).
Selectors:Register("detail.has.dye", {
    reads = { "session.ui.filterMode", "session.ui.selectedDyeID" },
    fn    = function(state)
        local mode = state.session.ui.filterMode
        if mode == "herbs" or mode == "canvas" or mode == "custom" then return false end
        return state.session.ui.selectedDyeID ~= nil
    end,
})

-- "Editing: Foo   Dyes: 6/20   Est: ~Ng"; cost segment omitted when no price source.
Selectors:Register("detail.has.palette", {
    reads = { "session.ui.filterMode", "session.ui.byView" },
    fn    = function(state)
        if state.session.ui.filterMode ~= "custom" then return false end
        local bucket = state.session.ui.byView[state.session.ui.filterMode]
        return (bucket.editingThemeName or bucket.activeTheme) ~= nil
    end,
})

Selectors:Register("detail.palette.summary", {
    reads = {
        "session.ui.filterMode",
        "session.ui.byView",
        "session.dyes.catalog",
        "account.customThemes",
    },
    fn    = function(state)
        if state.session.ui.filterMode ~= "custom" then return "" end
        local bucket  = state.session.ui.byView[state.session.ui.filterMode]
        local editing = bucket.editingThemeName
        local active  = editing or bucket.activeTheme
        if not active then return "" end

        local theme
        for _, t in ipairs(state.account.customThemes) do
            if t.name == active then theme = t; break end
        end
        if not theme then return "" end

        local dyes    = theme.dyes or {}  -- pre-v2 migration guard
        local infos   = state.session.dyes.catalog.infos  -- pre-init shape guaranteed
        local SD = VDS.StaticData
        local totalCopper = 0
        local anyPrice = false
        for _, dyeID in ipairs(dyes) do
            local info = infos[dyeID]
            local price = info and info.itemID and SD.GetAuctionPrice(info.itemID) or nil  -- exception(nullable): info can miss; price via optional Auctionator
            if price then totalCopper = totalCopper + price; anyPrice = true end
        end

        local parts = {}
        parts[#parts + 1] = (editing and (C.success .. "Editing" .. C.reset .. ": ")
                                       or (C.placeholder .. "Palette" .. C.reset .. ": "))
                            .. theme.name
        parts[#parts + 1] = C.placeholder .. "Dyes" .. C.reset .. ": " .. #dyes .. "/20"
        if anyPrice then
            local pretty = SD.FormatGold(totalCopper)
            if pretty then
                parts[#parts + 1] = C.placeholder .. "Est" .. C.reset .. ": ~" .. C.warn .. pretty .. C.reset
            end
        end
        return table.concat(parts, "   ")
    end,
})

-- (cart.hasItems + cart.actionsShown removed: Stock tab is the sole cart workflow.)

-- "Edit" when viewing a palette; "Done" when editing.
Selectors:Register("detail.editToggle.text", {
    reads = { "session.ui.filterMode", "session.ui.byView" },
    fn    = function(state)
        return state.session.ui.byView[state.session.ui.filterMode].editingThemeName and "Done" or "Edit"
    end,
})

-- Spacer only for fixed-width left clusters; herbs herbInfo is width="fill" so a second fill splits space 50/50.
Selectors:Register("detail.spacerShown", {
    reads = { "session.ui.filterMode", "session.ui.selectedHerbID" },
    fn    = function(state)
        local mode = state.session.ui.filterMode
        if mode == "custom" then return false end
        if mode == "herbs" and state.session.ui.selectedHerbID then
            return false  -- herbInfo absorbs the slack
        end
        return true
    end,
})

-- Palette strip (visible only when editing a custom theme).

Selectors:Register("palette.shown", {
    reads = { "session.ui.filterMode", "session.ui.byView" },
    fn    = function(state)
        return state.session.ui.byView[state.session.ui.filterMode].editingThemeName ~= nil
    end,
})

-- Drives the Rename/Delete/Export/Edit-Done action grid visibility. Includes
-- activeTheme (palette selected but not yet editing) so the Edit button is
-- reachable -- gating on editingThemeName alone hid the buttons that toggle it.
Selectors:Register("palette.editing", {
    reads = { "session.ui.filterMode", "session.ui.byView" },
    fn    = function(state)
        local b = state.session.ui.byView[state.session.ui.filterMode]
        return b.editingThemeName ~= nil or b.activeTheme ~= nil
    end,
})

Selectors:Register("palette.label.name", {
    reads = { "session.ui.filterMode", "session.ui.byView" },
    fn    = function(state)
        return state.session.ui.byView[state.session.ui.filterMode].editingThemeName
    end,
})

Selectors:Register("palette.swatches.dyeIDs", {
    reads = { "session.ui.filterMode", "session.ui.byView", "account.customThemes" },
    fn    = function(state)
        local name = state.session.ui.byView[state.session.ui.filterMode].editingThemeName
        if not name then return {} end
        for _, theme in ipairs(state.account.customThemes) do
            if theme.name == name then return theme.dyes end
        end
        return {}
    end,
})
