local _, namespace = ...

local WTUI = namespace.WildThingsUI
local Theme = namespace.Theme

local DEFAULT_WIDTH = 150
local DEFAULT_HEIGHT = Theme.H_DROPDOWN

-- The open menu is Blizzard's own popup (WowStyle1DropdownTemplate -> MenuStyle1Mixin), which
-- paints its background from the "common-dropdown-bg" atlas -- Blizzard's default gold-bordered
-- chrome, clashing with the closed control's WTUI skin. Inherits MenuStyle1Mixin's inset/child
-- padding untouched; only Generate() (the background draw) is replaced with a WTUI-colored
-- rounded fill, same corner-quadrant technique as WTUI:AddRoundedFill.
--
-- Can't reuse WTUI:AddRoundedFill/frame:CreateTexture() here: the popup is a Compositor-pooled
-- proxy frame (Blizzard_Menu/Compositor.lua) that hard-disallows CreateTexture (assertsafe
-- error) precisely so pooled-frame reuse across the whole UI stays leak-free. self:AttachTexture()
-- is the Compositor-provided replacement Blizzard's own style mixins use for the same reason.
local MENU_CORNER_TEXTURE = [[Interface\CHARACTERFRAME\TempPortraitAlphaMaskSmall]]
local MENU_CORNER_TEXCOORDS = {
    TopLeft     = {0, 0.5, 0, 0.5},
    TopRight    = {0.5, 1, 0, 0.5},
    BottomLeft  = {0, 0.5, 0.5, 1},
    BottomRight = {0.5, 1, 0.5, 1},
}

local WTUIMenuStyleMixin = CreateFromMixins(MenuStyle1Mixin)

function WTUIMenuStyleMixin:Generate()
    local radius = Theme.RADIUS_MD
    local bg = Theme.PANEL_BG
    local corner = {}

    for cornerName, texcoord in pairs(MENU_CORNER_TEXCOORDS) do
        local tex = self:AttachTexture()
        tex:SetTexture(MENU_CORNER_TEXTURE, "CLAMP", "CLAMP", "TRILINEAR")
        tex:SetTexCoord(unpack(texcoord))
        tex:SetVertexColor(bg.r, bg.g, bg.b, bg.a)
        tex:SetSize(radius, radius)
        tex:SetPoint(cornerName, self, cornerName, 0, 0)
        corner[cornerName] = tex
    end

    local topEdge = self:AttachTexture()
    topEdge:SetColorTexture(bg.r, bg.g, bg.b, bg.a)
    topEdge:SetPoint("TOPLEFT", corner.TopLeft, "TOPRIGHT")
    topEdge:SetPoint("BOTTOMLEFT", corner.TopLeft, "BOTTOMRIGHT")
    topEdge:SetPoint("TOPRIGHT", corner.TopRight, "TOPLEFT")
    topEdge:SetPoint("BOTTOMRIGHT", corner.TopRight, "BOTTOMLEFT")

    local bottomEdge = self:AttachTexture()
    bottomEdge:SetColorTexture(bg.r, bg.g, bg.b, bg.a)
    bottomEdge:SetPoint("TOPLEFT", corner.BottomLeft, "TOPRIGHT")
    bottomEdge:SetPoint("BOTTOMLEFT", corner.BottomLeft, "BOTTOMRIGHT")
    bottomEdge:SetPoint("TOPRIGHT", corner.BottomRight, "TOPLEFT")
    bottomEdge:SetPoint("BOTTOMRIGHT", corner.BottomRight, "BOTTOMLEFT")

    local centerBlock = self:AttachTexture()
    centerBlock:SetColorTexture(bg.r, bg.g, bg.b, bg.a)
    centerBlock:SetPoint("TOPLEFT", corner.TopLeft, "BOTTOMLEFT")
    centerBlock:SetPoint("BOTTOMLEFT", corner.BottomLeft, "TOPLEFT")
    centerBlock:SetPoint("TOPRIGHT", corner.TopRight, "BOTTOMRIGHT")
    centerBlock:SetPoint("BOTTOMRIGHT", corner.BottomRight, "TOPRIGHT")
end

function namespace:CreateDropdown(parent, title, _infoTable, OnValueChanged, initialValue)
    local infoTable, i, selectedIndices
    local dropdown = CreateFrame("DropdownButton", nil, parent, "WowStyle1DropdownTemplate")

    dropdown:SetSize(DEFAULT_WIDTH, DEFAULT_HEIGHT)
    -- Must be set before the first SetValue/SetDefaultValue call below (both trigger
    -- GenerateMenu -> CreateDefaultRootMenuDescription, which reads this per-instance
    -- so only OUR dropdowns are affected, not every Blizzard menu in the game).
    dropdown.menuMixin = WTUIMenuStyleMixin

    -- Tooltip purposes
    dropdown.OnEnter = function() end
    dropdown.OnLeave = function() end

    dropdown:SetScript("OnEnter", function(_self) _self.OnEnter() end)
    dropdown:SetScript("OnLeave", function(_self) _self.OnLeave() end)

    -- Title
    local dropdownTitle = dropdown:CreateFontString()

    dropdownTitle:SetFontObject(WildThingsFont13)
    dropdownTitle:SetPoint("BOTTOMLEFT", dropdown, "TOPLEFT")
    dropdownTitle:SetText(title or "")
    dropdownTitle:SetTextColor(Theme.TEXT.r, Theme.TEXT.g, Theme.TEXT.b, Theme.TEXT.a)

    local function IsSelected(index)
        if not selectedIndices then return end

        return selectedIndices[index]
    end

    local function SetSelected(indices, values, text)
        selectedIndices = indices

        dropdown:OverrideText(text)

        if not dropdown._suppressOnValueChanged then
            OnValueChanged(unpack(values))
        end
    end

    local function MakeSubmenu(parentButton, subInfoTable, values, parentSelectionIndices)
        local selectionIndices = CopyTable(parentSelectionIndices)

        i = i + 1
        subInfoTable.selectionIndex = i
        selectionIndices[i] = true

        local text = subInfoTable.text
        local icon = subInfoTable.icon
        local iconString = icon and namespace:IconString(icon)

        if iconString then
            text = string.format("%s %s", iconString, text)
        end

        local button = parentButton:CreateRadio(
            subInfoTable.text,
            IsSelected,
            subInfoTable.children and function() end or
            function()
                selectedIndices = selectionIndices

                SetSelected(selectionIndices, values, text)
            end,
            i
        )

        button:AddInitializer(
            function(_button)
                -- Text
                local fontString = _button.fontString

                if IsSelected(subInfoTable.selectionIndex) then
                    fontString:SetTextColor(Theme.ACCENT.r, Theme.ACCENT.g, Theme.ACCENT.b, Theme.ACCENT.a)
                end

                -- Icon
                local iconTexture = _button:AttachTexture()

                iconTexture:SetTexCoord(0.08, 0.92, 0.08, 0.92)
                iconTexture:SetSize(18, 18)
                iconTexture:SetPoint("RIGHT", _button, "RIGHT", subInfoTable.children and -20 or 0, 0)

                if subInfoTable.icon then
                    if C_Texture.GetAtlasInfo(subInfoTable.icon) then
                        iconTexture:SetAtlas(subInfoTable.icon)
                    else
                        iconTexture:SetTexture(subInfoTable.icon)
                    end
                end

                iconTexture:SetSnapToPixelGrid(false)
                iconTexture:SetTexelSnappingBias(0)

                -- Calculate size
                local arrowWidth = subInfoTable.children and 20 or 0
                local padding = 32

                local buttonWidth = padding + arrowWidth + fontString:GetUnboundedStringWidth() + iconTexture:GetWidth()

                return buttonWidth, 20
            end
        )

        parentButton:SetScrollMode(20 * 24);

        if not subInfoTable.children then return end

        for index, childInfoTable in ipairs(subInfoTable.children) do
            local value = childInfoTable.value or index
            local childValues = CopyTable(values)

            table.insert(childValues, value)

            MakeSubmenu(button, childInfoTable, childValues, selectionIndices)
        end
    end

    function dropdown:SetValue(infoTableIndices)
        if not next(infoTable) then return end

        local values = {}
        local node = infoTable
        local newSelectionIndices = {}
        local text

        for _, index in ipairs(infoTableIndices) do
            if not node then break end
            if not node[index] then break end

            table.insert(values, node[index].value or index)

            text = node[index].text

            local icon = node[index].icon
            local iconString = icon and namespace:IconString(icon)

            if iconString then
                text = string.format("%s %s", iconString, text)
            end

            newSelectionIndices[node[index].selectionIndex] = true
            node = node[index].children
        end

        if not next(newSelectionIndices) then return end

        SetSelected(newSelectionIndices, values, text)

        dropdown:GenerateMenu()
    end

    -- Suppress-guarded programmatic set (TOOLKIT-F1 / Plan 49-02): brackets SetValue
    -- with _suppressOnValueChanged so the caller's OnValueChanged never fires during
    -- Refresh. Follows the Slider.lua bracket pattern (instance-scoped flag, not global).
    function dropdown:SetValueSilent(indices)
        dropdown._suppressOnValueChanged = true
        dropdown:SetValue(indices)
        dropdown._suppressOnValueChanged = false
    end

    -- Effectively the same as SetValue, except it keeps choosing index 1 until it reaches a leaf node
    function dropdown:SetDefaultValue()
		if not next(infoTable) then return end

        local values = {}
        local node = infoTable
        local newSelectionIndices = {}
        local text

        while node and node[1] do
            table.insert(values, node[1].value or 1)

            text = node[1].text

            local icon = node[1].icon
            local iconString = icon and namespace:IconString(icon)

            if iconString then
                text = string.format("%s %s", iconString, text)
            end

            newSelectionIndices[node[1].selectionIndex] = true
            node = node[1].children
        end

        if not next(newSelectionIndices) then return end

        SetSelected(newSelectionIndices, values, text)

        dropdown:GenerateMenu()
	end

    function dropdown:SetInfoTable(__infoTable)
        infoTable = __infoTable

        -- Clear stale selection so a rebuilt menu starts unselected: without this, a
        -- repeated-rebuild caller (SpecCooldownPicker's add/remove) would carry a stale
        -- selectedIndices entry, painting a since-shifted, unrelated row with the accent
        -- "selected" highlight. Safe for the single-build callers (InviteScreen): the
        -- constructor re-selects via SetValue/SetDefaultValue immediately after this call.
        selectedIndices = nil

        dropdown:SetupMenu(
            function(_, rootNode)
                i = 0

                for index, childInfoTable in ipairs(infoTable) do
                    local value = childInfoTable.value or index

                    MakeSubmenu(rootNode, childInfoTable, {value}, {})
                end
            end
        )
    end

    dropdown:SetInfoTable(_infoTable)

    if initialValue then
        dropdown:SetValue(initialValue)
	else
        dropdown:SetDefaultValue()
    end

    -- Skinning. The body carries no border of its own (fills only, carried-forward Phase
    -- 27/28 lock) -- it used to build a square one and immediately hide it. LRArrowFrame's
    -- border below IS drawn, and keeps its hover color-swap affordance.

    dropdown.Background:Hide()
    dropdown.Arrow:Hide()

    -- Background: rounds the base's LEFT 2 corners only (AddRoundedFill corner-quadrant
    -- technique -- the base carries no full-rect overlay to clip, so the mask primitive
    -- isn't needed here). The right corners are drawn square by design; LRArrowFrame
    -- (below) rounds its own RIGHT 2 corners and, as a higher frame-level child, draws
    -- over this square remainder (Pitfall 4).
    WTUI:AddRoundedFill(dropdown, Theme.RADIUS_SM, Theme.DD_BG, {TopLeft = true, BottomLeft = true})

    -- Arrow frame (chrome box housing the chevron on the right): rounds its own RIGHT 2
    -- corners independently, at the same DD_BG tone, so the split reads as one seamless
    -- control -- mirroring Phase 28's shell titlebar-vs-window selective-corner split.
    dropdown.LRArrowFrame = CreateFrame("Frame", nil, dropdown)
    dropdown.LRArrowFrame:SetSize(DEFAULT_HEIGHT, DEFAULT_HEIGHT)
    dropdown.LRArrowFrame:SetPoint("RIGHT")

    WTUI:AddRoundedFill(dropdown.LRArrowFrame, Theme.RADIUS_SM, Theme.DD_BG, {TopRight = true, BottomRight = true})

    -- Same corner allowlist as the fill above, so the seam against the body stays flat.
    WTUI:AddRoundedBorder(dropdown.LRArrowFrame, Theme.RADIUS_SM, 1, {TopRight = true, BottomRight = true})
    dropdown.LRArrowFrame:SetBorderColor(Theme.BORDER_STRONG.r, Theme.BORDER_STRONG.g, Theme.BORDER_STRONG.b, Theme.BORDER_STRONG.a)

    dropdown:ClearHighlightTexture()
    dropdown:ClearDisabledTexture()

    -- Chevron (replaces the dead-path legacy arrow textures, which pointed at a
    -- WildThingsUI\Media\Textures directory that does not exist in this repo)
    dropdown.chevron = dropdown:CreateTexture(nil, "ARTWORK")
    dropdown.chevron:SetTexture(Theme.GetIconPath("chevron-down"))
    dropdown.chevron:SetSize(Theme.ICON_SM, Theme.ICON_SM)
    dropdown.chevron:SetPoint("RIGHT", dropdown.LRArrowFrame, "RIGHT", -4, 0)
    dropdown.chevron:SetSnapToPixelGrid(false)
    dropdown.chevron:SetTexelSnappingBias(0)

    -- Chevron rotation: shared motion helper (WIDGET-12) — single AnimationGroup played
    -- forward on open / reversed via Play(true) on close (Rotation is a relative delta,
    -- not an absolute angle — pairing forward/reverse on the same group guarantees the
    -- chevron always returns exactly to 0 degrees regardless of toggle count; see
    -- 09-RESEARCH.md Pitfall 5).
    dropdown.chevronRotate = WTUI:AddToggleAnimation(dropdown, dropdown.chevron, "Rotation", {to = 180, duration = Theme.DUR_IN})

    dropdown:RegisterCallback(DropdownButtonMixin.Event.OnMenuOpen, function()
        dropdown.chevronRotate:Play()
    end)
    dropdown:RegisterCallback(DropdownButtonMixin.Event.OnMenuClose, function()
        dropdown.chevronRotate:Play(true)
    end)

    -- Corners allowlist: LRArrowFrame is the RIGHT half of a split control, so only its outer
    -- corners round -- its left edge must stay flat against the base body (matching the
    -- SetRoundedFillColor corners at :244). Without this the hover outline would bow away
    -- from the seam on both left corners.
    WTUI:AddHoverHighlight(dropdown, dropdown.LRArrowFrame, nil, nil, nil, nil, nil,
        Theme.RADIUS_SM, {TopRight = true, BottomRight = true})

    -- Hover fill: brightens the base's rounded fill in place (same-texture recolor, mirrors
    -- Toggle's ApplyTrackColor pattern) -- the base border is suppressed above
    -- (SetBorderShown(false)), so a plain SetBorderColor no-ops on hidden textures and the
    -- base body had no other hover affordance (CR-01). Uses HookScript (not the
    -- dropdown.OnEnter/OnLeave stub fields above, which callers such as WTUI:AddTooltip are
    -- free to override) so this stays additive. LRArrowFrame's own border is NOT suppressed
    -- (see comment above) and keeps its existing, still-working hover color-swap unchanged.
    local DD_BG_HOVER_BRIGHTEN = 0.06
    dropdown:HookScript("OnEnter", function()
        dropdown:SetRoundedFillColor(
            math.min(Theme.DD_BG.r + DD_BG_HOVER_BRIGHTEN, 1),
            math.min(Theme.DD_BG.g + DD_BG_HOVER_BRIGHTEN, 1),
            math.min(Theme.DD_BG.b + DD_BG_HOVER_BRIGHTEN, 1),
            Theme.DD_BG.a
        )
        dropdown.LRArrowFrame:SetBorderColor(Theme.BORDER_HOVER.r, Theme.BORDER_HOVER.g, Theme.BORDER_HOVER.b, Theme.BORDER_HOVER.a)
    end)
    dropdown:HookScript("OnLeave", function()
        dropdown:SetRoundedFillColor(Theme.DD_BG.r, Theme.DD_BG.g, Theme.DD_BG.b, Theme.DD_BG.a)
        dropdown.LRArrowFrame:SetBorderColor(Theme.BORDER_STRONG.r, Theme.BORDER_STRONG.g, Theme.BORDER_STRONG.b, Theme.BORDER_STRONG.a)
    end)

    -- Text
    dropdown.Text:AdjustPointsOffset(0, -1)
    dropdown.Text:SetFontObject(WildThingsFont13)
    dropdown.Text:SetDrawLayer("ARTWORK")

    dropdown.Text:ClearAllPoints()
    dropdown.Text:SetPoint("LEFT", dropdown, "LEFT", 6, 0)
    dropdown.Text:SetPoint("RIGHT", dropdown, "RIGHT", -DEFAULT_HEIGHT - 6, 0)
    dropdown.Text:SetJustifyH("RIGHT")

    return dropdown
end
