local addonName, ns = ...

function ns.CreateExtrasTab()
    local UI = ns.UI
    local Palette = ns.Palette

    UI.tabFrames[3]  = UI.CreateTabContent()
    UI.tabButtons[3] = UI.CreateTabButton("Additional Options", 3)

    local frame = UI.tabFrames[3]

    -- Every header shares the framework's plain-text build: full
    -- hairline/platform/hairline band sized from the rendered string,
    -- resting at the candlelight tint. No AttachHeaderHover here — the
    -- hover cue is reserved for headers that are actually clickable.
    local DecorateTextHeader = UI.DecorateTextHeader

    -- Section heading
    local heading = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    heading:SetPoint("TOPLEFT", 24, -24)
    heading:SetText("Additional Options")
    DecorateTextHeader(heading)

    local subheading = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    subheading:SetPoint("TOPLEFT", 24, -60)
    subheading:SetText("Additional toggles and tweaks land here.")
    subheading:SetTextColor(unpack(Palette.textLabel))

    -- ============================================
    -- SHARED-key control wrappers
    -- ============================================
    -- The framework's CreateCheckbox/CreateSlider/CreateNumberBox all write to
    -- ns.settings (the active profile). The settings on this tab live at
    -- ns.db root (per-character via SHARED_KEYS), so we wrap the helpers and
    -- override their callbacks to route through the SHARED setters.
    local function MakeSharedCheckbox(parent, x, y, label, getFn, setFn, refreshFn)
        local cb = UI.CreateCheckbox(parent, x, y, label, nil, nil)
        cb:SetScript("OnClick", function(self)
            -- Replacing OnClick bypasses the framework's handler, so drive
            -- the toggle-switch knob animation ourselves
            if self.UpdateToggleVisual then self:UpdateToggleVisual() end
            -- One sound both ways: the state is already obvious from the knob,
            -- and two near-identical sounds read as inconsistency, not feedback.
            ns.PlayUISound("Toggle")
            setFn(self:GetChecked() and true or false)
            if refreshFn then refreshFn() end
        end)
        return cb
    end

    local function MakeSharedSlider(parent, x, y, label, min, max, step, getFn, setFn, refreshFn)
        local slider = UI.CreateSlider(parent, x, y, label, min, max, step, nil, false)
        slider:SetScript("OnValueChanged", function(self, value)
            value = math.floor(value / step + 0.5) * step
            self.valueText:SetText(value)
            -- Replacing OnValueChanged bypasses the framework's handler, so
            -- keep the capsule fill in sync ourselves
            if self.UpdateFill then self.UpdateFill() end
            setFn(value)
            if refreshFn then refreshFn() end
        end)
        return slider
    end

    local function MakeSharedNumberBox(parent, x, y, label, min, max, getFn, setFn, refreshFn)
        local box = UI.CreateNumberBox(parent, x, y, label, min, max, nil)
        box:SetScript("OnEnterPressed", function(self)
            local val = tonumber(self:GetText())
            if val then
                val = math.floor(val + 0.5)
                val = math.max(min, math.min(max, val))
                setFn(val)
                self:SetText(tostring(val))
                if refreshFn then refreshFn() end
            else
                self:SetText(tostring(getFn()))
            end
            self:ClearFocus()
        end)
        box:SetScript("OnEscapePressed", function(self)
            self:SetText(tostring(getFn()))
            self:ClearFocus()
        end)
        return box
    end

    -- Custom dropdown that writes via SHARED setter. The framework's
    -- CreateDropdown closure-binds ns.settings, which we can't redirect.
    local function MakeSharedDropdown(parent, x, y, label, width, options, getFn, setFn, refreshFn)
        local container = CreateFrame("Frame", nil, parent)
        container:SetSize(width + 16, 50)
        container:SetPoint("TOPLEFT", x, y)

        local text = container:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        text:SetPoint("TOPLEFT", 0, 0)
        text:SetText(label)
        text:SetTextColor(unpack(Palette.textLabel))

        local dropdown = CreateFrame("DropdownButton", nil, container, "WowStyle1DropdownTemplate")
        dropdown:SetPoint("TOPLEFT", 0, -16)
        dropdown:SetWidth(width)

        dropdown:SetupMenu(function(_, rootDescription)
            for _, value in ipairs(options) do
                rootDescription:CreateRadio(value,
                    function() return getFn() == value end,
                    function()
                        setFn(value)
                        if refreshFn then refreshFn() end
                    end
                )
            end
        end)

        return dropdown
    end

    -- ============================================
    -- PvP Queue Warnings
    -- ============================================
    local drinkCheckbox = MakeSharedCheckbox(
        frame, 40, -115, "Low Drink Warning",
        ns.GetDrinkWarningEnabled, ns.SetDrinkWarningEnabled, ns.UpdateDrinkWarning
    )
    drinkCheckbox.text:SetFontObject("GameFontNormalLarge")
    drinkCheckbox.text:SetTextColor(1, 1, 1)

    local drinkHint = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    drinkHint:SetPoint("TOPLEFT", drinkCheckbox, "BOTTOMLEFT", 26, -2)
    drinkHint:SetText("Shows a warning icon when total PvP drinks are below 20 (healers only, while queued).")
    drinkHint:SetTextColor(unpack(Palette.textMuted))

    -- Artwork comparison toggle. Shared rather than per-frame-set: it exists to
    -- judge the class art, and having to flip it in four places to compare
    -- would defeat that. Lives here with the other shared toggles rather than
    -- in a Battlegrounds tab, which owns per-frame-set settings only.
    local classArtCheckbox = MakeSharedCheckbox(
        frame, 40, -250, "Use Blizzard Class Icons",
        ns.GetClassArtLegacy, ns.SetClassArtLegacy, function()
            -- Test bars and the preview panes restyle immediately; live bars
            -- only re-read the art when they are rebuilt, and that is
            -- combat-locked.
            if ns.RefreshTestBars then ns.RefreshTestBars() end
            if ns.RefreshPreviews then ns.RefreshPreviews() end
            if not InCombatLockdown() and ns.RebuildBars then ns.RebuildBars() end
        end)
    classArtCheckbox.text:SetFontObject("GameFontNormalLarge")
    classArtCheckbox.text:SetTextColor(1, 1, 1)

    local classArtHint = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    classArtHint:SetPoint("TOPLEFT", classArtCheckbox, "BOTTOMLEFT", 26, -2)
    classArtHint:SetText("Swaps the redrawn class art for Blizzard's own class icons, square and unstyled.")
    classArtHint:SetTextColor(unpack(Palette.textMuted))

    local flaskCheckbox = MakeSharedCheckbox(
        frame, 40, -180, "Low Flask Warning  |  Click button to consume Flask of Honor",
        ns.GetFlaskWarningEnabled, ns.SetFlaskWarningEnabled, ns.UpdateFlaskWarning
    )
    flaskCheckbox.text:SetFontObject("GameFontNormalLarge")
    flaskCheckbox.text:SetTextColor(1, 1, 1)

    local flaskHint = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    flaskHint:SetPoint("TOPLEFT", flaskCheckbox, "BOTTOMLEFT", 26, -2)
    flaskHint:SetText("Shows a warning when Flask of Honor is missing or below 25 minutes (while queued).")
    flaskHint:SetTextColor(unpack(Palette.textMuted))

    -- ============================================
    -- Raid Frame Objective Icons
    -- ============================================
    -- Whole section sits 90px lower than it used to: the class-artwork toggle
    -- above it carries a hint line, and the two collided. Every control here
    -- and the preview frame moved together, so the section keeps its internal
    -- spacing — adjust them as a group if anything is inserted above again.
    local section2 = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    section2:SetPoint("TOPLEFT", 24, -360)
    section2:SetText("Raid Frame Objective Icons")
    DecorateTextHeader(section2)

    local section2Hint = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    section2Hint:SetPoint("TOPLEFT", 24, -390)
    section2Hint:SetText("Render the flag/orb icon on Blizzard raid & party frames for friendly objective carriers.")
    section2Hint:SetTextColor(unpack(Palette.textMuted))

    -- Forward-declared so controls below can route through a stable closure
    -- that resolves UpdatePreview at call time (it's defined further down,
    -- after the preview frames exist).
    local UpdatePreview
    local function RefreshAndPreview()
        if ns.UpdateRaidObjectiveIcons then ns.UpdateRaidObjectiveIcons() end
        if UpdatePreview then UpdatePreview() end
    end

    -- Left column controls
    local raidEnableCheckbox = MakeSharedCheckbox(
        frame, 40, -430, "Show Objective Icons On Raid Frames",
        ns.GetRaidObjectiveEnabled, ns.SetRaidObjectiveEnabled, RefreshAndPreview
    )
    raidEnableCheckbox.text:SetFontObject("GameFontNormalLarge")
    raidEnableCheckbox.text:SetTextColor(1, 1, 1)

    local raidIconSizeSlider = MakeSharedSlider(
        frame, 40, -480, "Icon Size", 16, 64, 1,
        ns.GetRaidObjectiveIconSize, ns.SetRaidObjectiveIconSize, RefreshAndPreview
    )

    local raidOffsetX = MakeSharedNumberBox(
        frame, 40, -545, "X Offset", -200, 200,
        ns.GetRaidObjectiveOffsetX, ns.SetRaidObjectiveOffsetX, RefreshAndPreview
    )
    local raidOffsetY = MakeSharedNumberBox(
        frame, 200, -545, "Y Offset", -200, 200,
        ns.GetRaidObjectiveOffsetY, ns.SetRaidObjectiveOffsetY, RefreshAndPreview
    )

    local raidStackSlider = MakeSharedSlider(
        frame, 40, -590, "Stack Text Size", 8, 24, 1,
        ns.GetRaidObjectiveStackTextSize, ns.SetRaidObjectiveStackTextSize, RefreshAndPreview
    )

    local raidAnchorDropdown = MakeSharedDropdown(
        frame, 40, -655, "Anchor Point", 140, ns.RAID_OBJECTIVE_ANCHORS,
        ns.GetRaidObjectiveAnchor, ns.SetRaidObjectiveAnchor, RefreshAndPreview
    )

    -- ============================================
    -- Preview frame (right column)
    -- ============================================
    -- Live-syncs to the real Blizzard raid frame size by polling
    -- CompactRaidGroup1Member1. Outside a group those frames are
    -- zero-sized, so we fall back to a default and show a hint.
    local DEFAULT_RF_W, DEFAULT_RF_H = 138, 63
    local mockW, mockH = DEFAULT_RF_W, DEFAULT_RF_H

    local previewContainer = CreateFrame("Frame", nil, frame)
    previewContainer:SetSize(360, 220)
    previewContainer:SetPoint("TOPLEFT", 580, -430)

    local previewHeader = previewContainer:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    previewHeader:SetPoint("TOPLEFT", 0, 0)
    previewHeader:SetText("Preview")
    previewHeader:SetTextColor(1, 1, 1)

    local mockRF = CreateFrame("Frame", nil, previewContainer, "BackdropTemplate")
    mockRF:SetSize(mockW, mockH)
    mockRF:SetPoint("TOPLEFT", previewContainer, "TOPLEFT", 0, -30)
    mockRF:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        edgeSize = 8,
    })
    mockRF:SetBackdropColor(0.1, 0.1, 0.1, 0.8)
    mockRF:SetBackdropBorderColor(0.3, 0.3, 0.3, 1)

    local healthBar = mockRF:CreateTexture(nil, "ARTWORK")
    healthBar:SetTexture("Interface\\RaidFrame\\Raid-Bar-Hp-Fill")
    healthBar:SetPoint("TOPLEFT", mockRF, "TOPLEFT", 1, -1)
    healthBar:SetPoint("BOTTOMLEFT", mockRF, "BOTTOMLEFT", 1, 1)
    healthBar:SetWidth(mockW * 0.85)
    healthBar:SetGradient("HORIZONTAL",
        CreateColor(0.2, 0.7, 0.2, 0.9),
        CreateColor(0.2, 0.5, 0.2, 0.6))

    local nameText = mockRF:CreateFontString(nil, "OVERLAY")
    nameText:SetFont(ns.LOCALE_FONT, 9, "OUTLINE")
    nameText:SetPoint("TOPLEFT", mockRF, "TOPLEFT", 4, -4)
    nameText:SetText("Carrier")
    nameText:SetTextColor(1, 1, 1, 0.9)

    local sizeHint = previewContainer:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    sizeHint:SetPoint("TOPLEFT", mockRF, "BOTTOMLEFT", 0, -8)
    sizeHint:SetWidth(360)
    sizeHint:SetJustifyH("LEFT")
    sizeHint:SetTextColor(unpack(Palette.textLabel))

    local function UpdateSizeHint(synced)
        if synced then
            sizeHint:SetText(string.format(
                "|cff66cc66Synced to your raid frame size (%dx%d).|r To re-sync, open Edit Mode or join a raid/group while outside an instance.",
                math.floor(mockW + 0.5), math.floor(mockH + 0.5)))
        else
            sizeHint:SetText(string.format(
                "|cffff8844Showing default size (%dx%d).|r To sync the preview to your real raid-frame size, open Edit Mode or join a raid/group while outside an instance — the preview live-syncs whenever a Blizzard raid frame is visible.",
                DEFAULT_RF_W, DEFAULT_RF_H))
        end
    end

    -- Mock carrier icon. Forced above mockRF's frame level so the orb sits
    -- on top of the raid-frame backdrop instead of being occluded by it.
    local mockCarrier = CreateFrame("Frame", nil, previewContainer)
    mockCarrier:SetFrameLevel(mockRF:GetFrameLevel() + 5)
    mockCarrier:SetSize(28, 28)
    mockCarrier.glow = mockCarrier:CreateTexture(nil, "BACKGROUND")
    mockCarrier.glow:SetPoint("CENTER")
    mockCarrier.glow:SetTexture("Interface\\AddOns\\BlitzFramesTargets\\Textures\\PingHalo.tga")
    mockCarrier.glow:SetBlendMode("ADD")
    mockCarrier.glow:SetVertexColor(0.30, 0.80, 1.00, 1) -- blue, matches the orb
    mockCarrier.glow:SetAlpha(0.6)
    mockCarrier.icon = mockCarrier:CreateTexture(nil, "ARTWORK")
    mockCarrier.icon:SetPoint("CENTER")
    -- OrbBlue, not ObjOrbBlue: the Obj* set is the PRE-REDRAW artwork, and this
    -- preview was still showing it long after the bars moved to the new icons.
    -- A preview that disagrees with the live element is worse than no preview.
    mockCarrier.icon:SetTexture("Interface\\AddOns\\BlitzFramesTargets\\Textures\\OrbBlue.tga") -- Kotmogu blue orb (sample, classification 7)

    local stackOverlay = CreateFrame("Frame", nil, mockCarrier)
    stackOverlay:SetAllPoints()
    stackOverlay:SetFrameLevel(mockCarrier:GetFrameLevel() + 2)
    mockCarrier.stackText = stackOverlay:CreateFontString(nil, "OVERLAY")
    mockCarrier.stackText:SetFont(ns.LOCALE_FONT, 12, "OUTLINE")
    mockCarrier.stackText:SetPoint("CENTER", mockCarrier, "CENTER", 0, 0)
    mockCarrier.stackText:SetTextColor(1, 1, 1, 1)
    mockCarrier.stackText:SetText("90")

    -- UNGUARDED ON PURPOSE, and this is the one that gives up something real.
    -- It reads a BLIZZARD frame we do not own, where a Secret Aspect is a
    -- legitimate outcome of the API rather than a mistake by us -- so the old
    -- issecretvalue check here was arguably honest. It still goes: this mock
    -- exists to be sized like the real raid frame, and a silent fall back to
    -- defaults means it just looks slightly wrong forever with nothing to
    -- explain why. If GetSize starts returning secrets, the `w > 1` comparison
    -- throws and names the line.
    local function GetBlizzRFSize()
        local f = _G.CompactRaidGroup1Member1 or _G.CompactRaidFrame1 or _G.CompactPartyFrameMember1
        if f then
            local w, h = f:GetSize()
            if w and h and w > 1 and h > 1 then return w, h end
        end
        return nil, nil
    end

    local function ResizeMockRF()
        local w, h = GetBlizzRFSize()
        local synced = w and h
        if synced then
            mockW, mockH = w, h
        else
            mockW, mockH = DEFAULT_RF_W, DEFAULT_RF_H
        end
        mockRF:SetSize(mockW, mockH)
        healthBar:SetWidth(mockW * 0.85)
        UpdateSizeHint(synced)
    end

    UpdatePreview = function()
        local enabled = ns.GetRaidObjectiveEnabled()
        if not enabled then
            mockCarrier:Hide()
            return
        end
        local size = ns.GetRaidObjectiveIconSize()
        local offX = ns.GetRaidObjectiveOffsetX()
        local offY = ns.GetRaidObjectiveOffsetY()
        local anchor = ns.GetRaidObjectiveAnchor() or "RIGHT"
        local stackSize = ns.GetRaidObjectiveStackTextSize()

        mockCarrier:SetSize(size, size)
        mockCarrier.icon:SetSize(size, size)
        mockCarrier.glow:SetSize(size * 1.5, size * 1.5)
        mockCarrier:ClearAllPoints()
        mockCarrier:SetPoint(anchor, mockRF, anchor, offX, offY)
        mockCarrier.stackText:SetFont(ns.LOCALE_FONT, stackSize, "OUTLINE")
        mockCarrier:Show()
    end

    -- Periodic resize sync (~4Hz)
    local lastSync = 0
    previewContainer:SetScript("OnUpdate", function(self, elapsed)
        lastSync = lastSync + elapsed
        if lastSync < 0.25 then return end
        lastSync = 0
        local w, h = GetBlizzRFSize()
        local desiredW = (w and h) and w or DEFAULT_RF_W
        local desiredH = (w and h) and h or DEFAULT_RF_H
        if desiredW ~= mockW or desiredH ~= mockH then
            ResizeMockRF()
            UpdatePreview()
        end
    end)

    -- Grey state: the objective-icon options only apply while the master
    -- toggle is on
    local function UpdateRaidGreyState()
        local en = raidEnableCheckbox:GetChecked()
        UI.SetSliderGreyState(raidIconSizeSlider, en)
        UI.SetNumberBoxGreyState(raidOffsetX, en)
        UI.SetNumberBoxGreyState(raidOffsetY, en)
        UI.SetSliderGreyState(raidStackSlider, en)
        UI.SetDropdownGreyState(raidAnchorDropdown, en)
        previewContainer:SetAlpha(en and 1 or UI.GREY_ALPHA)
    end
    raidEnableCheckbox:HookScript("OnClick", UpdateRaidGreyState)

    ResizeMockRF()
    UpdatePreview()
    UpdateRaidGreyState()

    -- OnShow refresher
    return function()
        drinkCheckbox:SetChecked(ns.GetDrinkWarningEnabled())
        flaskCheckbox:SetChecked(ns.GetFlaskWarningEnabled())
        classArtCheckbox:SetChecked(ns.GetClassArtLegacy())
        raidEnableCheckbox:SetChecked(ns.GetRaidObjectiveEnabled())
        raidIconSizeSlider:SetValue(ns.GetRaidObjectiveIconSize())
        raidOffsetX:SetText(tostring(ns.GetRaidObjectiveOffsetX()))
        raidOffsetY:SetText(tostring(ns.GetRaidObjectiveOffsetY()))
        raidStackSlider:SetValue(ns.GetRaidObjectiveStackTextSize())
        ResizeMockRF()
        UpdatePreview()
        UpdateRaidGreyState()
    end
end
