-- CastBarConfig.lua
-- Simple addon to configure PlayerCastingBarFrame and its sub-elements

local ADDON_NAME = ...


local defaults = {
    scale = 1.2,
    alpha = 1,
    x = 0,
    y = 0,
    height = 32,
    castTimeTextX = 0,
    castTimeTextY = 0,
    castNameTextX = 0,
    castNameTextY = 0,
    hideTextBorder = false,
    iconSize = 32,
    iconX = -36,
    iconY = 0,
    hideBorderMask = false,
    barWidth = 200,
    barTexture = 1,
    castTimeFontSize = 12,
    castNameFontSize = 12,
    gradientStart = {r=1, g=0.7, b=0, a=1}, -- default orange
    gradientEnd = {r=1, g=0, b=0, a=1},     -- default red
}

local BAR_TEXTURES = {
    { name = "Default (Blizzard)",     path = "Interface\\RaidFrame\\Raid-Bar-Hp-Fill" },
    { name = "Flat",                   path = "Interface\\Buttons\\WHITE8x8" },
    { name = "UI StatusBar",           path = "Interface\\TargetingFrame\\UI-StatusBar" },
    { name = "Skills Bar",             path = "Interface\\PaperDollInfoFrame\\UI-Character-Skills-Bar" },
    { name = "Raid Bar Resource",      path = "Interface\\RaidFrame\\Raid-Bar-Resource-Fill" },
    { name = "Nameplate Bar",          path = "Interface\\TargetingFrame\\UI-TargetingFrame-BarFill" },
}

-- Append LibSharedMedia statusbar textures (if available)
local function AppendSharedMediaTextures()
    local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
    if not LSM then return end

    -- Build a set of paths we already have to avoid duplicates
    local existing = {}
    for _, entry in ipairs(BAR_TEXTURES) do
        existing[entry.path] = true
    end

    local list = LSM:List(LSM.MediaType.STATUSBAR)
    if not list then return end

    for _, name in ipairs(list) do
        local path = LSM:Fetch(LSM.MediaType.STATUSBAR, name)
        if path and not existing[path] then
            BAR_TEXTURES[#BAR_TEXTURES + 1] = { name = "SM: " .. name, path = path }
            existing[path] = true
        end
    end
end

local function ApplyConfig()
    local f = PlayerCastingBarFrame
    if not f then return end
    f:SetScale(CastBarConfigDB.scale or defaults.scale)
    f:SetAlpha(CastBarConfigDB.alpha or defaults.alpha)
    f:SetHeight(CastBarConfigDB.height or defaults.height)
    if f.SetWidth then f:SetWidth(CastBarConfigDB.barWidth or defaults.barWidth) end

    -- StatusBar texture
    local texIdx = CastBarConfigDB.barTexture or defaults.barTexture
    if texIdx < 1 or texIdx > #BAR_TEXTURES then texIdx = 1 end
    if f.SetStatusBarTexture then
        f:SetStatusBarTexture(BAR_TEXTURES[texIdx].path)
    end

    -- Only set position if not managed by Edit Mode or not user-placed
    local isEditMode = false
    if EditModeManagerFrame and EditModeManagerFrame.ShouldUseEditMode and type(EditModeManagerFrame.ShouldUseEditMode) == "function" then
        if EditModeManagerFrame:ShouldUseEditMode() and f.layoutIndex then
            isEditMode = true
        end
    elseif f.isManagedFrame then
        isEditMode = true
    end
    if not isEditMode and not f:IsUserPlaced() then
        f:ClearAllPoints()
        f:SetPoint("CENTER", UIParent, "CENTER", CastBarConfigDB.x or defaults.x, CastBarConfigDB.y or defaults.y)
    end

    -- Make Spark match castbar height
    if f.Spark then
        f.Spark:SetHeight(f:GetHeight())
    end

    -- Move CastTimeText if it exists
    if f.CastTimeText then
        f.CastTimeText:ClearAllPoints()
        f.CastTimeText:SetPoint("RIGHT", f, "RIGHT", CastBarConfigDB.castTimeTextX or defaults.castTimeTextX, CastBarConfigDB.castTimeTextY or defaults.castTimeTextY)
        local font = f.CastTimeText:GetFont()
        f.CastTimeText:SetFont(font or "Fonts\\FRIZQT__.TTF", CastBarConfigDB.castTimeFontSize or defaults.castTimeFontSize, "OUTLINE")
    end
    -- Move Cast Name Text if it exists
    if f.Text then
        f.Text:ClearAllPoints()
        f.Text:SetPoint("LEFT", f, "LEFT", CastBarConfigDB.castNameTextX or defaults.castNameTextX, CastBarConfigDB.castNameTextY or defaults.castNameTextY)
        local font = f.Text:GetFont()
        f.Text:SetFont(font or "Fonts\\FRIZQT__.TTF", CastBarConfigDB.castNameFontSize or defaults.castNameFontSize, "OUTLINE")
    end

    -- Hide or show TextBorder (hook checks DB so toggling works both ways)
    if f.TextBorder then
        if CastBarConfigDB.hideTextBorder then f.TextBorder:Hide() else f.TextBorder:Show() end
        if not f.TextBorder._ccbHooked then
            hooksecurefunc(f.TextBorder, "Show", function(self)
                if CastBarConfigDB.hideTextBorder then self:Hide() end
            end)
            f.TextBorder._ccbHooked = true
        end
    end
    -- Hide or show Border/Mask (hook checks DB so toggling works both ways)
    if f.Border then
        if CastBarConfigDB.hideBorderMask then f.Border:Hide() else f.Border:Show() end
        if not f.Border._ccbHooked then
            hooksecurefunc(f.Border, "Show", function(self)
                if CastBarConfigDB.hideBorderMask then self:Hide() end
            end)
            f.Border._ccbHooked = true
        end
    end
    if f.BorderMask then
        if CastBarConfigDB.hideBorderMask then f.BorderMask:Hide() else f.BorderMask:Show() end
        if not f.BorderMask._ccbHooked then
            hooksecurefunc(f.BorderMask, "Show", function(self)
                if CastBarConfigDB.hideBorderMask then self:Hide() end
            end)
            f.BorderMask._ccbHooked = true
        end
    end

    -- Custom 1-pixel border
    if not f.CustomBorder then
        local border = CreateFrame("Frame", nil, f, "BackdropTemplate")
        border:SetFrameLevel(f:GetFrameLevel() + 10)
        f.CustomBorder = border
        border:SetBackdrop({
            edgeFile = "Interface/Buttons/WHITE8x8",
            edgeSize = 1,
        })
        border:SetBackdropBorderColor(0, 0, 0, 1)
    end
    f.CustomBorder:SetPoint("TOPLEFT", f, "TOPLEFT", -1, 1)
    f.CustomBorder:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 1, -1)
    if CastBarConfigDB.hideBorderMask then
        f.CustomBorder:Show()
    else
        f.CustomBorder:Hide()
    end

    -- Cast Icon
    if not f.CastIcon then
        local icon = f:CreateTexture(nil, "ARTWORK")
        icon:SetSize(CastBarConfigDB.iconSize or defaults.iconSize, CastBarConfigDB.iconSize or defaults.iconSize)
        icon:SetPoint("LEFT", f, "LEFT", CastBarConfigDB.iconX or defaults.iconX, CastBarConfigDB.iconY or defaults.iconY)
        f.CastIcon = icon
    else
        f.CastIcon:SetSize(CastBarConfigDB.iconSize or defaults.iconSize, CastBarConfigDB.iconSize or defaults.iconSize)
        f.CastIcon:ClearAllPoints()
        f.CastIcon:SetPoint("LEFT", f, "LEFT", CastBarConfigDB.iconX or defaults.iconX, CastBarConfigDB.iconY or defaults.iconY)
    end

    -- Gradient application
    local start = CastBarConfigDB.gradientStart or defaults.gradientStart
    local finish = CastBarConfigDB.gradientEnd or defaults.gradientEnd
    if f:GetStatusBarTexture() and f:GetStatusBarTexture().SetGradient then
        f:GetStatusBarTexture():SetGradient("HORIZONTAL",
            CreateColor(start.r, start.g, start.b, start.a),
            CreateColor(finish.r, finish.g, finish.b, finish.a)
        )
    end

    -- Hook SetStatusBarColor: Blizzard calls this on interruptibility changes
    -- which destroys the gradient. Re-apply after every call.
    if not f._ccbColorHooked then
        hooksecurefunc(f, "SetStatusBarColor", function(self)
            local s = CastBarConfigDB.gradientStart or defaults.gradientStart
            local e = CastBarConfigDB.gradientEnd or defaults.gradientEnd
            local tex = self:GetStatusBarTexture()
            if tex and tex.SetGradient then
                tex:SetGradient("HORIZONTAL",
                    CreateColor(s.r, s.g, s.b, s.a),
                    CreateColor(e.r, e.g, e.b, e.a)
                )
            end
        end)
        f._ccbColorHooked = true
    end
end

-- Lightweight re-apply for things Blizzard resets during cast events:
-- gradient, border/mask visibility, custom border. No position/scale work.
local function ReapplyVisuals()
    local f = PlayerCastingBarFrame
    if not f then return end

    -- Re-apply texture (Blizzard can reset it)
    if f.SetStatusBarTexture then
        local texIdx = CastBarConfigDB.barTexture or defaults.barTexture
        if texIdx >= 1 and texIdx <= #BAR_TEXTURES then
            f:SetStatusBarTexture(BAR_TEXTURES[texIdx].path)
        end
    end

    -- Gradient
    local start = CastBarConfigDB.gradientStart or defaults.gradientStart
    local finish = CastBarConfigDB.gradientEnd or defaults.gradientEnd
    if f:GetStatusBarTexture() and f:GetStatusBarTexture().SetGradient then
        f:GetStatusBarTexture():SetGradient("HORIZONTAL",
            CreateColor(start.r, start.g, start.b, start.a),
            CreateColor(finish.r, finish.g, finish.b, finish.a)
        )
    end

    -- Border / Mask visibility
    if f.TextBorder then
        if CastBarConfigDB.hideTextBorder then f.TextBorder:Hide() else f.TextBorder:Show() end
    end
    if f.Border then
        if CastBarConfigDB.hideBorderMask then f.Border:Hide() else f.Border:Show() end
    end
    if f.BorderMask then
        if CastBarConfigDB.hideBorderMask then f.BorderMask:Hide() else f.BorderMask:Show() end
    end

    -- Custom 1-pixel border
    if f.CustomBorder then
        if CastBarConfigDB.hideBorderMask then f.CustomBorder:Show() else f.CustomBorder:Hide() end
    end
end

-- Color Picker helper
local function CreateColorPicker(parent, labelText, initialColor, onChanged)
    local container = CreateFrame("Frame", nil, parent)
    container:SetSize(210, 28)

    local label = container:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    label:SetPoint("LEFT", 0, 0)
    label:SetText(labelText)
    label:SetTextColor(0.85, 0.7, 1, 1)

    local swatch = CreateFrame("Button", nil, container)
    swatch:SetSize(24, 24)
    swatch:SetPoint("LEFT", label, "RIGHT", 8, 0)
    swatch.bg = swatch:CreateTexture(nil, "BACKGROUND")
    swatch.bg:SetAllPoints()
    swatch.bg:SetColorTexture(initialColor.r, initialColor.g, initialColor.b, initialColor.a)

    swatch:SetScript("OnClick", function()
        local function ColorCallback(restore)
            local r, g, b, a
            if restore then
                r, g, b, a = unpack(restore)
            else
                if ColorPickerFrame.GetColorRGB then
                    r, g, b = ColorPickerFrame:GetColorRGB()
                else
                    r, g, b = ColorPickerFrame.r, ColorPickerFrame.g, ColorPickerFrame.b
                end
                if _G.OpacitySliderFrame and _G.OpacitySliderFrame.GetValue then
                    a = 1 - OpacitySliderFrame:GetValue()
                else
                    a = initialColor.a or 1
                end
            end
            swatch.bg:SetColorTexture(r, g, b, a)
            onChanged({r=r, g=g, b=b, a=a})
        end
        ColorPickerFrame.hasOpacity = true
        ColorPickerFrame.opacity = 1 - (initialColor.a or 1)
        ColorPickerFrame.previousValues = {initialColor.r, initialColor.g, initialColor.b, initialColor.a}
        ColorPickerFrame.func = ColorCallback
        ColorPickerFrame.opacityFunc = ColorCallback
        ColorPickerFrame.cancelFunc = ColorCallback
        ColorPickerFrame.swatchFunc = ColorCallback
        if ColorPickerFrame.SetColorRGB then
            ColorPickerFrame:SetColorRGB(initialColor.r, initialColor.g, initialColor.b)
        else
            ColorPickerFrame.r, ColorPickerFrame.g, ColorPickerFrame.b = initialColor.r, initialColor.g, initialColor.b
        end
        ColorPickerFrame:Show()
    end)

    -- Expose a method to update the color from outside
    container.UpdateValue = function(color)
        swatch.bg:SetColorTexture(color.r, color.g, color.b, color.a)
    end

    return container
end


local function ResetConfig()
    CastBarConfigDB = CopyTable(defaults)
    ApplyConfig()
end


-- Format slider values cleanly (avoids long float strings like "1.5700000524521")
local function FormatSliderValue(val, step)
    if step >= 1 then return tostring(math.floor(val + 0.5)) end
    if step >= 0.1 then return string.format("%.1f", val) end
    return string.format("%.2f", val)
end

-- Compact slider with input box
local function CreateSlider(parent, labelText, min, max, step, initial, onChanged)
    local container = CreateFrame("Frame", nil, parent)
    container:SetSize(320, 44)

    local label = container:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    label:SetPoint("TOPLEFT", 0, 0)
    label:SetText(labelText)
    label:SetTextColor(0.85, 0.7, 1, 1)

    -- Value label displayed to the right of the label text
    local valueLabel = container:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    valueLabel:SetPoint("LEFT", label, "RIGHT", 8, 0)

    local slider = CreateFrame("Slider", nil, container, "OptionsSliderTemplate")
    slider:SetPoint("TOPLEFT", 0, -18)
    slider:SetSize(160, 18)
    slider:SetMinMaxValues(min, max)
    slider:SetValueStep(step)
    slider:SetObeyStepOnDrag(true)
    slider:SetValue(initial)

    -- Hide default "Low" / "High" labels from the template
    if slider.Low then slider.Low:SetText("") end
    if slider.High then slider.High:SetText("") end
    if slider.Text then slider.Text:SetText("") end

    -- Single input box: displays the value and accepts typed input
    local input = CreateFrame("EditBox", nil, container, "InputBoxTemplate")
    input:SetSize(64, 22)
    input:SetPoint("LEFT", slider, "RIGHT", 10, 0)
    input:SetAutoFocus(false)
    input:SetMaxLetters(8)
    input:SetFontObject(GameFontHighlight)
    input:SetJustifyH("CENTER")
    input:Show()

    local function FormatAndSet(val)
        local txt = FormatSliderValue(val, step)
        input:SetText(txt)
        input:SetCursorPosition(0)
        valueLabel:SetText(txt)
    end

    local function SetValue(val)
        val = math.max(min, math.min(max, val))
        if step >= 1 then val = math.floor(val + 0.5) end
        slider:SetValue(val)
        FormatAndSet(val)
    end
    SetValue(initial)

    slider:SetScript("OnValueChanged", function(_, val)
        FormatAndSet(val)
        onChanged(val)
    end)

    input:SetScript("OnEnterPressed", function(self)
        local val = tonumber(self:GetText())
        if val then
            val = math.max(min, math.min(max, val))
            slider:SetValue(val)
            onChanged(val)
        end
        FormatAndSet(slider:GetValue())
        self:ClearFocus()
    end)

    input:SetScript("OnEscapePressed", function(self)
        FormatAndSet(slider:GetValue())
        self:ClearFocus()
    end)

    input:SetScript("OnEditFocusLost", function(self)
        FormatAndSet(slider:GetValue())
    end)

    -- InputBoxTemplate can clear text set before visible; re-apply on every Show
    input:SetScript("OnShow", function()
        FormatAndSet(slider:GetValue())
    end)

    container:SetScript("OnShow", function()
        FormatAndSet(slider:GetValue())
    end)

    container.UpdateValue = function(val)
        SetValue(val)
    end

    return container
end

local optionsPanel
function CreateOptionsPanel()
    if optionsPanel then return optionsPanel end
    optionsPanel = CreateFrame("Frame", "CastBarConfigOptionsPanel", InterfaceOptionsFramePanelContainer or UIParent)
    optionsPanel.name = "CastBarConfig"
    optionsPanel:SetSize(400, 600)

    -- ScrollFrame setup
    local scrollFrame = CreateFrame("ScrollFrame", nil, optionsPanel, "UIPanelScrollFrameTemplate")
    scrollFrame:SetPoint("TOPLEFT", 0, 0)
    scrollFrame:SetPoint("BOTTOMRIGHT", 0, 0)

    local content = CreateFrame("Frame", nil, scrollFrame)
    content:SetSize(380, 1600)
    scrollFrame:SetScrollChild(content)
    optionsPanel._content = content

    local y = -20
    -- Add logo at the top
    local logo = content:CreateTexture(nil, "ARTWORK")
    logo:SetTexture("Interface/AddOns/CastBarConfig/CK_logo.tga")
    logo:SetSize(128, 64)
    logo:SetPoint("TOP", -40, y)
    -- Add note next to logo
    local note = content:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    note:SetPoint("LEFT", logo, "RIGHT", 16, 0)
    note:SetText("Made by Ckraigfriend")
    note:SetTextColor(0.8, 0.95, 1, 1)
    note:SetJustifyH("LEFT")
    y = y - 70
    local sliders = {}
    local function AddWidget(widget, key)
        widget:SetPoint("TOPLEFT", 20, y)
        y = y - widget:GetHeight() - 12
        if key then sliders[key] = widget end
    end

    local title = content:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOP", 0, y)
    title:SetText("CastBarConfig")
    title:SetTextColor(1, 1, 1, 1)
    y = y - 36

    -- Section header helper
    local function AddSection(text)
        y = y - 8
        local header = content:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
        header:SetPoint("TOPLEFT", 20, y)
        header:SetText(text)
        header:SetTextColor(1, 0.82, 0, 1)
        local line = content:CreateTexture(nil, "ARTWORK")
        line:SetColorTexture(1, 0.82, 0, 0.3)
        line:SetSize(340, 1)
        line:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, -2)
        y = y - 24
    end

    -- Checkbox helper (with UpdateValue for generic refresh)
    local function CreateCheckbox(parent, labelText, initial, onChanged)
        local container = CreateFrame("Frame", nil, parent)
        container:SetSize(210, 24)
        local checkbox = CreateFrame("CheckButton", nil, container, "UICheckButtonTemplate")
        checkbox:SetPoint("LEFT", 0, 0)
        checkbox:SetSize(20, 20)
        checkbox:SetChecked(initial)
        local label = container:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        label:SetPoint("LEFT", checkbox, "RIGHT", 8, 0)
        label:SetText(labelText)
        label:SetTextColor(0.85, 0.7, 1, 1)
        checkbox:SetScript("OnClick", function(self)
            onChanged(self:GetChecked() and true or false)
        end)
        container.UpdateValue = function(val)
            checkbox:SetChecked(val and true or false)
        end
        return container
    end

    -- ===== Bar =====
    AddSection("Bar")
    AddWidget(CreateSlider(content, "Scale", 0.5, 3, 0.01, CastBarConfigDB.scale or defaults.scale, function(v) CastBarConfigDB.scale = v; ApplyConfig() end), "scale")
    AddWidget(CreateSlider(content, "Alpha", 0.1, 1, 0.01, CastBarConfigDB.alpha or defaults.alpha, function(v) CastBarConfigDB.alpha = v; ApplyConfig() end), "alpha")
    AddWidget(CreateSlider(content, "Height", 8, 128, 1, CastBarConfigDB.height or defaults.height, function(v) CastBarConfigDB.height = v; ApplyConfig() end), "height")
    AddWidget(CreateSlider(content, "Width", 50, 500, 1, CastBarConfigDB.barWidth or defaults.barWidth, function(v) CastBarConfigDB.barWidth = v; ApplyConfig() end), "barWidth")
    AddWidget(CreateSlider(content, "X Offset", -500, 500, 1, CastBarConfigDB.x or defaults.x, function(v) CastBarConfigDB.x = v; ApplyConfig() end), "x")
    AddWidget(CreateSlider(content, "Y Offset", -500, 500, 1, CastBarConfigDB.y or defaults.y, function(v) CastBarConfigDB.y = v; ApplyConfig() end), "y")

    -- Texture picker
    local function CreateTexturePicker(parent, initial, onChanged)
        local container = CreateFrame("Frame", nil, parent)
        container:SetSize(320, 60)

        local label = container:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        label:SetPoint("TOPLEFT", 0, 0)
        label:SetText("Bar Texture")
        label:SetTextColor(0.85, 0.7, 1, 1)

        local nameLabel = container:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        nameLabel:SetPoint("LEFT", label, "RIGHT", 8, 0)

        -- Texture preview strip
        local preview = container:CreateTexture(nil, "ARTWORK")
        preview:SetSize(160, 16)
        preview:SetPoint("TOPLEFT", 30, -18)
        preview:SetVertexColor(1, 0.7, 0, 1)

        local idx = initial
        local function Refresh()
            if idx < 1 then idx = #BAR_TEXTURES end
            if idx > #BAR_TEXTURES then idx = 1 end
            local entry = BAR_TEXTURES[idx]
            if entry then
                nameLabel:SetText(entry.name)
                preview:SetTexture(entry.path)
            end
        end
        Refresh()

        -- Prev button
        local prevBtn = CreateFrame("Button", nil, container, "UIPanelButtonTemplate")
        prevBtn:SetSize(24, 18)
        prevBtn:SetPoint("TOPLEFT", 0, -18)
        prevBtn:SetText("<")
        prevBtn:SetScript("OnClick", function()
            idx = idx - 1; Refresh(); onChanged(idx)
        end)

        -- Next button
        local nextBtn = CreateFrame("Button", nil, container, "UIPanelButtonTemplate")
        nextBtn:SetSize(24, 18)
        nextBtn:SetPoint("LEFT", preview, "RIGHT", 4, 0)
        nextBtn:SetText(">")
        nextBtn:SetScript("OnClick", function()
            idx = idx + 1; Refresh(); onChanged(idx)
        end)

        container.UpdateValue = function(val)
            idx = val or 1; Refresh()
        end

        return container
    end

    AddWidget(CreateTexturePicker(content, CastBarConfigDB.barTexture or defaults.barTexture, function(idx)
        CastBarConfigDB.barTexture = idx; ApplyConfig()
    end), "barTexture")

    -- ===== Text =====
    AddSection("Text")
    AddWidget(CreateSlider(content, "Cast Time Text X", -200, 200, 1, CastBarConfigDB.castTimeTextX or defaults.castTimeTextX, function(v) CastBarConfigDB.castTimeTextX = v; ApplyConfig() end), "castTimeTextX")
    AddWidget(CreateSlider(content, "Cast Time Text Y", -200, 200, 1, CastBarConfigDB.castTimeTextY or defaults.castTimeTextY, function(v) CastBarConfigDB.castTimeTextY = v; ApplyConfig() end), "castTimeTextY")
    AddWidget(CreateSlider(content, "Cast Time Font Size", 6, 32, 1, CastBarConfigDB.castTimeFontSize or defaults.castTimeFontSize, function(v) CastBarConfigDB.castTimeFontSize = v; ApplyConfig() end), "castTimeFontSize")
    AddWidget(CreateSlider(content, "Cast Name Text X", -200, 200, 1, CastBarConfigDB.castNameTextX or defaults.castNameTextX, function(v) CastBarConfigDB.castNameTextX = v; ApplyConfig() end), "castNameTextX")
    AddWidget(CreateSlider(content, "Cast Name Text Y", -200, 200, 1, CastBarConfigDB.castNameTextY or defaults.castNameTextY, function(v) CastBarConfigDB.castNameTextY = v; ApplyConfig() end), "castNameTextY")
    AddWidget(CreateSlider(content, "Cast Name Font Size", 6, 32, 1, CastBarConfigDB.castNameFontSize or defaults.castNameFontSize, function(v) CastBarConfigDB.castNameFontSize = v; ApplyConfig() end), "castNameFontSize")

    -- ===== Icon =====
    AddSection("Icon")
    AddWidget(CreateSlider(content, "Icon Size", 8, 64, 1, CastBarConfigDB.iconSize or defaults.iconSize, function(v) CastBarConfigDB.iconSize = v; ApplyConfig() end), "iconSize")
    AddWidget(CreateSlider(content, "Icon X Offset", -200, 200, 1, CastBarConfigDB.iconX or defaults.iconX, function(v) CastBarConfigDB.iconX = v; ApplyConfig() end), "iconX")
    AddWidget(CreateSlider(content, "Icon Y Offset", -200, 200, 1, CastBarConfigDB.iconY or defaults.iconY, function(v) CastBarConfigDB.iconY = v; ApplyConfig() end), "iconY")

    -- ===== Colors =====
    AddSection("Colors")
    AddWidget(CreateColorPicker(content, "Gradient Start", CastBarConfigDB.gradientStart or defaults.gradientStart, function(color)
        CastBarConfigDB.gradientStart = color; ApplyConfig()
    end), "gradientStart")
    AddWidget(CreateColorPicker(content, "Gradient End", CastBarConfigDB.gradientEnd or defaults.gradientEnd, function(color)
        CastBarConfigDB.gradientEnd = color; ApplyConfig()
    end), "gradientEnd")

    -- ===== Visibility =====
    AddSection("Visibility")
    AddWidget(CreateCheckbox(content, "Hide Text Border", CastBarConfigDB.hideTextBorder == true, function(v)
        CastBarConfigDB.hideTextBorder = v; ApplyConfig()
    end), "hideTextBorder")
    AddWidget(CreateCheckbox(content, "Hide Border/Mask", CastBarConfigDB.hideBorderMask == true, function(v)
        CastBarConfigDB.hideBorderMask = v; ApplyConfig()
    end), "hideBorderMask")

    -- Reset button
    local resetBtn = CreateFrame("Button", nil, content, "UIPanelButtonTemplate")
    resetBtn:SetSize(140, 24)
    resetBtn:SetPoint("TOPLEFT", 20, y)
    resetBtn:SetText("Reset to Defaults")
    resetBtn:SetScript("OnClick", function()
        ResetConfig()
        for key, widget in pairs(sliders) do
            if widget.UpdateValue then
                local val = CastBarConfigDB[key] or defaults[key]
                if val ~= nil then widget.UpdateValue(val) end
            end
        end
    end)
    y = y - 36

    -- Preview button: shows the cast bar for 3 seconds so you can see your settings
    local previewBtn = CreateFrame("Button", nil, content, "UIPanelButtonTemplate")
    previewBtn:SetSize(140, 24)
    previewBtn:SetPoint("TOPLEFT", 20, y)
    previewBtn:SetText("Preview Cast Bar")
    previewBtn:SetScript("OnClick", function()
        local bar = PlayerCastingBarFrame
        if not bar then return end
        ApplyConfig()
        bar:Show()
        C_Timer.After(3, function()
            if not UnitCastingInfo("player") and not UnitChannelInfo("player") then
                bar:Hide()
            end
        end)
    end)

    -- OnShow: generic refresh of all registered widgets from saved variables
    optionsPanel:HookScript("OnShow", function()
        for key, widget in pairs(sliders) do
            if widget.UpdateValue then
                local val = CastBarConfigDB[key] or defaults[key]
                if val ~= nil then widget.UpdateValue(val) end
            end
        end
    end)

    -- Register options panel for both Dragonflight+ and Classic/older
    if Settings and Settings.RegisterCanvasLayoutCategory and Settings.RegisterAddOnCategory then
        local category = Settings.RegisterCanvasLayoutCategory(optionsPanel, "CastBarConfig")
        optionsPanel._settingsCategoryID = category:GetID()
        Settings.RegisterAddOnCategory(category)
    elseif InterfaceOptions_AddCategory then
        InterfaceOptions_AddCategory(optionsPanel)
    end
    return optionsPanel
end

-- ================================================================
-- Edit Mode Integration: attach settings to Blizzard Cast Bar dialog
-- ================================================================
local function SetupEditModeSettings()
    local LibEM = LibStub and LibStub("LibEditMode", true)
    if not LibEM then return end

    local castBarSystem = Enum.EditModeSystem and Enum.EditModeSystem.CastBar
    if not castBarSystem then return end

    local ST = LibEM.SettingType

    -- Build texture dropdown values
    local textureValues = {}
    for i, tex in ipairs(BAR_TEXTURES) do
        textureValues[i] = { text = tex.name, value = i }
    end

    local settings = {
        -- Bar
        { kind = ST.Slider, name = "Scale", default = defaults.scale,
          minValue = 0.5, maxValue = 3, valueStep = 0.01,
          get = function() return CastBarConfigDB.scale or defaults.scale end,
          set = function(_, val) CastBarConfigDB.scale = val; ApplyConfig() end },
        { kind = ST.Slider, name = "Alpha", default = defaults.alpha,
          minValue = 0.1, maxValue = 1, valueStep = 0.01,
          get = function() return CastBarConfigDB.alpha or defaults.alpha end,
          set = function(_, val) CastBarConfigDB.alpha = val; ApplyConfig() end },
        { kind = ST.Slider, name = "Height", default = defaults.height,
          minValue = 8, maxValue = 128, valueStep = 1,
          get = function() return CastBarConfigDB.height or defaults.height end,
          set = function(_, val) CastBarConfigDB.height = val; ApplyConfig() end },
        { kind = ST.Slider, name = "Width", default = defaults.barWidth,
          minValue = 50, maxValue = 500, valueStep = 1,
          get = function() return CastBarConfigDB.barWidth or defaults.barWidth end,
          set = function(_, val) CastBarConfigDB.barWidth = val; ApplyConfig() end },
        { kind = ST.Dropdown, name = "Bar Texture", default = defaults.barTexture,
          values = textureValues, height = 300,
          get = function() return CastBarConfigDB.barTexture or defaults.barTexture end,
          set = function(_, val) CastBarConfigDB.barTexture = val; ApplyConfig() end },
        -- Text
        { kind = ST.Slider, name = "Cast Time Text X", default = defaults.castTimeTextX,
          minValue = -200, maxValue = 200, valueStep = 1,
          get = function() return CastBarConfigDB.castTimeTextX or defaults.castTimeTextX end,
          set = function(_, val) CastBarConfigDB.castTimeTextX = val; ApplyConfig() end },
        { kind = ST.Slider, name = "Cast Time Text Y", default = defaults.castTimeTextY,
          minValue = -200, maxValue = 200, valueStep = 1,
          get = function() return CastBarConfigDB.castTimeTextY or defaults.castTimeTextY end,
          set = function(_, val) CastBarConfigDB.castTimeTextY = val; ApplyConfig() end },
        { kind = ST.Slider, name = "Cast Time Font Size", default = defaults.castTimeFontSize,
          minValue = 6, maxValue = 32, valueStep = 1,
          get = function() return CastBarConfigDB.castTimeFontSize or defaults.castTimeFontSize end,
          set = function(_, val) CastBarConfigDB.castTimeFontSize = val; ApplyConfig() end },
        { kind = ST.Slider, name = "Cast Name Text X", default = defaults.castNameTextX,
          minValue = -200, maxValue = 200, valueStep = 1,
          get = function() return CastBarConfigDB.castNameTextX or defaults.castNameTextX end,
          set = function(_, val) CastBarConfigDB.castNameTextX = val; ApplyConfig() end },
        { kind = ST.Slider, name = "Cast Name Text Y", default = defaults.castNameTextY,
          minValue = -200, maxValue = 200, valueStep = 1,
          get = function() return CastBarConfigDB.castNameTextY or defaults.castNameTextY end,
          set = function(_, val) CastBarConfigDB.castNameTextY = val; ApplyConfig() end },
        { kind = ST.Slider, name = "Cast Name Font Size", default = defaults.castNameFontSize,
          minValue = 6, maxValue = 32, valueStep = 1,
          get = function() return CastBarConfigDB.castNameFontSize or defaults.castNameFontSize end,
          set = function(_, val) CastBarConfigDB.castNameFontSize = val; ApplyConfig() end },
        -- Icon
        { kind = ST.Slider, name = "Icon Size", default = defaults.iconSize,
          minValue = 8, maxValue = 64, valueStep = 1,
          get = function() return CastBarConfigDB.iconSize or defaults.iconSize end,
          set = function(_, val) CastBarConfigDB.iconSize = val; ApplyConfig() end },
        { kind = ST.Slider, name = "Icon X Offset", default = defaults.iconX,
          minValue = -200, maxValue = 200, valueStep = 1,
          get = function() return CastBarConfigDB.iconX or defaults.iconX end,
          set = function(_, val) CastBarConfigDB.iconX = val; ApplyConfig() end },
        { kind = ST.Slider, name = "Icon Y Offset", default = defaults.iconY,
          minValue = -200, maxValue = 200, valueStep = 1,
          get = function() return CastBarConfigDB.iconY or defaults.iconY end,
          set = function(_, val) CastBarConfigDB.iconY = val; ApplyConfig() end },
        -- Colors
        { kind = ST.ColorPicker, name = "Gradient Start", hasOpacity = true,
          default = CreateColor(defaults.gradientStart.r, defaults.gradientStart.g, defaults.gradientStart.b, defaults.gradientStart.a),
          get = function()
              local c = CastBarConfigDB.gradientStart or defaults.gradientStart
              return CreateColor(c.r, c.g, c.b, c.a)
          end,
          set = function(_, color)
              local r, g, b, a = color:GetRGBA()
              CastBarConfigDB.gradientStart = {r=r, g=g, b=b, a=a}; ApplyConfig()
          end },
        { kind = ST.ColorPicker, name = "Gradient End", hasOpacity = true,
          default = CreateColor(defaults.gradientEnd.r, defaults.gradientEnd.g, defaults.gradientEnd.b, defaults.gradientEnd.a),
          get = function()
              local c = CastBarConfigDB.gradientEnd or defaults.gradientEnd
              return CreateColor(c.r, c.g, c.b, c.a)
          end,
          set = function(_, color)
              local r, g, b, a = color:GetRGBA()
              CastBarConfigDB.gradientEnd = {r=r, g=g, b=b, a=a}; ApplyConfig()
          end },
        -- Visibility
        { kind = ST.Checkbox, name = "Hide Text Border", default = false,
          get = function() return CastBarConfigDB.hideTextBorder end,
          set = function(_, val) CastBarConfigDB.hideTextBorder = val; ApplyConfig() end },
        { kind = ST.Checkbox, name = "Hide Border/Mask", default = false,
          get = function() return CastBarConfigDB.hideBorderMask end,
          set = function(_, val) CastBarConfigDB.hideBorderMask = val; ApplyConfig() end },
    }

    LibEM:AddSystemSettings(castBarSystem, settings)

    LibEM:AddSystemSettingsButtons(castBarSystem, {
        { text = "Preview Cast Bar", click = function()
            local bar = PlayerCastingBarFrame
            if not bar then return end
            ApplyConfig()
            bar:Show()
            C_Timer.After(3, function()
                if not UnitCastingInfo("player") and not UnitChannelInfo("player") then
                    bar:Hide()
                end
            end)
        end },
    })
end

local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_LOGIN")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:RegisterEvent("ZONE_CHANGED_NEW_AREA")
f:RegisterEvent("PLAYER_ENTERING_BATTLEGROUND")
f:RegisterEvent("PLAYER_REGEN_ENABLED")
f:SetScript("OnEvent", function(self, event, ...)
    if event == "PLAYER_LOGIN" then
        if type(CastBarConfigDB) ~= "table" then
            CastBarConfigDB = CopyTable(defaults)
        end
        AppendSharedMediaTextures()
        CreateOptionsPanel()
        SetupEditModeSettings()
    end
    -- Always re-apply config on any registered event
    ApplyConfig()
    -- Hook cast events to update icon
    local bar = PlayerCastingBarFrame
    if bar and bar.CastIcon == nil then
        ApplyConfig() -- ensure icon exists
    end
    if bar and not bar._iconHooked then
        -- OnShow: Blizzard calls Show() at cast start, which resets visuals
        bar:HookScript("OnShow", function()
            ReapplyVisuals()
        end)

        bar:HookScript("OnEvent", function(self, event, unit, ...)
            if (event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START") and unit == "player" then
                local name, _, icon = UnitCastingInfo("player")
                if not name then
                    name, _, icon = UnitChannelInfo("player")
                end
                -- Try to get empower spell icon if still nil
                if (not icon or icon == 0) and C_CastingBar and C_CastingBar.GetEmpowerSpellInfo then
                    local empowerInfo = C_CastingBar.GetEmpowerSpellInfo("player")
                    if empowerInfo and empowerInfo.icon then
                        icon = empowerInfo.icon
                    end
                end
                if icon and self.CastIcon then
                    self.CastIcon:SetTexture(icon)
                    self.CastIcon:Show()
                end
                ReapplyVisuals()
            elseif (event == "UNIT_SPELLCAST_STOP" or event == "UNIT_SPELLCAST_CHANNEL_STOP"
                    or event == "UNIT_SPELLCAST_FAILED" or event == "UNIT_SPELLCAST_INTERRUPTED") and unit == "player" then
                if self.CastIcon then
                    self.CastIcon:Hide()
                end
                ReapplyVisuals()
            elseif (event == "UNIT_SPELLCAST_INTERRUPTIBLE" or event == "UNIT_SPELLCAST_NOT_INTERRUPTIBLE") and unit == "player" then
                -- Blizzard calls SetStatusBarColor() on these, destroying the gradient
                ReapplyVisuals()
            end
        end)
        bar._iconHooked = true
    end
end)

-- Slash command: /castbar opens the options panel
SLASH_CASTBARCONFIG1 = "/castbar"
SlashCmdList["CASTBARCONFIG"] = function()
    if Settings and Settings.OpenToCategory then
        local panel = optionsPanel or CreateOptionsPanel()
        local catID = panel and panel._settingsCategoryID
        if catID then
            Settings.OpenToCategory(catID)
        end
    elseif InterfaceOptionsFrame_OpenToCategory then
        InterfaceOptionsFrame_OpenToCategory("CastBarConfig")
        InterfaceOptionsFrame_OpenToCategory("CastBarConfig")
    end
end
