-- VDS_Components_Harmony
-- ============================================================================
-- harmonyBand widget kind -- Colors-tab horizontal harmony band.
-- Spec (§4 of VDS_AutoHarmonise_Design.md): one horizontal band of 4 labelled
-- relationship groups (Neutral, Analogous, Complement, Discordant) + a
-- "+ Bold" toggle that reveals the Triad group. Each group has 1-2 chips;
-- each chip is a 28px gradient swatch (swatchStart->swatchEnd) matching the
-- recentDyeStrip swatch chrome. Unowned picks are dimmed. Click selects the
-- dye + scrolls the Colors WowScrollBoxList to its row.
--
-- Widget kind: "harmonyBand"
-- Binding field: "data" (harmony.band.data output table | nil)
-- Visible gate: "harmony.visible" (selector)
-- Click wired by: VDS_Controller_Harmony.lua (Controller:Wire pattern)
--
-- All paint via Theme:Register + SetVertexColor. No inline SetBackdropColor.
-- Uses existing ATLAS constants for the 28px gradient swatch (same as recent
-- dye strip in VDS_Components_Custom.lua:862-885).

VDS = VDS or {}
local UI    = VDS.UI
local Types = VDS.WidgetTypes
local WHITE = "Interface\\Buttons\\WHITE8x8"

-- Watercolor wash texture path (same as dye rows + colorSwatch).
local DYE_SWATCH_TEX = "Interface\\AddOns\\VamoosesDyeStudio\\textures\\vds_swatch_tex"

-- Groups shown permanently; Triad shown only when boldMode is active.
-- Same set + labels as the Live/Canvas strategy buttons (one vocabulary across
-- every surface). Chromatic anchors show the 7 hue strategies; neutral anchors
-- swap to the 3 value/temperature ones (each set is empty for the other anchor).
local BAND_GROUPS = {
    { rel = "Neutral",       label = "Neutral", picks = "neutral",    count = 2 },
    { rel = "Monochromatic", label = "Mono",    picks = "mono",       count = 2 },
    { rel = "Analogous",     label = "Analog",  picks = "analogous",  count = 2 },
    { rel = "Complement",    label = "Comp",    picks = "complement", count = 1 },
    { rel = "Split",         label = "Split",   picks = "split",      count = 2 },
    { rel = "Triad",         label = "Triad",   picks = "triad",      count = 2 },
    { rel = "Clash",         label = "Clash",   picks = "clash",      count = 1 },
    { rel = "Tones",         label = "Tones",   picks = "tones",      count = 2 },
    { rel = "Contrast",      label = "Contrast", picks = "contrast",  count = 1 },
    { rel = "Accent",        label = "Accent",  picks = "accent",     count = 2 },
}

local CHIP_SIZE = 28
local CHIP_GAP  = 3
local LABEL_H   = 14
local GROUP_GAP = 12  -- horizontal gap between groups
local ROW_GAP   = 4   -- vertical gap between the two rows

-- ============================================================================
-- buildChipChrome(frame, size)
-- Adds the three dye-swatch atlas textures (Base, Gradient, Border) to an
-- already-created frame/button at the requested pixel size. Returns nothing;
-- textures are stored as frame._swBase / ._swGrad / ._border.
-- Shared by buildChip (28px chips) and the gradientSwatch widget (44px swatch).
-- ============================================================================
local function buildChipChrome(frame, size)
    local ATLAS = VDS.Constants.ATLAS

    local swBase = frame:CreateTexture(nil, "ARTWORK", nil, 1)
    swBase:SetSize(size, size)
    swBase:SetPoint("CENTER")
    swBase:SetAtlas(ATLAS.DYE_SWATCH_BASE, true)  -- useAtlasSize=true keeps 1:1 texels; SetSize overrides
    swBase:SetSize(size, size)                      -- force after SetAtlas
    frame._swBase = swBase

    local swGrad = frame:CreateTexture(nil, "ARTWORK", nil, 2)
    swGrad:SetSize(size, size)
    swGrad:SetPoint("CENTER")
    swGrad:SetAtlas(ATLAS.DYE_SWATCH_GRADIENT, true)
    swGrad:SetSize(size, size)
    frame._swGrad = swGrad

    local border = frame:CreateTexture(nil, "OVERLAY", nil, 1)
    border:SetSize(size, size)
    border:SetPoint("CENTER")
    border:SetAtlas(ATLAS.DYE_FRAME_DEFAULT, true)
    border:SetSize(size, size)
    frame._border = border
end

-- ============================================================================
-- Build one swatch chip Button (28px, clickable, with tooltip + highlight).
-- ============================================================================
local function buildChip(parent)
    local btn = CreateFrame("Button", nil, parent)
    btn:SetSize(CHIP_SIZE, CHIP_SIZE)
    btn:RegisterForClicks("AnyUp")

    buildChipChrome(btn, CHIP_SIZE)

    local hl = btn:CreateTexture(nil, "HIGHLIGHT")
    hl:SetAllPoints()
    hl:SetTexture(WHITE)
    hl:SetVertexColor(1, 1, 1, 0.15)

    VDS.TooltipEngine:Attach(btn, VDS.TooltipRecipes.HarmonyChip)

    return btn
end

-- ============================================================================
-- Build one group column (label + chips vertical stack).
-- Returns the group frame + the chip array.
-- ============================================================================
local function buildGroupColumn(parent, labelText, numChips)
    local colH   = LABEL_H + CHIP_GAP + CHIP_SIZE
    local colW   = numChips * CHIP_SIZE + (numChips - 1) * CHIP_GAP
    local col    = CreateFrame("Frame", nil, parent)
    col:SetSize(colW, colH)

    -- Group card: a subtle bordered panel behind the WHOLE group (label + chips)
    -- so each relationship reads as one contained unit instead of loose swatches
    -- under a floating "sticky note" label. The dark card also backs the white
    -- label, so it stays legible on any wash (no per-label scrim needed). Outset
    -- horizontally into the inter-group gap for breathing room; the border is a
    -- 1px lighter ring around the fill.
    local cardBorder = col:CreateTexture(nil, "BACKGROUND", nil, 0)
    cardBorder:SetPoint("TOPLEFT",     col, "TOPLEFT",     -4,  1)
    cardBorder:SetPoint("BOTTOMRIGHT", col, "BOTTOMRIGHT",  4, -1)
    cardBorder:SetTexture(WHITE)
    cardBorder:SetVertexColor(1, 1, 1, 0.12)
    col._cardBorder = cardBorder

    local card = col:CreateTexture(nil, "BACKGROUND", nil, 1)
    card:SetPoint("TOPLEFT",     col, "TOPLEFT",     -3,  0)
    card:SetPoint("BOTTOMRIGHT", col, "BOTTOMRIGHT",  3,  0)
    card:SetTexture(WHITE)
    card:SetVertexColor(0, 0, 0, 0.26)
    col._card = card

    local label  = col:CreateFontString(nil, "OVERLAY")
    UI.applyFontRole(label, "small")
    label:SetPoint("TOPLEFT", col, "TOPLEFT", 1, 0)
    label:SetJustifyH("LEFT")
    label:SetText(labelText)
    -- Label stays white; the drop shadow + dark group card keep it legible on
    -- any wash. Registered for theme-default (no-data) colour only.
    VDS.Theme:Register(label, "TextRowMeta")
    label:SetShadowColor(0, 0, 0, 0.85)  -- drop shadow keeps labels crisp on any wash
    label:SetShadowOffset(1, -1)
    col._label = label

    local chips = {}
    for i = 1, numChips do
        local chip = buildChip(col)
        chip:SetPoint("TOPLEFT", col, "TOPLEFT",
            (i - 1) * (CHIP_SIZE + CHIP_GAP),
            -(LABEL_H + CHIP_GAP))
        chip:Hide()
        chips[i] = chip
    end
    col._chips = chips

    return col
end

-- ============================================================================
-- Paint one group's chips from its picks (DRY: every group + both rows).
-- ============================================================================
local function paintGroupChips(col, picks, anchorID, rel)
    for j = 1, #col._chips do
        local chip = col._chips[j]
        local pick = picks[j]
        if pick then
            -- Resolve swatch colours at paint time via DyeOwnershipObserver
            -- (invariant §1: selectors don't call Blizzard APIs).
            local info = VDS.DyeOwnershipObserver:ResolveInfo(pick.dyeID)
            if info and info.swatchStart and info.swatchEnd then  -- exception(nullable): catalog lookup can miss pre-CATALOG_READY
                local cs, ce = info.swatchStart, info.swatchEnd
                chip._swBase:SetVertexColor(cs.r, cs.g, cs.b, 1)
                chip._swGrad:SetVertexColor(ce.r, ce.g, ce.b, 1)
            else
                chip._swBase:SetVertexColor(0.35, 0.35, 0.38, 1)
                chip._swGrad:SetVertexColor(0.35, 0.35, 0.38, 1)
            end
            chip:SetAlpha(1.0)  -- comparison tool: never dim, owned or not
            chip._dyeID           = pick.dyeID
            chip._harmonyRel      = rel
            chip._harmonyPicks    = picks   -- whole group, so the bar shows anchor + all suggestions
            chip._harmonyAnchorID = anchorID
            chip:Show()
        else
            chip:Hide()
            chip._dyeID = nil
        end
    end
end

-- ============================================================================
-- _paintWash(widget, anchorID)
-- Tints the band's watercolor background wash (vds_swatch_tex + gloss gradient,
-- same recipe as the dye rows) to the anchor dye's swatchStart at full tint.
-- Returns the wash lightness (OKLab L) so label colours can adapt for contrast.
-- ============================================================================
local function _paintWash(widget, anchorID)
    local info = VDS.DyeOwnershipObserver:ResolveInfo(anchorID)
    if info and info.swatchStart and info.swatchEnd then  -- exception(nullable): catalog miss pre-CATALOG_READY; wash stays hidden
        local cs, ce = info.swatchStart, info.swatchEnd
        -- Two-tone gradient (the dye's own swatchStart -> swatchEnd) instead of a
        -- flat tint: gives the panel depth and shows the selected dye's range.
        -- Crosses the vertical gloss for a richer, dimensional watercolour pool.
        widget._wash:SetGradient("HORIZONTAL",
            _G.CreateColor(cs.r, cs.g, cs.b, 1), _G.CreateColor(ce.r, ce.g, ce.b, 1))
        widget._wash:Show()
        widget._washGloss:Show()
        return VDS.ColorMath.L(cs.r, cs.g, cs.b)
    end
    widget._wash:Hide()
    widget._washGloss:Hide()
    return 0  -- dark default -> light labels
end

-- ============================================================================
-- _labelColor -- group labels stay near-white on every wash. The dark group
-- card (+ drop shadow) keeps them legible even on pale-dye washes, so we don't
-- flip to dark text on light anchors (reads fine white -- user call).
-- ============================================================================
local function _labelColor()
    return 0.95, 0.94, 0.92
end

-- ============================================================================
-- dispatchHarmonyBand  (called every time binding value changes)
-- Two-row grid: the populated groups (the chromatic set XOR the neutral set)
-- split across two rows so the band stays narrow. Triad is a permanent group.
-- ============================================================================
local function dispatchHarmonyBand(widget, values)
    local data = values.data
    if not data then
        widget._wash:Hide()
        widget._washGloss:Hide()
        for _, grp in ipairs(widget._groups) do grp.col:Hide() end
        return
    end
    local anchorID = data.anchorID

    -- Tint the background wash to the anchor colour. Labels stay white; the
    -- dark group card keeps them legible on pale washes.
    _paintWash(widget, anchorID)
    local lr, lg, lb = _labelColor()

    -- Collect the groups that have picks; hide the inactive lens's groups.
    local shown = {}
    for _, grp in ipairs(widget._groups) do
        local picks = data[grp.picks] or {}
        if #picks > 0 then
            shown[#shown + 1] = { grp = grp, picks = picks }
        else
            grp.col:Hide()
        end
    end

    -- Lay the shown groups across two rows (top row gets the first half).
    local perRow = math.max(1, math.ceil(#shown / 2))
    local rowH   = LABEL_H + CHIP_GAP + CHIP_SIZE + ROW_GAP
    local rowX   = { 0, 0 }
    for idx, item in ipairs(shown) do
        local row = (idx > perRow) and 2 or 1
        local col = item.grp.col
        col:ClearAllPoints()
        col:SetPoint("TOPLEFT", widget, "TOPLEFT", rowX[row], -(row - 1) * rowH)
        col:Show()
        paintGroupChips(col, item.picks, anchorID, item.grp.rel)
        -- Column width = max(chips, label) so a wide label never overlaps its neighbour.
        local chipsW   = #item.picks * CHIP_SIZE + math.max(0, #item.picks - 1) * CHIP_GAP
        local visibleW = math.max(chipsW, col._label:GetUnboundedStringWidth(), 1)
        col:SetWidth(visibleW)
        rowX[row] = rowX[row] + visibleW + GROUP_GAP
        -- Drive label colour from wash lightness for legibility.
        col._label:SetTextColor(lr, lg, lb, 1)
    end
end

-- ============================================================================
-- Widget type registration
-- ============================================================================

Types:Register("harmonyBand", {
    specFields = {},
    build = function(parent, _spec)
        local f = CreateFrame("Frame", nil, parent)
        -- Height set by layout engine via spec.height.

        -- Watercolor wash: same two-texture treatment as dye rows.
        -- _wash: vds_swatch_tex (ARTWORK sublayer 0) tinted to anchor swatchStart.
        -- _washGloss: WHITE (ARTWORK sublayer 1) ADD-blended vertical gradient
        --   (transparent -> white 0.15) for the row-swatch gloss highlight.
        -- Both hidden until first dispatchHarmonyBand with live data.
        -- Wash fills the ENTIRE detail panel, not just the band's fill-slot, so
        -- the colour covers the swatch column + section padding + footer height.
        -- `parent` IS the detailPanel frame (detail.row is a chrome-less virtual
        -- section, so its widgets parent straight to the panel -- VDS_Layout.lua).
        -- BACKGROUND layer keeps it behind the swatch + chips (ARTWORK / child
        -- frames). It lives on the panel (not `f`), so the band's visible gate no
        -- longer hides it -- harmony.band.data returns nil outside Colors and the
        -- dispatch hides it (see the selector gate).
        local wash = parent:CreateTexture(nil, "BACKGROUND", nil, 1)
        wash:SetAllPoints(parent)
        wash:SetTexture(DYE_SWATCH_TEX)
        wash:Hide()
        f._wash = wash

        local washGloss = parent:CreateTexture(nil, "BACKGROUND", nil, 2)
        washGloss:SetAllPoints(parent)
        washGloss:SetTexture(WHITE)
        washGloss:SetBlendMode("ADD")
        washGloss:SetGradient("VERTICAL",
            _G.CreateColor(1, 1, 1, 0), _G.CreateColor(1, 1, 1, 0.15))
        washGloss:Hide()
        f._washGloss = washGloss

        -- Build permanent group columns.
        f._groups  = {}
        for _, grp in ipairs(BAND_GROUPS) do
            local col = buildGroupColumn(f, grp.label, grp.count)
            col:Hide()
            f._groups[#f._groups + 1] = { col = col, rel = grp.rel, picks = grp.picks }
        end

        return f
    end,
    dispatch = {
        fields = { "data" },
        push   = function(widget, values)
            dispatchHarmonyBand(widget, values)
        end,
    },
})

-- ============================================================================
-- gradientSwatch widget kind
-- ============================================================================
-- The large anchor swatch in the detail panel footer. Renders using the same
-- three-atlas chip chrome as harmony chips (DYE_SWATCH_BASE + DYE_SWATCH_GRADIENT
-- + DYE_FRAME_DEFAULT) so the detail swatch is visually consistent with the
-- harmony band chips it sits beside.
-- Binding: { dye = "detail.dye" } -- { dyeColorID = N, name = ... }
-- Size: 44x44 (set by LayoutConfig spec.width / spec.height).

local function dispatchGradientSwatch(widget, values)
    local dye = values.dye
    if not dye then
        -- No selection: grey out like an empty chip.
        widget._swBase:SetVertexColor(0.20, 0.20, 0.22, 1)
        widget._swGrad:SetVertexColor(0.20, 0.20, 0.22, 1)
        return
    end
    local info = VDS.DyeOwnershipObserver:ResolveInfo(dye.dyeColorID)
    if info and info.swatchStart and info.swatchEnd then  -- exception(nullable): catalog miss pre-CATALOG_READY
        local cs, ce = info.swatchStart, info.swatchEnd
        widget._swBase:SetVertexColor(cs.r, cs.g, cs.b, 1)
        widget._swGrad:SetVertexColor(ce.r, ce.g, ce.b, 1)
    else
        widget._swBase:SetVertexColor(0.35, 0.35, 0.38, 1)
        widget._swGrad:SetVertexColor(0.35, 0.35, 0.38, 1)
    end
end

Types:Register("gradientSwatch", {
    specFields = {},
    build = function(parent, _spec)
        local f = CreateFrame("Frame", nil, parent)
        -- Size driven by spec.width / spec.height in LayoutConfig (44x44).
        -- Chrome is built at build time; textures are SetPoint("CENTER") so
        -- they re-center correctly if the layout engine resizes f post-build.
        -- The size is overwritten after atlas load below to lock the texels.
        buildChipChrome(f, 44)
        return f
    end,
    dispatch = {
        fields = { "dye" },
        push   = function(widget, values)
            dispatchGradientSwatch(widget, values)
        end,
    },
})
