local RUI = LibStub("RUI-1.0")
if not RUI then return end

-- RUI.OpenColorPicker(color, hasAlpha, onAccept, onCancel, onChange, defaultColor)
--   color       = {r,g,b,a}  (0-1 each)
--   onAccept    = function({r,g,b,a})  -- called when user clicks Okay
--   onCancel    = function()           -- called on Cancel / Escape
--   onChange    = function({r,g,b,a}) -- called on every live drag update
--   defaultColor = {r,g,b,a} or nil  -- shows "Default" button when set
-- Singleton frame: created once on first call, reused thereafter.

local MEDIA = function(file)
    return "Interface\\AddOns\\" .. RUI._addonName .. "\\libs\\RUI\\Media\\Icons\\" .. file
end

-- ─── Color conversion ─────────────────────────────────────────────────────────

local function HSVtoRGB(h, s, v)
    if s == 0 then return v, v, v end
    h = h / 60
    local i = math.floor(h)
    local f = h - i
    local p = v * (1 - s)
    local q = v * (1 - s * f)
    local t = v * (1 - s * (1 - f))
    i = i % 6
    if     i == 0 then return v, t, p
    elseif i == 1 then return q, v, p
    elseif i == 2 then return p, v, t
    elseif i == 3 then return p, q, v
    elseif i == 4 then return t, p, v
    else               return v, p, q end
end

local function RGBtoHSV(r, g, b)
    local mx = math.max(r, g, b)
    local mn = math.min(r, g, b)
    local h, s, v = 0, 0, mx
    local d = mx - mn
    if mx ~= 0 then s = d / mx end
    if mx ~= mn then
        if     mx == r then h = (g - b) / d; if g < b then h = h + 6 end
        elseif mx == g then h = (b - r) / d + 2
        else                 h = (r - g) / d + 4 end
        h = h * 60
    end
    return h, s, v
end

local function RGBtoHex(r, g, b, a)
    if a then
        return string.format("#%02X%02X%02X%02X",
            math.floor(r*255+.5), math.floor(g*255+.5),
            math.floor(b*255+.5), math.floor(a*255+.5))
    end
    return string.format("#%02X%02X%02X",
        math.floor(r*255+.5), math.floor(g*255+.5), math.floor(b*255+.5))
end

local function HexToRGB(hex)
    hex = hex:gsub("#", "")
    if #hex == 8 then
        return (tonumber(hex:sub(1,2),16) or 255)/255,
               (tonumber(hex:sub(3,4),16) or 255)/255,
               (tonumber(hex:sub(5,6),16) or 255)/255,
               (tonumber(hex:sub(7,8),16) or 255)/255
    elseif #hex == 6 then
        return (tonumber(hex:sub(1,2),16) or 255)/255,
               (tonumber(hex:sub(3,4),16) or 255)/255,
               (tonumber(hex:sub(5,6),16) or 255)/255
    end
    return 1, 1, 1, nil
end

-- ─── Session-only saved / recent storage ─────────────────────────────────────

local savedColors  = {}
local recentColors = {}
local MAX_RECENT   = 27   -- 3 rows of 9
local MAX_SAVED    = 27

local SWATCH_W       = 28
local SWATCH_H       = 28
local SWATCH_GAP     = 2
local SWATCHES_PER_ROW = 9

-- ─── Layout constants ─────────────────────────────────────────────────────────

local SQ_SZ      = 155   -- sat/val square side
local HUE_W      = 20    -- hue bar width
local ALPHA_W    = 20    -- alpha bar width
local DLG_W      = 300
local DLG_H      = 440
local HDR_H      = 28
local FTR_H      = 40
local PAD        = 10

-- ─── Singleton picker state ───────────────────────────────────────────────────

local pickerFrame = nil
local curH, curS, curV, curA = 0, 1, 1, 1
local curHasAlpha  = false
local isUpdating   = false
local skipChange   = false
local activeTab    = "saved"

local onAcceptCb, onCancelCb, onChangeCb
local pickerDefaultColor

-- Widget refs populated by BuildColorPicker:
local hueLayer, blackLayer, squareThumb
local hueBar, hueIndicator, hueIndicatorBorder
local alphaBar, alphaGradient, alphaIndicator, alphaIndicatorBorder
local previewTexture
local rIn, gIn, bIn, aIn, aInParent, hexIn
local savedContent, recentContent, classContent
local defaultBtn, swatchPools

-- ─── Update functions ─────────────────────────────────────────────────────────

local function UpdateHueGradient()
    local r, g, b = HSVtoRGB(curH, 1, 1)
    hueLayer:SetGradient("HORIZONTAL", CreateColor(1,1,1,1), CreateColor(r,g,b,1))
end

local function UpdateAlphaGradient()
    local r, g, b = HSVtoRGB(curH, curS, curV)
    alphaGradient:SetGradient("VERTICAL", CreateColor(r,g,b,0), CreateColor(r,g,b,1))
end

local function UpdateSquareThumb()
    squareThumb:ClearAllPoints()
    squareThumb:SetPoint("CENTER", squareThumb:GetParent(), "BOTTOMLEFT",
                         curS * SQ_SZ, curV * SQ_SZ)
end

local function UpdateHueIndicator()
    local y = (curH / 360) * SQ_SZ
    hueIndicator:ClearAllPoints()
    hueIndicator:SetPoint("CENTER", hueBar, "TOP", 0, -y)
    hueIndicatorBorder:ClearAllPoints()
    hueIndicatorBorder:SetPoint("CENTER", hueIndicator)
end

local function UpdateAlphaIndicator()
    local y = (1 - curA) * SQ_SZ
    alphaIndicator:ClearAllPoints()
    alphaIndicator:SetPoint("CENTER", alphaBar, "TOP", 0, -y)
    alphaIndicatorBorder:ClearAllPoints()
    alphaIndicatorBorder:SetPoint("CENTER", alphaIndicator)
end

local function UpdateInputs()
    if isUpdating then return end
    isUpdating = true
    local r, g, b = HSVtoRGB(curH, curS, curV)
    rIn:SetText(math.floor(r*255+.5))
    gIn:SetText(math.floor(g*255+.5))
    bIn:SetText(math.floor(b*255+.5))
    aIn:SetText(math.floor(curA*100+.5))
    hexIn:SetText(curHasAlpha and RGBtoHex(r,g,b,curA) or RGBtoHex(r,g,b))
    isUpdating = false
end

local function UpdateAll()
    local r, g, b = HSVtoRGB(curH, curS, curV)
    previewTexture:SetColorTexture(r, g, b, curA)
    UpdateHueGradient()
    UpdateAlphaGradient()
    UpdateSquareThumb()
    UpdateHueIndicator()
    UpdateAlphaIndicator()
    UpdateInputs()
    if onChangeCb and not skipChange then
        onChangeCb({ r=r, g=g, b=b, a=curHasAlpha and curA or 1 })
    end
end

-- ─── Swatch management ────────────────────────────────────────────────────────

local function RefreshSwatches(parent, colors)
    if not swatchPools then return end
    local pool = swatchPools[parent]
    if not pool then pool = {}; swatchPools[parent] = pool end

    for _, btn in ipairs(pool) do btn:Hide() end

    if #colors == 0 then
        if parent._emptyLabel then parent._emptyLabel:Show() end
        return
    end
    if parent._emptyLabel then parent._emptyLabel:Hide() end

    for i, c in ipairs(colors) do
        local btn = pool[i]
        if not btn then
            btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
            btn:SetSize(SWATCH_W, SWATCH_H)
            btn:SetBackdrop({
                bgFile   = "Interface\\Buttons\\WHITE8x8",
                edgeFile = "Interface\\Buttons\\WHITE8x8",
                edgeSize = 1,
            })
            btn:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
            btn:SetScript("OnEnter", function(self)
                self:SetBackdropBorderColor(1, 1, 1, 1)
            end)
            btn:SetScript("OnLeave", function(self)
                self:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
            end)
            pool[i] = btn
        end
        local row = math.floor((i-1) / SWATCHES_PER_ROW)
        local col = (i-1) % SWATCHES_PER_ROW
        btn:ClearAllPoints()
        btn:SetPoint("TOPLEFT", col*(SWATCH_W+SWATCH_GAP), -row*(SWATCH_H+SWATCH_GAP))
        btn:SetBackdropColor(c.r, c.g, c.b, c.a or 1)
        local cr, cg, cb, ca = c.r, c.g, c.b, c.a or 1
        btn:SetScript("OnClick", function()
            curH, curS, curV = RGBtoHSV(cr, cg, cb)
            curA = ca
            UpdateAll()
        end)
        btn:Show()
    end
end

local function AddToRecent(r, g, b, a)
    a = a or 1
    local key = string.format("%.2f,%.2f,%.2f,%.2f", r, g, b, a)
    for i, c in ipairs(recentColors) do
        if string.format("%.2f,%.2f,%.2f,%.2f", c.r, c.g, c.b, c.a or 1) == key then
            table.remove(recentColors, i); break
        end
    end
    table.insert(recentColors, 1, {r=r, g=g, b=b, a=a})
    if #recentColors > MAX_RECENT then table.remove(recentColors) end
end

-- ─── Class colors ─────────────────────────────────────────────────────────────

local CLASS_COLORS = {
    {name="Warrior",      r=0.78, g=0.61, b=0.43},
    {name="Paladin",      r=0.96, g=0.55, b=0.73},
    {name="Hunter",       r=0.67, g=0.83, b=0.45},
    {name="Rogue",        r=1.00, g=0.96, b=0.41},
    {name="Priest",       r=1.00, g=1.00, b=1.00},
    {name="Death Knight", r=0.77, g=0.12, b=0.23},
    {name="Shaman",       r=0.00, g=0.44, b=0.87},
    {name="Mage",         r=0.41, g=0.80, b=0.94},
    {name="Warlock",      r=0.58, g=0.51, b=0.79},
    {name="Monk",         r=0.00, g=1.00, b=0.59},
    {name="Druid",        r=1.00, g=0.49, b=0.04},
    {name="Demon Hunter", r=0.64, g=0.19, b=0.79},
    {name="Evoker",       r=0.20, g=0.58, b=0.50},
}

-- ─── Build ────────────────────────────────────────────────────────────────────

local function BuildColorPicker()
    swatchPools = {}

    local f = CreateFrame("Frame", "RUIColorPicker", UIParent, "BackdropTemplate")
    f:SetSize(DLG_W, DLG_H)
    f:SetPoint("CENTER")
    f:SetMovable(true)
    f:EnableMouse(true)
    f:SetClampedToScreen(true)
    f:SetFrameStrata("FULLSCREEN_DIALOG")
    f:SetFrameLevel(500)
    f:SetToplevel(true)
    f:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    })
    f:SetBackdropColor(RUI.C.BG.r, RUI.C.BG.g, RUI.C.BG.b, 1)
    f:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
    f:Hide()

    tinsert(UISpecialFrames, "RUIColorPicker")

    f:SetScript("OnHide", function()
        if not f._applied then
            if onCancelCb then onCancelCb() end
        end
        f._applied = false
        onAcceptCb = nil; onCancelCb = nil; onChangeCb = nil
    end)

    -- ── Header ──────────────────────────────────────────────────────────────────
    local hdr = CreateFrame("Frame", nil, f)
    hdr:SetHeight(HDR_H)
    hdr:SetPoint("TOPLEFT", 0, 0)
    hdr:SetPoint("TOPRIGHT", 0, 0)
    hdr:EnableMouse(true)
    hdr:RegisterForDrag("LeftButton")
    hdr:SetScript("OnDragStart", function() f:StartMoving() end)
    hdr:SetScript("OnDragStop",  function() f:StopMovingOrSizing() end)

    local hdrBg = hdr:CreateTexture(nil, "BACKGROUND")
    hdrBg:SetAllPoints()
    hdrBg:SetColorTexture(RUI.C.PANEL.r, RUI.C.PANEL.g, RUI.C.PANEL.b, 1)

    local hdrSep = hdr:CreateTexture(nil, "BORDER")
    hdrSep:SetHeight(1)
    hdrSep:SetPoint("BOTTOMLEFT",  hdr, "BOTTOMLEFT",  0, 0)
    hdrSep:SetPoint("BOTTOMRIGHT", hdr, "BOTTOMRIGHT", 0, 0)
    hdrSep:SetColorTexture(RUI.C.ACCENT.r, RUI.C.ACCENT.g, RUI.C.ACCENT.b, 1)

    local titleFs = hdr:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    titleFs:SetPoint("LEFT", 10, 0)
    titleFs:SetText("Color Picker")
    titleFs:SetTextColor(RUI.C.ACCENT.r, RUI.C.ACCENT.g, RUI.C.ACCENT.b)

    local closeBtn = RUI.Widget.CloseButton(hdr, function() f:Hide() end)
    closeBtn:SetPoint("RIGHT", hdr, "RIGHT", -4, 0)

    -- ── Footer ──────────────────────────────────────────────────────────────────
    local ftr = CreateFrame("Frame", nil, f, "BackdropTemplate")
    ftr:SetHeight(FTR_H)
    ftr:SetPoint("BOTTOMLEFT",  f, "BOTTOMLEFT",  0, 0)
    ftr:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
    ftr:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8" })
    ftr:SetBackdropColor(RUI.C.ELEMENT.r, RUI.C.ELEMENT.g, RUI.C.ELEMENT.b, 1)

    local okBtn = CreateFrame("Button", nil, ftr, "BackdropTemplate")
    okBtn:SetSize(80, 26)
    okBtn:SetPoint("RIGHT", ftr, "CENTER", -5, 0)
    okBtn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    })
    okBtn:SetBackdropColor(RUI.C.ACCENT.r, RUI.C.ACCENT.g, RUI.C.ACCENT.b, 1)
    okBtn:SetBackdropBorderColor(RUI.C.ACCENT.r, RUI.C.ACCENT.g, RUI.C.ACCENT.b, 1)
    local okText = okBtn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    okText:SetPoint("CENTER")
    okText:SetText("Okay")
    okText:SetTextColor(1, 1, 1)
    okBtn:SetScript("OnEnter", function(self)
        self:SetBackdropColor(RUI.C.ACCENT.r*1.15, RUI.C.ACCENT.g*1.15, RUI.C.ACCENT.b*1.15, 1)
    end)
    okBtn:SetScript("OnLeave", function(self)
        self:SetBackdropColor(RUI.C.ACCENT.r, RUI.C.ACCENT.g, RUI.C.ACCENT.b, 1)
    end)
    okBtn:SetScript("OnClick", function()
        local r, g, b = HSVtoRGB(curH, curS, curV)
        AddToRecent(r, g, b, curHasAlpha and curA or 1)
        f._applied = true
        if onAcceptCb then
            onAcceptCb({ r=r, g=g, b=b, a=curHasAlpha and curA or 1 })
        end
        onAcceptCb = nil; onCancelCb = nil; onChangeCb = nil
        f:Hide()
    end)

    local cancelBtn = CreateFrame("Button", nil, ftr, "BackdropTemplate")
    cancelBtn:SetSize(80, 26)
    cancelBtn:SetPoint("LEFT", ftr, "CENTER", 5, 0)
    cancelBtn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    })
    cancelBtn:SetBackdropColor(RUI.C.BG.r, RUI.C.BG.g, RUI.C.BG.b, 1)
    cancelBtn:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
    local cancelText = cancelBtn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    cancelText:SetPoint("CENTER")
    cancelText:SetText("Cancel")
    cancelText:SetTextColor(RUI.C.DIM.r, RUI.C.DIM.g, RUI.C.DIM.b)
    cancelBtn:SetScript("OnEnter", function(self)
        self:SetBackdropBorderColor(0.8, 0.4, 0.4, 1)
    end)
    cancelBtn:SetScript("OnLeave", function(self)
        self:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
    end)
    cancelBtn:SetScript("OnClick", function() f:Hide() end)

    -- ── Content area ────────────────────────────────────────────────────────────
    local content = CreateFrame("Frame", nil, f)
    content:SetPoint("TOPLEFT",     f, "TOPLEFT",     PAD,  -HDR_H)
    content:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -PAD,  FTR_H)

    -- ── Sat/Val square ──────────────────────────────────────────────────────────
    local sqFrame = CreateFrame("Frame", nil, content, "BackdropTemplate")
    sqFrame:SetSize(SQ_SZ, SQ_SZ)
    sqFrame:SetPoint("TOPLEFT", 0, 0)
    sqFrame:SetBackdrop({ edgeFile="Interface\\Buttons\\WHITE8x8", edgeSize=1 })
    sqFrame:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)

    hueLayer = sqFrame:CreateTexture(nil, "BACKGROUND")
    hueLayer:SetAllPoints()
    hueLayer:SetColorTexture(1, 1, 1, 1)

    blackLayer = sqFrame:CreateTexture(nil, "ARTWORK")
    blackLayer:SetAllPoints()
    blackLayer:SetColorTexture(1, 1, 1, 1)
    -- VERTICAL: bottom=black-opaque, top=black-transparent → top is bright, bottom is dark
    blackLayer:SetGradient("VERTICAL", CreateColor(0,0,0,1), CreateColor(0,0,0,0))

    squareThumb = sqFrame:CreateTexture(nil, "OVERLAY")
    squareThumb:SetSize(14, 14)
    squareThumb:SetTexture("Interface\\Buttons\\UI-ColorPicker-Buttons")
    squareThumb:SetTexCoord(0, 0.15625, 0, 0.625)

    -- ── Hue bar ─────────────────────────────────────────────────────────────────
    hueBar = CreateFrame("Frame", nil, content, "BackdropTemplate")
    hueBar:SetSize(HUE_W, SQ_SZ)
    hueBar:SetPoint("TOPLEFT", sqFrame, "TOPRIGHT", 8, 0)
    hueBar:SetBackdrop({ edgeFile="Interface\\Buttons\\WHITE8x8", edgeSize=1 })
    hueBar:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)

    local hueColors = {{1,0,0},{1,1,0},{0,1,0},{0,1,1},{0,0,1},{1,0,1},{1,0,0}}
    for i = 1, 6 do
        local seg = hueBar:CreateTexture(nil, "BACKGROUND")
        seg:SetSize(HUE_W, SQ_SZ / 6)
        seg:SetPoint("TOPLEFT", 0, -((i-1) * SQ_SZ / 6))
        local lo, hi = hueColors[i+1], hueColors[i]
        seg:SetGradient("VERTICAL", CreateColor(lo[1],lo[2],lo[3],1), CreateColor(hi[1],hi[2],hi[3],1))
    end

    hueIndicator = hueBar:CreateTexture(nil, "OVERLAY", nil, 2)
    hueIndicator:SetSize(HUE_W + 4, 6)
    hueIndicator:SetColorTexture(1, 1, 1, 1)

    hueIndicatorBorder = hueBar:CreateTexture(nil, "OVERLAY", nil, 1)
    hueIndicatorBorder:SetSize(HUE_W + 6, 8)
    hueIndicatorBorder:SetColorTexture(0, 0, 0, 1)

    -- ── Alpha bar ────────────────────────────────────────────────────────────────
    alphaBar = CreateFrame("Frame", nil, content, "BackdropTemplate")
    alphaBar:SetSize(ALPHA_W, SQ_SZ)
    alphaBar:SetPoint("TOPLEFT", hueBar, "TOPRIGHT", 8, 0)
    alphaBar:SetBackdrop({ edgeFile="Interface\\Buttons\\WHITE8x8", edgeSize=1 })
    alphaBar:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)

    local cSz = 8
    local cW  = ALPHA_W - 2
    local cH  = SQ_SZ  - 2
    for row = 0, math.ceil(cH/cSz)-1 do
        for col = 0, math.ceil(cW/cSz)-1 do
            local ch = alphaBar:CreateTexture(nil, "BACKGROUND")
            ch:SetSize(math.min(cSz, cW-col*cSz), math.min(cSz, cH-row*cSz))
            ch:SetPoint("TOPLEFT", 1+col*cSz, -1-row*cSz)
            local lite = (row+col)%2 == 0
            ch:SetColorTexture(lite and 0.4 or 0.2, lite and 0.4 or 0.2, lite and 0.4 or 0.2, 1)
        end
    end

    alphaGradient = alphaBar:CreateTexture(nil, "ARTWORK")
    alphaGradient:SetPoint("TOPLEFT", 1, -1)
    alphaGradient:SetPoint("BOTTOMRIGHT", -1, 1)
    alphaGradient:SetColorTexture(1, 1, 1, 1)

    alphaIndicator = alphaBar:CreateTexture(nil, "OVERLAY", nil, 2)
    alphaIndicator:SetSize(ALPHA_W + 4, 6)
    alphaIndicator:SetColorTexture(1, 1, 1, 1)

    alphaIndicatorBorder = alphaBar:CreateTexture(nil, "OVERLAY", nil, 1)
    alphaIndicatorBorder:SetSize(ALPHA_W + 6, 8)
    alphaIndicatorBorder:SetColorTexture(0, 0, 0, 1)

    -- ── Preview swatch ───────────────────────────────────────────────────────────
    -- squareFrame right edge: SQ_SZ; hue right: SQ_SZ+8+HUE_W; alpha right: +8+ALPHA_W
    -- preview starts 8px past alpha right: SQ_SZ+8+HUE_W+8+ALPHA_W+8 = 207
    local prevX = SQ_SZ + 8 + HUE_W + 8 + ALPHA_W + 8
    local previewFrame = CreateFrame("Frame", nil, content, "BackdropTemplate")
    previewFrame:SetSize(55, 55)
    previewFrame:SetPoint("TOPLEFT", content, "TOPLEFT", prevX, 0)
    previewFrame:SetBackdrop({ edgeFile="Interface\\Buttons\\WHITE8x8", edgeSize=1 })
    previewFrame:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)

    local pW, pH = 53, 53
    for row = 0, math.ceil(pH/8)-1 do
        for col = 0, math.ceil(pW/8)-1 do
            local ch = previewFrame:CreateTexture(nil, "BACKGROUND")
            ch:SetSize(math.min(8,pW-col*8), math.min(8,pH-row*8))
            ch:SetPoint("TOPLEFT", 1+col*8, -1-row*8)
            local lite = (row+col)%2 == 0
            ch:SetColorTexture(lite and 0.4 or 0.2, lite and 0.4 or 0.2, lite and 0.4 or 0.2, 1)
        end
    end

    previewTexture = previewFrame:CreateTexture(nil, "ARTWORK")
    previewTexture:SetPoint("TOPLEFT", 1, -1)
    previewTexture:SetPoint("BOTTOMRIGHT", -1, 1)

    -- ── RGBA inputs ──────────────────────────────────────────────────────────────
    local inputRow = CreateFrame("Frame", nil, content)
    inputRow:SetSize(DLG_W - 2*PAD, 24)
    inputRow:SetPoint("TOPLEFT", sqFrame, "BOTTOMLEFT", 0, -8)

    local function MakeRGBAInput(parent, lbl, lc, xOff, w)
        local box = CreateFrame("Frame", nil, parent, "BackdropTemplate")
        box:SetSize(w, 22)
        box:SetPoint("LEFT", xOff, 0)
        box:SetBackdrop({ bgFile="Interface\\Buttons\\WHITE8x8" })
        box:SetBackdropColor(RUI.C.ELEMENT.r, RUI.C.ELEMENT.g, RUI.C.ELEMENT.b, 1)

        local ls = box:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        ls:SetPoint("LEFT", 4, 0)
        ls:SetText(lbl)
        ls:SetTextColor(lc.r, lc.g, lc.b)

        local eb = CreateFrame("EditBox", nil, box)
        eb:SetSize(w - 22, 18)
        eb:SetPoint("LEFT", ls, "RIGHT", 2, 0)
        eb:SetFontObject("GameFontHighlightSmall")
        eb:SetTextColor(RUI.C.TEXT.r, RUI.C.TEXT.g, RUI.C.TEXT.b)
        eb:SetAutoFocus(false)
        eb:SetNumeric(true)
        eb:SetMaxLetters(3)
        eb:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
        return eb, box
    end

    rIn = MakeRGBAInput(inputRow, "R",  {r=1,   g=0.4, b=0.4}, 0,   60)
    gIn = MakeRGBAInput(inputRow, "G",  {r=0.4, g=1,   b=0.4}, 64,  60)
    bIn = MakeRGBAInput(inputRow, "B",  {r=0.4, g=0.6, b=1  }, 128, 60)
    local aEb, aBox = MakeRGBAInput(inputRow, "A%", {r=0.8, g=0.8, b=0.8}, 192, 60)
    aIn = aEb; aInParent = aBox

    -- ── Hex input + copy button ───────────────────────────────────────────────────
    local hexFrame = CreateFrame("Frame", nil, content, "BackdropTemplate")
    hexFrame:SetSize(148, 22)
    hexFrame:SetPoint("TOPLEFT", inputRow, "BOTTOMLEFT", 0, -4)
    hexFrame:SetBackdrop({ bgFile="Interface\\Buttons\\WHITE8x8" })
    hexFrame:SetBackdropColor(RUI.C.ELEMENT.r, RUI.C.ELEMENT.g, RUI.C.ELEMENT.b, 1)

    local hexLabel = hexFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    hexLabel:SetPoint("LEFT", 4, 0)
    hexLabel:SetText("Hex")
    hexLabel:SetTextColor(RUI.C.DIM.r, RUI.C.DIM.g, RUI.C.DIM.b)

    hexIn = CreateFrame("EditBox", nil, hexFrame)
    hexIn:SetSize(98, 18)
    hexIn:SetPoint("LEFT", hexLabel, "RIGHT", 4, 0)
    hexIn:SetFontObject("GameFontHighlightSmall")
    hexIn:SetTextColor(RUI.C.TEXT.r, RUI.C.TEXT.g, RUI.C.TEXT.b)
    hexIn:SetAutoFocus(false)
    hexIn:SetMaxLetters(9)
    hexIn:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)

    local copyBtn = CreateFrame("Button", nil, hexFrame, "BackdropTemplate")
    copyBtn:SetSize(18, 18)
    copyBtn:SetPoint("LEFT", hexIn, "RIGHT", 2, 0)
    copyBtn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    })
    copyBtn:SetBackdropColor(RUI.C.ELEMENT.r*0.8, RUI.C.ELEMENT.g*0.8, RUI.C.ELEMENT.b*0.8, 1)
    copyBtn:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
    local copyIcon = copyBtn:CreateTexture(nil, "OVERLAY")
    copyIcon:SetPoint("CENTER")
    copyIcon:SetSize(12, 12)
    copyIcon:SetTexture(MEDIA("content_copy"))
    copyIcon:SetVertexColor(RUI.C.TEXT.r, RUI.C.TEXT.g, RUI.C.TEXT.b)
    copyBtn:SetScript("OnEnter", function(self)
        self:SetBackdropBorderColor(RUI.C.ACCENT.r, RUI.C.ACCENT.g, RUI.C.ACCENT.b, 1)
        copyIcon:SetVertexColor(RUI.C.ACCENT.r, RUI.C.ACCENT.g, RUI.C.ACCENT.b)
    end)
    copyBtn:SetScript("OnLeave", function(self)
        self:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
        copyIcon:SetVertexColor(RUI.C.TEXT.r, RUI.C.TEXT.g, RUI.C.TEXT.b)
    end)

    -- Copy popup (Ctrl+C helper)
    local copyPopup = CreateFrame("Frame", nil, f, "BackdropTemplate")
    copyPopup:SetSize(200, 70)
    copyPopup:SetPoint("CENTER", f, "CENTER")
    copyPopup:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    })
    copyPopup:SetBackdropColor(RUI.C.BG.r, RUI.C.BG.g, RUI.C.BG.b, 1)
    copyPopup:SetBackdropBorderColor(RUI.C.ACCENT.r, RUI.C.ACCENT.g, RUI.C.ACCENT.b, 1)
    copyPopup:SetFrameLevel(f:GetFrameLevel() + 10)
    copyPopup:Hide()

    local cpLabel = copyPopup:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    cpLabel:SetPoint("TOP", 0, -8)
    cpLabel:SetText("Press Ctrl+C to copy:")
    cpLabel:SetTextColor(RUI.C.TEXT.r, RUI.C.TEXT.g, RUI.C.TEXT.b)

    local cpEdit = CreateFrame("EditBox", nil, copyPopup, "BackdropTemplate")
    cpEdit:SetSize(180, 22)
    cpEdit:SetPoint("TOP", cpLabel, "BOTTOM", 0, -6)
    cpEdit:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    })
    cpEdit:SetBackdropColor(RUI.C.ELEMENT.r, RUI.C.ELEMENT.g, RUI.C.ELEMENT.b, 1)
    cpEdit:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
    cpEdit:SetFontObject("GameFontHighlightSmall")
    cpEdit:SetTextColor(1, 1, 1)
    cpEdit:SetAutoFocus(false)
    cpEdit:SetJustifyH("CENTER")
    cpEdit:SetScript("OnEscapePressed", function() copyPopup:Hide() end)
    cpEdit:SetScript("OnEnterPressed", function() copyPopup:Hide() end)

    copyBtn:SetScript("OnClick", function()
        local hex = hexIn:GetText()
        if hex ~= "" then
            cpEdit:SetText(hex)
            copyPopup:Show()
            cpEdit:SetFocus()
            cpEdit:HighlightText()
        end
    end)

    -- ── Tabs ─────────────────────────────────────────────────────────────────────
    local tabFrame = CreateFrame("Frame", nil, content)
    tabFrame:SetSize(DLG_W - 2*PAD, 22)
    tabFrame:SetPoint("TOPLEFT", hexFrame, "BOTTOMLEFT", 0, -8)

    local tabBtns = {}

    local function SetActiveTab(name)
        activeTab = name
        for n, b in pairs(tabBtns) do
            if n == name then
                b.ul:Show()
                b.fs:SetTextColor(RUI.C.TEXT.r, RUI.C.TEXT.g, RUI.C.TEXT.b)
            else
                b.ul:Hide()
                b.fs:SetTextColor(RUI.C.DIM.r, RUI.C.DIM.g, RUI.C.DIM.b)
            end
        end
        classContent:SetShown(name == "class")
        savedContent:SetShown(name == "saved")
        recentContent:SetShown(name == "recent")
        if name == "recent" then RefreshSwatches(recentContent, recentColors) end
        if name == "saved"  then RefreshSwatches(savedContent,  savedColors)  end
    end

    local function MakeTab(name, label, xOff)
        local btn = CreateFrame("Button", nil, tabFrame)
        btn:SetSize(55, 20)
        btn:SetPoint("LEFT", xOff, 0)
        local fs = btn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        fs:SetPoint("CENTER")
        fs:SetText(label)
        btn.fs = fs
        local ul = btn:CreateTexture(nil, "OVERLAY")
        ul:SetHeight(2)
        ul:SetPoint("BOTTOMLEFT")
        ul:SetPoint("BOTTOMRIGHT")
        ul:SetColorTexture(RUI.C.ACCENT.r, RUI.C.ACCENT.g, RUI.C.ACCENT.b, 1)
        ul:Hide()
        btn.ul = ul
        btn:SetScript("OnClick", function() SetActiveTab(name) end)
        tabBtns[name] = btn
    end

    MakeTab("saved",  "Saved",  0)
    MakeTab("recent", "Recent", 60)
    MakeTab("class",  "Class",  120)

    -- Save button (top-right of tab row)
    local saveSwatchBtn = CreateFrame("Button", nil, tabFrame, "BackdropTemplate")
    saveSwatchBtn:SetSize(50, 18)
    saveSwatchBtn:SetPoint("TOPRIGHT", 0, 0)
    saveSwatchBtn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    })
    saveSwatchBtn:SetBackdropColor(RUI.C.ELEMENT.r, RUI.C.ELEMENT.g, RUI.C.ELEMENT.b, 1)
    saveSwatchBtn:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
    local saveSwatchText = saveSwatchBtn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    saveSwatchText:SetPoint("CENTER")
    saveSwatchText:SetText("Save")
    saveSwatchText:SetTextColor(RUI.C.TEXT.r, RUI.C.TEXT.g, RUI.C.TEXT.b)
    saveSwatchBtn:SetScript("OnEnter", function(self)
        self:SetBackdropBorderColor(RUI.C.ACCENT.r, RUI.C.ACCENT.g, RUI.C.ACCENT.b, 1)
    end)
    saveSwatchBtn:SetScript("OnLeave", function(self)
        self:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
    end)
    saveSwatchBtn:SetScript("OnClick", function()
        local r, g, b = HSVtoRGB(curH, curS, curV)
        local key = string.format("%.2f,%.2f,%.2f,%.2f", r, g, b, curA)
        for _, c in ipairs(savedColors) do
            if string.format("%.2f,%.2f,%.2f,%.2f", c.r, c.g, c.b, c.a or 1) == key then
                return
            end
        end
        if #savedColors >= MAX_SAVED then table.remove(savedColors) end
        table.insert(savedColors, 1, {r=r, g=g, b=b, a=curA})
        SetActiveTab("saved")
    end)

    -- Default button (below Save)
    defaultBtn = CreateFrame("Button", nil, tabFrame, "BackdropTemplate")
    defaultBtn:SetSize(50, 18)
    defaultBtn:SetPoint("TOP", saveSwatchBtn, "BOTTOM", 0, -2)
    defaultBtn:SetBackdrop({
        bgFile   = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Buttons\\WHITE8x8",
        edgeSize = 1,
    })
    defaultBtn:SetBackdropColor(RUI.C.ELEMENT.r, RUI.C.ELEMENT.g, RUI.C.ELEMENT.b, 1)
    defaultBtn:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
    local defBtnText = defaultBtn:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    defBtnText:SetPoint("CENTER")
    defBtnText:SetText("Default")
    defBtnText:SetTextColor(RUI.C.TEXT.r, RUI.C.TEXT.g, RUI.C.TEXT.b)
    defaultBtn:SetScript("OnEnter", function(self)
        self:SetBackdropBorderColor(RUI.C.ACCENT.r, RUI.C.ACCENT.g, RUI.C.ACCENT.b, 1)
    end)
    defaultBtn:SetScript("OnLeave", function(self)
        self:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
    end)
    defaultBtn:SetScript("OnClick", function()
        if not pickerDefaultColor then return end
        curH, curS, curV = RGBtoHSV(pickerDefaultColor.r, pickerDefaultColor.g, pickerDefaultColor.b)
        curA = pickerDefaultColor.a or 1
        UpdateAll()
    end)
    defaultBtn:Hide()

    -- Tab content area
    local tabContentH = 3*SWATCH_H + 2*SWATCH_GAP
    local tabContent = CreateFrame("Frame", nil, content)
    tabContent:SetSize(DLG_W - 2*PAD, tabContentH)
    tabContent:SetPoint("TOPLEFT", tabFrame, "BOTTOMLEFT", 0, -4)

    savedContent  = CreateFrame("Frame", nil, tabContent); savedContent:SetAllPoints()
    recentContent = CreateFrame("Frame", nil, tabContent); recentContent:SetAllPoints(); recentContent:Hide()
    classContent  = CreateFrame("Frame", nil, tabContent); classContent:SetAllPoints();  classContent:Hide()

    local noSaved = savedContent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    noSaved:SetPoint("CENTER")
    noSaved:SetText("No saved colors yet\nClick 'Save' to add current color")
    noSaved:SetTextColor(RUI.C.DIM.r, RUI.C.DIM.g, RUI.C.DIM.b)
    noSaved:SetJustifyH("CENTER")
    savedContent._emptyLabel = noSaved

    local noRecent = recentContent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    noRecent:SetPoint("CENTER")
    noRecent:SetText("No recent colors")
    noRecent:SetTextColor(RUI.C.DIM.r, RUI.C.DIM.g, RUI.C.DIM.b)
    noRecent:SetJustifyH("CENTER")
    recentContent._emptyLabel = noRecent

    -- Class swatches (static)
    for i, cl in ipairs(CLASS_COLORS) do
        local row = math.floor((i-1) / SWATCHES_PER_ROW)
        local col = (i-1) % SWATCHES_PER_ROW
        local btn = CreateFrame("Button", nil, classContent, "BackdropTemplate")
        btn:SetSize(SWATCH_W, SWATCH_H)
        btn:SetPoint("TOPLEFT", col*(SWATCH_W+SWATCH_GAP), -row*(SWATCH_H+SWATCH_GAP))
        btn:SetBackdrop({
            bgFile   = "Interface\\Buttons\\WHITE8x8",
            edgeFile = "Interface\\Buttons\\WHITE8x8",
            edgeSize = 1,
        })
        btn:SetBackdropColor(cl.r, cl.g, cl.b, 1)
        btn:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
        btn:SetScript("OnEnter", function(self)
            self:SetBackdropBorderColor(1, 1, 1, 1)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:SetText(cl.name)
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function(self)
            self:SetBackdropBorderColor(RUI.C.BORDER.r, RUI.C.BORDER.g, RUI.C.BORDER.b, 1)
            GameTooltip:Hide()
        end)
        local cr, cg, cb = cl.r, cl.g, cl.b
        btn:SetScript("OnClick", function()
            curH, curS, curV = RGBtoHSV(cr, cg, cb)
            UpdateAll()
        end)
    end

    SetActiveTab("saved")

    -- ── Mouse handlers ────────────────────────────────────────────────────────────
    local isDragSq, isDragHue, isDragAlpha = false, false, false

    sqFrame:EnableMouse(true)
    sqFrame:SetScript("OnMouseDown", function(self, btn)
        if btn ~= "LeftButton" then return end
        isDragSq = true
        local sc = self:GetEffectiveScale()
        local cx, cy = GetCursorPosition()
        cx, cy = cx/sc, cy/sc
        curS = math.max(0, math.min(1, (cx - self:GetLeft()) / SQ_SZ))
        curV = math.max(0, math.min(1, (cy - self:GetBottom()) / SQ_SZ))
        UpdateAll()
    end)
    sqFrame:SetScript("OnMouseUp", function() isDragSq = false end)
    sqFrame:SetScript("OnUpdate", function(self)
        if not isDragSq then return end
        local sc = self:GetEffectiveScale()
        local cx, cy = GetCursorPosition()
        cx, cy = cx/sc, cy/sc
        curS = math.max(0, math.min(1, (cx - self:GetLeft()) / SQ_SZ))
        curV = math.max(0, math.min(1, (cy - self:GetBottom()) / SQ_SZ))
        UpdateAll()
    end)

    hueBar:EnableMouse(true)
    hueBar:SetScript("OnMouseDown", function(self, btn)
        if btn ~= "LeftButton" then return end
        isDragHue = true
        local sc = self:GetEffectiveScale()
        local _, cy = GetCursorPosition()
        cy = cy/sc
        curH = math.max(0, math.min(360, ((self:GetTop() - cy) / SQ_SZ) * 360))
        UpdateAll()
    end)
    hueBar:SetScript("OnMouseUp", function() isDragHue = false end)
    hueBar:SetScript("OnUpdate", function(self)
        if not isDragHue then return end
        local sc = self:GetEffectiveScale()
        local _, cy = GetCursorPosition()
        cy = cy/sc
        curH = math.max(0, math.min(360, ((self:GetTop() - cy) / SQ_SZ) * 360))
        UpdateAll()
    end)

    alphaBar:EnableMouse(true)
    alphaBar:SetScript("OnMouseDown", function(self, btn)
        if btn ~= "LeftButton" then return end
        isDragAlpha = true
        local sc = self:GetEffectiveScale()
        local _, cy = GetCursorPosition()
        cy = cy/sc
        curA = math.max(0, math.min(1, 1 - ((self:GetTop() - cy) / SQ_SZ)))
        UpdateAll()
    end)
    alphaBar:SetScript("OnMouseUp", function() isDragAlpha = false end)
    alphaBar:SetScript("OnUpdate", function(self)
        if not isDragAlpha then return end
        local sc = self:GetEffectiveScale()
        local _, cy = GetCursorPosition()
        cy = cy/sc
        curA = math.max(0, math.min(1, 1 - ((self:GetTop() - cy) / SQ_SZ)))
        UpdateAll()
    end)

    -- ── Input handlers ────────────────────────────────────────────────────────────
    local function OnRGBAChanged()
        if isUpdating then return end
        local r = math.max(0, math.min(1, (tonumber(rIn:GetText()) or 0) / 255))
        local g = math.max(0, math.min(1, (tonumber(gIn:GetText()) or 0) / 255))
        local b = math.max(0, math.min(1, (tonumber(bIn:GetText()) or 0) / 255))
        local a = math.max(0, math.min(1, (tonumber(aIn:GetText()) or 100) / 100))
        curH, curS, curV = RGBtoHSV(r, g, b)
        curA = a
        UpdateAll()
    end

    for _, eb in ipairs({rIn, gIn, bIn, aIn}) do
        eb:SetScript("OnEnterPressed", function(self) OnRGBAChanged(); self:ClearFocus() end)
        eb:SetScript("OnTextChanged",  function(self, ui) if ui then OnRGBAChanged() end end)
    end

    hexIn:SetScript("OnEnterPressed", function(self)
        if isUpdating then return end
        local r, g, b, a = HexToRGB(self:GetText())
        curH, curS, curV = RGBtoHSV(r, g, b)
        if a and curHasAlpha then curA = a end
        UpdateAll(); self:ClearFocus()
    end)
    hexIn:SetScript("OnTextChanged", function(self, ui)
        if not ui or isUpdating then return end
        local hex = self:GetText()
        if hex:match("^#%x%x%x%x%x%x") then
            local r, g, b, a = HexToRGB(hex)
            if r then
                curH, curS, curV = RGBtoHSV(r, g, b)
                if a and curHasAlpha then curA = a end
                UpdateAll()
            end
        end
    end)

    return f
end

-- ─── Public API ───────────────────────────────────────────────────────────────

function RUI.OpenColorPicker(initialColor, hasAlpha, onAccept, onCancel, onChange, defaultColor)
    if not pickerFrame then
        pickerFrame = BuildColorPicker()
    end

    if pickerFrame:IsShown() then
        pickerFrame._applied = true
        pickerFrame:Hide()
    end

    onAcceptCb       = onAccept
    onCancelCb       = onCancel
    onChangeCb       = onChange
    pickerDefaultColor = defaultColor
    pickerFrame._applied = false

    curHasAlpha = hasAlpha and true or false
    alphaBar:SetShown(curHasAlpha)
    aInParent:SetShown(curHasAlpha)

    if defaultColor then defaultBtn:Show() else defaultBtn:Hide() end

    if initialColor then
        curH, curS, curV = RGBtoHSV(
            initialColor.r or 1,
            initialColor.g or 1,
            initialColor.b or 1)
        curA = initialColor.a or 1
    end

    RefreshSwatches(savedContent,  savedColors)
    RefreshSwatches(recentContent, recentColors)

    skipChange = true
    UpdateAll()
    skipChange = false

    pickerFrame:Show()
end
