local _, addonTable = ...
local core = addonTable
local TRANSLATE = core.TRANSLATE or function(msg) return msg end

core.FindGroupTab = core.FindGroupTab or {}
local FindGroupTab = core.FindGroupTab
local FindGroupList = core.FindGroupList or {}
local FindGroupSearch = core.FindGroupSearch or {}
local MeetingStoneConstants = core.MeetingStoneConstants or {}
local MeetingStoneForm = core.MeetingStoneForm or {}

local TrySetAtlas = MeetingStoneForm.TrySetAtlas or function() return false end
local CreateLabel = MeetingStoneForm.CreateLabel
local CreateInput = MeetingStoneForm.CreateInput
local StyleInputBox = MeetingStoneForm.StyleInputBox
local CreateSystemButton = MeetingStoneForm.CreateSystemButton
local RefreshDropdown = MeetingStoneForm.RefreshDropdown or function(dropdown)
    if dropdown and dropdown.GenerateMenu then
        dropdown:GenerateMenu()
    end
end
local EnableSelectAllAfterMouseLeave = MeetingStoneForm.EnableSelectAllAfterMouseLeave or function() end

local PANEL_INSET_LEFT = MeetingStoneConstants.MEETINGSTONE_PANEL_INSET_LEFT or 5
local PANEL_INSET_TOP = MeetingStoneConstants.MEETINGSTONE_PANEL_INSET_TOP or 0
local PANEL_INSET_RIGHT = MeetingStoneConstants.MEETINGSTONE_PANEL_INSET_RIGHT or 4
local PANEL_INSET_BOTTOM = MeetingStoneConstants.MEETINGSTONE_PANEL_INSET_BOTTOM or 8
local LEFT_PANEL_WIDTH = MeetingStoneConstants.MEETINGSTONE_LEFT_PANEL_WIDTH or 180

local LEFT_PANEL_BACKGROUND_ALPHA = MeetingStoneConstants.MEETINGSTONE_LEFT_PANEL_BACKGROUND_ALPHA or 0.6
local PANEL_DIVIDER_WIDTH = MeetingStoneConstants.MEETINGSTONE_PANEL_DIVIDER_WIDTH or 3
local PANEL_DIVIDER_OFFSET_X = MeetingStoneConstants.MEETINGSTONE_PANEL_DIVIDER_OFFSET_X or -3
local PANEL_DIVIDER_TOP_OFFSET = MeetingStoneConstants.MEETINGSTONE_PANEL_DIVIDER_TOP_OFFSET or 4
local PANEL_DIVIDER_BOTTOM_OFFSET = MeetingStoneConstants.MEETINGSTONE_PANEL_DIVIDER_BOTTOM_OFFSET or 2
local PANEL_DIVIDER_COLOR = MeetingStoneConstants.MEETINGSTONE_PANEL_DIVIDER_COLOR or { 0.38, 0.34, 0.24, 0.9 }
local PANEL_DIVIDER_HIGHLIGHT_COLOR = MeetingStoneConstants.MEETINGSTONE_PANEL_DIVIDER_HIGHLIGHT_COLOR or { 0.62, 0.55, 0.38, 0.3 }
local PANEL_DIVIDER_SHADOW_COLOR = MeetingStoneConstants.MEETINGSTONE_PANEL_DIVIDER_SHADOW_COLOR or { 0.08, 0.07, 0.05, 0.9 }
local HEADER_TOP_OFFSET = MeetingStoneConstants.MEETINGSTONE_HEADER_TOP_OFFSET or -20
local HEADER_BOTTOM_OFFSET = MeetingStoneConstants.MEETINGSTONE_HEADER_BOTTOM_OFFSET or -46
local HEADER_CONTENT_INSET_X = MeetingStoneConstants.MEETINGSTONE_HEADER_CONTENT_INSET_X or 4
local HEADER_CONTENT_OFFSET_Y = MeetingStoneConstants.MEETINGSTONE_HEADER_CONTENT_OFFSET_Y or 4
local HEADER_TEXT_SIZE = MeetingStoneConstants.MEETINGSTONE_HEADER_TEXT_SIZE or 14
local HEADER_TEXT_COLOR = MeetingStoneConstants.MEETINGSTONE_HEADER_TEXT_COLOR or { 1, 0.82, 0, 1 }
local FORM_INSET_X = MeetingStoneConstants.MEETINGSTONE_FORM_INSET_X or 15
local FORM_TOP_OFFSET = MeetingStoneConstants.MEETINGSTONE_FORM_TOP_OFFSET or -52
local FORM_LABEL_HEIGHT = MeetingStoneConstants.MEETINGSTONE_FORM_LABEL_HEIGHT or 15
local FORM_LABEL_TO_CONTROL_GAP = MeetingStoneConstants.MEETINGSTONE_FORM_LABEL_TO_CONTROL_GAP or 8
local FORM_CONTROL_WIDTH = MeetingStoneConstants.MEETINGSTONE_FORM_CONTROL_WIDTH or 150
local FORM_CONTROL_HEIGHT = MeetingStoneConstants.MEETINGSTONE_FORM_CONTROL_HEIGHT or 26
local FORM_ROW_GAP = MeetingStoneConstants.MEETINGSTONE_FORM_ROW_GAP or 8
local FORM_INLINE_LABEL_WIDTH = MeetingStoneConstants.MEETINGSTONE_FORM_INLINE_LABEL_WIDTH or 74
local FORM_INLINE_INPUT_WIDTH = MeetingStoneConstants.MEETINGSTONE_FORM_INLINE_INPUT_WIDTH or 70
local FORM_BUTTON_WIDTH = MeetingStoneConstants.MEETINGSTONE_FORM_BUTTON_WIDTH or 72
local FORM_BUTTON_HEIGHT = MeetingStoneConstants.MEETINGSTONE_FORM_BUTTON_HEIGHT or 26
local FORM_BUTTON_GAP = MeetingStoneConstants.MEETINGSTONE_FORM_BUTTON_GAP or 7
local FORM_BUTTON_BOTTOM_OFFSET = MeetingStoneConstants.MEETINGSTONE_FORM_BUTTON_BOTTOM_OFFSET or 8
local FORM_LABEL_TEXT_SIZE = MeetingStoneConstants.MEETINGSTONE_FORM_LABEL_TEXT_SIZE or 13
local FORM_LABEL_TEXT_COLOR = MeetingStoneConstants.MEETINGSTONE_FORM_LABEL_TEXT_COLOR or { 1, 0.82, 0, 1 }
local FORM_INPUT_TEXT_SIZE = 12
local FORM_INPUT_TEXT_COLOR = { 1, 0.96, 0.86, 1 }
local FORM_PLACEHOLDER_TEXT_COLOR = { 0.55, 0.55, 0.55, 1 }
local FORM_KEYWORD_BOTTOM_OFFSET = FORM_BUTTON_BOTTOM_OFFSET + FORM_BUTTON_HEIGHT + 12
local FIND_GROUP_SEARCH_TEXT_LEFT_INSET = 0
local FIND_GROUP_SEARCH_TEXT_WIDTH = FORM_BUTTON_WIDTH * 2 + FORM_BUTTON_GAP
local FIND_GROUP_SEARCH_TEXT_ICON_INSET = 8
local FIND_GROUP_SEARCH_TEXT_ICON_SIZE = 14
local FIND_GROUP_SEARCH_TEXT_TEXT_INSET_LEFT = 27
local FIND_GROUP_SEARCH_TEXT_TEXT_INSET_RIGHT = 24
local FIND_GROUP_SEARCH_TEXT_CLEAR_INSET = 6
local ACTION_BUTTON_TEXT_OFFSET_Y = MeetingStoneConstants.MEETINGSTONE_ACTION_BUTTON_TEXT_OFFSET_Y or 1
local LEFT_BOTTOM_GRADIENT_TOP_OFFSET = MeetingStoneConstants.MEETINGSTONE_LEFT_BOTTOM_GRADIENT_TOP_OFFSET or 32

local FIND_GROUP_LABEL_TO_CONTROL_GAP = math.max(4, FORM_LABEL_TO_CONTROL_GAP - 2)
local FIND_GROUP_ROW_GAP = math.max(4, FORM_ROW_GAP - 2)
local FIND_GROUP_CHECK_ROW_HEIGHT = math.max(22, FORM_CONTROL_HEIGHT - 2)
local FIND_GROUP_SECTION_GAP = 6
local FIND_GROUP_CHECK_COLUMN_GAP = 8
local FIND_GROUP_CHECK_TEXT_EXTRA_WIDTH = 6
local FIND_GROUP_CHECK_TEXT_SIZE = FORM_LABEL_TEXT_SIZE

local FIND_GROUP_MIN_AVAILABLE_SLOTS = 1
local FIND_GROUP_MAX_AVAILABLE_SLOTS = 4
local FIND_GROUP_SLOT_SPINNER_INPUT_WIDTH = FORM_INLINE_INPUT_WIDTH + 16
local FIND_GROUP_SLOT_SPINNER_INPUT_HEIGHT = FORM_CONTROL_HEIGHT
local FIND_GROUP_SLOT_SPINNER_BUTTON_WIDTH = 23
local FIND_GROUP_SLOT_SPINNER_BUTTON_HEIGHT = 22
local FIND_GROUP_SLOT_SPINNER_BUTTON_INSET = 6
FindGroupTab.SLOT_SPINNER_BUTTON_EDGE_OFFSET = 0

local function CreatePlainLabel(parent, text, size, justify)
    local label = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    label:SetText(text or "")
    label:SetJustifyH(justify or "LEFT")
    label:SetJustifyV("MIDDLE")
    label:SetWordWrap(false)
    if core.ApplyFont then
        core.ApplyFont(label, size or 12, "")
    end
    return label
end

local function ApplyFindGroupFontStringStyle(fontString, size, color, flags)
    if not fontString then
        return nil
    end
    color = color or FORM_LABEL_TEXT_COLOR
    if fontString.SetTextColor then
        fontString:SetTextColor(color[1] or 1, color[2] or 0.82, color[3] or 0, color[4] or 1)
    end
    if fontString.SetJustifyV then
        fontString:SetJustifyV("MIDDLE")
    end
    if fontString.SetWordWrap then
        fontString:SetWordWrap(false)
    end
    if core.ApplyFont then
        core.ApplyFont(fontString, size or FORM_LABEL_TEXT_SIZE, flags or "")
    end
    return fontString
end

local function CreateFindGroupLabel(parent, text)
    local label
    if CreateLabel then
        label = CreateLabel(parent, text)
    else
        label = CreatePlainLabel(parent, text, FORM_LABEL_TEXT_SIZE, "LEFT")
    end
    label:SetJustifyH("LEFT")
    return ApplyFindGroupFontStringStyle(label, FORM_LABEL_TEXT_SIZE, FORM_LABEL_TEXT_COLOR)
end

local function CreateFindGroupInput(parent, width, height, numeric)
    if CreateInput then
        return CreateInput(parent, width, height, numeric)
    end
    local editBox = CreateFrame("EditBox", nil, parent, "InputBoxTemplate")
    editBox:SetSize(width, height or 24)
    editBox:SetAutoFocus(false)
    if numeric then
        editBox:SetNumeric(true)
    end
    ApplyFindGroupFontStringStyle(editBox, FORM_INPUT_TEXT_SIZE, FORM_INPUT_TEXT_COLOR)
    return editBox
end

local function AddFindGroupTooltip(frame, titleKey, descriptionKey)
    if not frame then
        return
    end

    frame:HookScript("OnEnter", function(self)
        if not GameTooltip then
            return
        end

        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:ClearLines()
        GameTooltip:AddLine(TRANSLATE(titleKey), 1, 0.82, 0, true)
        GameTooltip:AddLine(TRANSLATE(descriptionKey), 0.85, 0.85, 0.85, true)
        GameTooltip:Show()
    end)
    frame:HookScript("OnLeave", function()
        if GameTooltip then
            GameTooltip:Hide()
        end
    end)
end

local function CreateFindGroupStepperButton(parent, direction)
    local button = CreateFrame("Button", nil, parent)
    button:SetSize(FIND_GROUP_SLOT_SPINNER_BUTTON_WIDTH, FIND_GROUP_SLOT_SPINNER_BUTTON_HEIGHT)
    if direction == "left" then
        button:SetNormalTexture("Interface\\Buttons\\UI-SpellbookIcon-PrevPage-Up")
        button:SetPushedTexture("Interface\\Buttons\\UI-SpellbookIcon-PrevPage-Down")
        button:SetDisabledTexture("Interface\\Buttons\\UI-SpellbookIcon-PrevPage-Disabled")
    else
        button:SetNormalTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Up")
        button:SetPushedTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Down")
        button:SetDisabledTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Disabled")
    end
    button:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight", "ADD")
    button:RegisterForClicks("LeftButtonUp")
    return button
end

local function CreateFindGroupNumericSpinner(parent, minValue, maxValue)
    local spinner = CreateFrame("Frame", nil, parent)
    spinner.mkFindGroupUsesNativeSpinner = true
    spinner.mkFindGroupMinValue = minValue
    spinner.mkFindGroupMaxValue = maxValue
    spinner:SetSize(FIND_GROUP_SLOT_SPINNER_INPUT_WIDTH, FIND_GROUP_SLOT_SPINNER_INPUT_HEIGHT)

    local inputWidth = math.max(
        1,
        FIND_GROUP_SLOT_SPINNER_INPUT_WIDTH - (FIND_GROUP_SLOT_SPINNER_BUTTON_WIDTH * 2)
    )
    local editBox = CreateFrame("EditBox", nil, spinner)
    editBox:SetSize(inputWidth, FIND_GROUP_SLOT_SPINNER_INPUT_HEIGHT)
    editBox:SetPoint("CENTER", spinner, "CENTER", 0, 0)
    editBox:EnableMouse(true)
    editBox:SetAutoFocus(false)
    editBox:SetNumeric(true)
    editBox:SetMaxLetters(1)
    editBox:SetJustifyH("CENTER")
    editBox:SetTextColor(FORM_INPUT_TEXT_COLOR[1], FORM_INPUT_TEXT_COLOR[2], FORM_INPUT_TEXT_COLOR[3], FORM_INPUT_TEXT_COLOR[4])
    editBox:SetShadowColor(0, 0, 0, 0.8)
    editBox:SetShadowOffset(1, -1)
    if core.ApplyFont then
        core.ApplyFont(editBox, FORM_INPUT_TEXT_SIZE, "")
    else
        editBox:SetFontObject("GameFontHighlight")
    end

    local decrementButton = CreateFindGroupStepperButton(spinner, "left")
    decrementButton:SetPoint(
        "LEFT",
        spinner,
        "LEFT",
        FIND_GROUP_SLOT_SPINNER_BUTTON_INSET - FindGroupTab.SLOT_SPINNER_BUTTON_EDGE_OFFSET,
        0
    )
    local incrementButton = CreateFindGroupStepperButton(spinner, "right")
    incrementButton:SetPoint(
        "RIGHT",
        spinner,
        "RIGHT",
        -FIND_GROUP_SLOT_SPINNER_BUTTON_INSET + FindGroupTab.SLOT_SPINNER_BUTTON_EDGE_OFFSET,
        0
    )

    spinner.mkFindGroupEditBox = editBox
    spinner.DecrementButton = decrementButton
    spinner.IncrementButton = incrementButton

    local function clampValue(value)
        local numberValue = tonumber(value) or minValue or 0
        numberValue = math.floor(numberValue)
        return math.max(
            tonumber(spinner.mkFindGroupMinValue) or -math.huge,
            math.min(tonumber(spinner.mkFindGroupMaxValue) or math.huge, numberValue)
        )
    end

    function spinner:SetValue(value)
        local newValue = clampValue(value)
        local changed = newValue ~= self.currentValue
        self.currentValue = newValue
        self.mkFindGroupEditBox.mkFindGroupProgrammaticSetValue = true
        if self.mkFindGroupEditBox:GetText() ~= tostring(newValue) then
            self.mkFindGroupEditBox:SetText(tostring(newValue))
        end
        self.mkFindGroupEditBox.mkFindGroupProgrammaticSetValue = nil
        if changed and self.onValueChangedCallback and not self.mkFindGroupProgrammaticSetValue then
            self.onValueChangedCallback(self, newValue)
        end
    end

    function spinner:SetNumber(value)
        self:SetValue(value)
    end

    function spinner:GetNumber()
        return self.currentValue or clampValue(self.mkFindGroupEditBox:GetNumber())
    end

    function spinner:GetText()
        return self.mkFindGroupEditBox:GetText()
    end

    function spinner:SetText(text)
        self:SetValue(text)
    end

    function spinner:SetOnValueChangedCallback(callback)
        self.onValueChangedCallback = callback
    end

    editBox:SetScript("OnTextChanged", function(input)
        if input.mkFindGroupProgrammaticSetValue then
            return
        end
        spinner:SetValue(input:GetNumber())
    end)
    editBox:SetScript("OnEnterPressed", function(input)
        input:ClearFocus()
    end)
    editBox:SetScript("OnEscapePressed", function(input)
        input:ClearFocus()
    end)
    decrementButton:SetScript("OnClick", function()
        spinner:SetValue(spinner:GetNumber() - 1)
    end)
    incrementButton:SetScript("OnClick", function()
        spinner:SetValue(spinner:GetNumber() + 1)
    end)
    spinner:EnableMouseWheel(true)
    spinner:SetScript("OnMouseWheel", function(self, delta)
        if delta > 0 then
            self:SetValue(self:GetNumber() + 1)
        elseif delta < 0 then
            self:SetValue(self:GetNumber() - 1)
        end
    end)

    spinner:SetValue(minValue)
    return spinner
end

local function SetPanelAccentGradient(texture, startAlpha, endAlpha)
    if texture.SetGradient and CreateColor then
        local ok = pcall(
            texture.SetGradient,
            texture,
            "VERTICAL",
            CreateColor(1, 0.82, 0, startAlpha),
            CreateColor(1, 0.82, 0, endAlpha)
        )
        if ok then
            return
        end
    end
    if texture.SetGradientAlpha then
        texture:SetGradientAlpha("VERTICAL", 1, 0.82, 0, startAlpha, 1, 0.82, 0, endAlpha)
    else
        texture:SetVertexColor(1, 0.82, 0, math.max(startAlpha or 0, endAlpha or 0))
    end
end

local function CreatePanelCenterAccent(parent)
    local accent = CreateFrame("Frame", nil, parent)
    accent:SetPoint("TOP", parent, "TOP", 0, 0)
    accent:SetPoint("BOTTOM", parent, "BOTTOM", 0, 0)
    accent:SetPoint("CENTER", parent, "CENTER", 0, 0)
    accent:SetWidth(1)

    local top = accent:CreateTexture(nil, "OVERLAY")
    top:SetTexture("Interface\\Buttons\\WHITE8X8")
    top:SetPoint("TOPLEFT", accent, "TOPLEFT", 0, 0)
    top:SetPoint("BOTTOMRIGHT", accent, "RIGHT", 0, 0)
    SetPanelAccentGradient(top, 1, 0)

    local bottom = accent:CreateTexture(nil, "OVERLAY")
    bottom:SetTexture("Interface\\Buttons\\WHITE8X8")
    bottom:SetPoint("TOPLEFT", accent, "LEFT", 0, 0)
    bottom:SetPoint("BOTTOMRIGHT", accent, "BOTTOMRIGHT", 0, 0)
    SetPanelAccentGradient(bottom, 0, 1)

    return accent
end

local function AddFindGroupPanelDivider(container, leftPanel, rightPanel)
    local divider = CreateFrame("Frame", nil, container)
    divider:SetPoint("TOP", leftPanel, "TOPRIGHT", PANEL_DIVIDER_OFFSET_X, -PANEL_DIVIDER_TOP_OFFSET)
    divider:SetPoint("BOTTOM", leftPanel, "BOTTOMRIGHT", PANEL_DIVIDER_OFFSET_X, PANEL_DIVIDER_BOTTOM_OFFSET)
    divider:SetWidth(PANEL_DIVIDER_WIDTH)
    divider:SetFrameLevel(math.max(leftPanel:GetFrameLevel(), rightPanel:GetFrameLevel()) + 2)

    local leftShadow = divider:CreateTexture(nil, "ARTWORK", nil, 1)
    leftShadow:SetPoint("TOPLEFT", divider, "TOPLEFT", 0, 0)
    leftShadow:SetPoint("BOTTOMLEFT", divider, "BOTTOMLEFT", 0, 0)
    leftShadow:SetWidth(2)
    leftShadow:SetTexture("Interface\\Buttons\\WHITE8X8")
    leftShadow:SetVertexColor(PANEL_DIVIDER_SHADOW_COLOR[1], PANEL_DIVIDER_SHADOW_COLOR[2], PANEL_DIVIDER_SHADOW_COLOR[3], PANEL_DIVIDER_SHADOW_COLOR[4])

    local center = divider:CreateTexture(nil, "ARTWORK", nil, 2)
    center:SetPoint("TOPLEFT", leftShadow, "TOPRIGHT", 0, 0)
    center:SetPoint("BOTTOMRIGHT", divider, "BOTTOMRIGHT", -2, 0)
    center:SetTexture("Interface\\Buttons\\WHITE8X8")
    center:SetVertexColor(PANEL_DIVIDER_COLOR[1], PANEL_DIVIDER_COLOR[2], PANEL_DIVIDER_COLOR[3], PANEL_DIVIDER_COLOR[4])

    local highlight = divider:CreateTexture(nil, "ARTWORK", nil, 3)
    highlight:SetPoint("TOPLEFT", center, "TOPLEFT", 0, 0)
    highlight:SetPoint("BOTTOMLEFT", center, "BOTTOMLEFT", 0, 0)
    highlight:SetWidth(1)
    highlight:SetTexture("Interface\\Buttons\\WHITE8X8")
    highlight:SetVertexColor(PANEL_DIVIDER_HIGHLIGHT_COLOR[1], PANEL_DIVIDER_HIGHLIGHT_COLOR[2], PANEL_DIVIDER_HIGHLIGHT_COLOR[3], PANEL_DIVIDER_HIGHLIGHT_COLOR[4])

    local rightShadow = divider:CreateTexture(nil, "ARTWORK", nil, 1)
    rightShadow:SetPoint("TOPRIGHT", divider, "TOPRIGHT", 0, 0)
    rightShadow:SetPoint("BOTTOMRIGHT", divider, "BOTTOMRIGHT", 0, 0)
    rightShadow:SetWidth(2)
    rightShadow:SetTexture("Interface\\Buttons\\WHITE8X8")
    rightShadow:SetVertexColor(PANEL_DIVIDER_SHADOW_COLOR[1], PANEL_DIVIDER_SHADOW_COLOR[2], PANEL_DIVIDER_SHADOW_COLOR[3], PANEL_DIVIDER_SHADOW_COLOR[4])

    divider.CenterAccent = CreatePanelCenterAccent(divider)
    container.MSPanelDivider = divider
    return divider
end

local function CreateCheckButton(parent, labelText, width, onClick)
    local button = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
    button:SetSize(20, 20)
    button.Text:SetText(labelText or "")
    button.Text:ClearAllPoints()
    button.Text:SetPoint("LEFT", button, "RIGHT", 1, 0)
    button.Text:SetWidth(math.max(1, (width or 70) - 22 + FIND_GROUP_CHECK_TEXT_EXTRA_WIDTH))
    button.Text:SetJustifyH("LEFT")
    ApplyFindGroupFontStringStyle(button.Text, FIND_GROUP_CHECK_TEXT_SIZE, FORM_LABEL_TEXT_COLOR)
    if button.SetHitRectInsets then
        button:SetHitRectInsets(0, -math.max(0, (width or 70) - 20), 0, 0)
    end
    button:SetScript("OnClick", function(self)
        if onClick then
            onClick(self:GetChecked() == true)
        end
    end)
    button.mkFindGroupWidth = width or 70
    return button
end

local function CreateDungeonDropdown(parent, owner)
    local dropdown = CreateFrame("DropdownButton", "MKFindGroupDungeonDropdown", parent, "WowStyle1DropdownTemplate")
    dropdown:SetWidth(FORM_CONTROL_WIDTH)
    if dropdown.SetHeight then
        dropdown:SetHeight(24)
    end
    dropdown:SetSelectionText(function()
        return FindGroupSearch.GetSelectedDungeonText(owner)
    end)
    dropdown:SetupMenu(function(_, rootDescription)
        rootDescription:SetTag("MENU_MK_FIND_GROUP_DUNGEONS")
        rootDescription:CreateButton(TRANSLATE("全选"), function()
            FindGroupSearch.SelectAllDungeons(owner)
            RefreshDropdown(dropdown)
            owner:RefreshMainWindow("FindGroupSettings")
        end)
        rootDescription:CreateButton(TRANSLATE("清空"), function()
            FindGroupSearch.ClearDungeonSelection(owner)
            RefreshDropdown(dropdown)
            owner:RefreshMainWindow("FindGroupSettings")
        end)
        if rootDescription.CreateDivider then
            rootDescription:CreateDivider()
        end
        for _, option in ipairs(FindGroupSearch.GetDungeonOptions(owner)) do
            local text = option.activityName or tostring(option.activityID or "")
            local activityID = tonumber(option.activityID)
            if rootDescription.CreateCheckbox then
                rootDescription:CreateCheckbox(text, function(data)
                    return FindGroupSearch.IsDungeonSelected(owner, data)
                end, function(data)
                    FindGroupSearch.ToggleDungeon(owner, data)
                    RefreshDropdown(dropdown)
                    owner:RefreshMainWindow("FindGroupSettings")
                end, activityID)
            else
                rootDescription:CreateButton(text, function(data)
                    FindGroupSearch.ToggleDungeon(owner, data)
                    RefreshDropdown(dropdown)
                    owner:RefreshMainWindow("FindGroupSettings")
                end, activityID)
            end
        end
    end)
    return dropdown
end

local function SetCheckButtonValue(button, checked)
    if button and button.SetChecked then
        button:SetChecked(checked == true)
    end
end

local function SetFindGroupButtonEnabled(button, enabled)
    if not button then
        return
    end
    if enabled then
        button:Enable()
    else
        button:Disable()
    end
    if button.SetAlpha then
        button:SetAlpha((enabled or button.mkMeetingStoneButtonArtApplied == true) and 1 or 0.45)
    end
    if core.MeetingStoneButtonArt and core.MeetingStoneButtonArt.Update then
        core.MeetingStoneButtonArt.Update(button)
    end
end

local function SetFindGroupButtonText(button, text)
    if not button then
        return
    end
    button.mkFindGroupButtonText = text or ""
    local displayText = button.mkFindGroupButtonLoading == true and "" or button.mkFindGroupButtonText
    if button.SetText then
        button:SetText(displayText)
    end
    local fontString = button.Text or (button.GetFontString and button:GetFontString())
    if fontString then
        fontString:SetText(displayText)
        if button.mkFindGroupButtonLoading == true then
            if fontString.SetAlpha then
                fontString:SetAlpha(0)
            end
            if fontString.Hide then
                fontString:Hide()
            end
        else
            if fontString.Show then
                fontString:Show()
            end
            if fontString.SetAlpha then
                fontString:SetAlpha(1)
            end
        end
    end
end

local function SetFindGroupControlEnabled(control, enabled, applyAlpha)
    if not control then
        return
    end

    if control.SetEnabled then
        pcall(control.SetEnabled, control, enabled == true)
    elseif enabled and control.Enable then
        pcall(control.Enable, control)
    elseif not enabled and control.Disable then
        pcall(control.Disable, control)
    end

    if control.EnableMouse then
        pcall(control.EnableMouse, control, enabled == true)
    end
    if control.SetMouseClickEnabled then
        pcall(control.SetMouseClickEnabled, control, enabled == true)
    end
    if control.SetMouseMotionEnabled then
        pcall(control.SetMouseMotionEnabled, control, enabled == true)
    end
    if not enabled and control.ClearFocus then
        pcall(control.ClearFocus, control)
    end
    if applyAlpha ~= false and control.SetAlpha then
        control:SetAlpha(enabled and 1 or 0.45)
    end
    if core.MeetingStoneButtonArt and core.MeetingStoneButtonArt.Update then
        core.MeetingStoneButtonArt.Update(control)
    end
end

local function SetFindGroupLabelEnabled(label, enabled)
    if label and label.SetAlpha then
        label:SetAlpha(enabled and 1 or 0.45)
    end
end

local function SetFindGroupSearchBoxEnabled(editBox, enabled)
    SetFindGroupControlEnabled(editBox, enabled)
    if editBox and editBox.mkFindGroupUsesNativeSearchBox and editBox.Show then
        editBox:Show()
    end
    if editBox and editBox.clearButton then
        SetFindGroupControlEnabled(editBox.clearButton, enabled, false)
    end
end

local function SetFindGroupNumericInputEnabled(input, enabled)
    if not input then
        return
    end
    if input.mkFindGroupUsesNativeSpinner then
        SetFindGroupControlEnabled(input.mkFindGroupEditBox, enabled, false)
        SetFindGroupControlEnabled(input.DecrementButton, enabled, false)
        SetFindGroupControlEnabled(input.IncrementButton, enabled, false)
        if input.EnableMouseWheel then
            input:EnableMouseWheel(enabled == true)
        end
        if input.SetAlpha then
            input:SetAlpha(enabled and 1 or 0.45)
        end
    else
        SetFindGroupControlEnabled(input, enabled)
    end
end

local function SetEditBoxNumber(editBox, value)
    if not editBox then
        return
    end

    local numberValue = tonumber(value) or 0
    if editBox.mkFindGroupMinValue or editBox.mkFindGroupMaxValue then
        numberValue = math.max(
            tonumber(editBox.mkFindGroupMinValue) or -math.huge,
            math.min(tonumber(editBox.mkFindGroupMaxValue) or math.huge, math.floor(numberValue))
        )
    end

    editBox.mkFindGroupProgrammaticSetValue = true
    if editBox.SetValue then
        editBox:SetValue(numberValue)
    elseif editBox.GetText and tostring(numberValue) ~= (editBox:GetText() or "") then
        editBox:SetText(tostring(numberValue))
    end
    editBox.mkFindGroupProgrammaticSetValue = nil
end

local function SetEditBoxText(editBox, value)
    if not editBox then
        return
    end
    if editBox.mkFindGroupUsesNativeSearchBox then
        return
    end

    value = tostring(value or "")
    editBox.mkFindGroupProgrammaticSetValue = true
    if editBox:GetText() ~= value then
        editBox:SetText(value)
    end
    editBox.mkFindGroupProgrammaticSetValue = nil
end

local function TriggerFindGroupSearch(owner, refreshSource)
    local ok, reason = FindGroupSearch.Search(owner)
    if not ok and owner and owner.Print then
        owner:Print(reason)
    end
    if owner and owner.RefreshMainWindow then
        owner:RefreshMainWindow(refreshSource or "FindGroupSearch")
    end
    return ok
end

local function ApplyFindGroupSearchText(owner, input)
    if not input or input.mkFindGroupProgrammaticSetValue then
        return false
    end
    if FindGroupSearch.SetSearchTextFilter then
        return FindGroupSearch.SetSearchTextFilter(owner, input:GetText() or "")
    end
    return false
end

local function ApplyCurrentFindGroupSearchText(owner)
    local input = owner and owner.widgets and owner.widgets.findGroupSearchTextBox
    if input and FindGroupSearch.SetSearchTextFilter then
        FindGroupSearch.SetSearchTextFilter(owner, input:GetText() or "")
    end
end

local function CaptureFindGroupFrameState(frame)
    if not frame then
        return nil
    end

    local state = {
        parent = frame.GetParent and frame:GetParent() or nil,
        points = {},
        shown = frame.IsShown and frame:IsShown() or nil,
        enabled = nil,
        mouseEnabled = nil,
        mouseMotionEnabled = nil,
        alpha = frame.GetAlpha and frame:GetAlpha() or nil,
        width = frame.GetWidth and frame:GetWidth() or nil,
        height = frame.GetHeight and frame:GetHeight() or nil,
    }
    if frame.IsEnabled then
        local ok, enabled = pcall(frame.IsEnabled, frame)
        if ok then
            state.enabled = enabled == true or enabled == 1
        end
    end
    if frame.IsMouseEnabled then
        local ok, enabled = pcall(frame.IsMouseEnabled, frame)
        if ok then
            state.mouseEnabled = enabled == true or enabled == 1
        end
    end
    if frame.IsMouseMotionEnabled then
        local ok, enabled = pcall(frame.IsMouseMotionEnabled, frame)
        if ok then
            state.mouseMotionEnabled = enabled == true or enabled == 1
        end
    end
    if frame.GetNumPoints and frame.GetPoint then
        for index = 1, frame:GetNumPoints() do
            local point, relativeTo, relativePoint, xOffset, yOffset = frame:GetPoint(index)
            state.points[#state.points + 1] = {
                point = point,
                relativeTo = relativeTo,
                relativePoint = relativePoint,
                xOffset = xOffset,
                yOffset = yOffset,
            }
        end
    end
    return state
end

local function RestoreFindGroupFrameState(frame, state)
    if not (frame and state) then
        return
    end
    if frame.SetParent and state.parent then
        frame:SetParent(state.parent)
    end
    if frame.ClearAllPoints then
        frame:ClearAllPoints()
    end
    if frame.SetSize and state.width and state.height then
        frame:SetSize(state.width, state.height)
    elseif frame.SetWidth and state.width then
        frame:SetWidth(state.width)
    end
    if frame.SetPoint then
        for _, point in ipairs(state.points or {}) do
            frame:SetPoint(point.point, point.relativeTo, point.relativePoint, point.xOffset, point.yOffset)
        end
    end
    if frame.SetAlpha and state.alpha then
        frame:SetAlpha(state.alpha)
    end
    if state.enabled ~= nil then
        if state.enabled and frame.Enable then
            pcall(frame.Enable, frame)
        elseif frame.Disable then
            pcall(frame.Disable, frame)
        end
    end
    if frame.EnableMouse and state.mouseEnabled ~= nil then
        pcall(frame.EnableMouse, frame, state.mouseEnabled == true)
    end
    if frame.SetMouseMotionEnabled and state.mouseMotionEnabled ~= nil then
        pcall(frame.SetMouseMotionEnabled, frame, state.mouseMotionEnabled == true)
    end
    if state.shown ~= nil then
        if frame.SetShown then
            frame:SetShown(state.shown == true)
        elseif state.shown and frame.Show then
            frame:Show()
        elseif frame.Hide then
            frame:Hide()
        end
    end
end

local function CaptureFindGroupFontState(fontObject, options)
    if not fontObject then
        return nil
    end
    options = options or {}

    local state = CaptureFindGroupFrameState(fontObject) or {}
    if fontObject.GetFont then
        local ok, fontFile, fontHeight, fontFlags = pcall(fontObject.GetFont, fontObject)
        if ok then
            state.fontFile = fontFile
            state.fontHeight = fontHeight
            state.fontFlags = fontFlags
        end
    end
    if fontObject.GetTextColor then
        local ok, r, g, b, a = pcall(fontObject.GetTextColor, fontObject)
        if ok then
            state.textColor = { r, g, b, a }
        end
    end
    if fontObject.GetShadowColor then
        local ok, r, g, b, a = pcall(fontObject.GetShadowColor, fontObject)
        if ok then
            state.shadowColor = { r, g, b, a }
        end
    end
    if fontObject.GetShadowOffset then
        local ok, x, y = pcall(fontObject.GetShadowOffset, fontObject)
        if ok then
            state.shadowOffset = { x, y }
        end
    end
    if options.captureText ~= false and fontObject.GetText then
        local ok, text = pcall(fontObject.GetText, fontObject)
        if ok then
            state.text = text
        end
    end
    if fontObject.GetWordWrap then
        local ok, wordWrap = pcall(fontObject.GetWordWrap, fontObject)
        if ok then
            state.wordWrap = wordWrap
        end
    end
    return state
end

local function RestoreFindGroupFontState(fontObject, state, options)
    if not (fontObject and state) then
        return
    end
    options = options or {}

    RestoreFindGroupFrameState(fontObject, state)
    if options.restoreText ~= false and fontObject.SetText and state.text ~= nil then
        fontObject:SetText(state.text)
    end
    if fontObject.SetFont and state.fontFile and state.fontHeight then
        pcall(fontObject.SetFont, fontObject, state.fontFile, state.fontHeight, state.fontFlags)
    end
    if fontObject.SetTextColor and state.textColor then
        fontObject:SetTextColor(state.textColor[1], state.textColor[2], state.textColor[3], state.textColor[4])
    end
    if fontObject.SetShadowColor and state.shadowColor then
        fontObject:SetShadowColor(state.shadowColor[1], state.shadowColor[2], state.shadowColor[3], state.shadowColor[4])
    end
    if fontObject.SetShadowOffset and state.shadowOffset then
        fontObject:SetShadowOffset(state.shadowOffset[1], state.shadowOffset[2])
    end
    if fontObject.SetWordWrap and state.wordWrap ~= nil then
        pcall(fontObject.SetWordWrap, fontObject, state.wordWrap == true)
    end
end

local function CaptureNativeFindGroupSearchBoxState(editBox)
    if not editBox or editBox._mkFindGroupNativeSearchState then
        return
    end

    editBox.mkFindGroupNativeSearchRestored = nil
    local state = CaptureFindGroupFontState(editBox, { captureText = false }) or {}
    state.scripts = {}
    if editBox.GetScript then
        for _, scriptName in ipairs({ "OnEnterPressed", "OnEscapePressed", "OnTextChanged", "OnEditFocusGained", "OnEditFocusLost" }) do
            state.scripts[scriptName] = editBox:GetScript(scriptName)
        end
    end
    if editBox.GetMaxLetters then
        local ok, maxLetters = pcall(editBox.GetMaxLetters, editBox)
        if ok then
            state.maxLetters = maxLetters
        end
    end
    if editBox.GetTextInsets then
        local ok, left, right, top, bottom = pcall(editBox.GetTextInsets, editBox)
        if ok then
            state.textInsets = { left, right, top, bottom }
        end
    end
    state.searchIcon = CaptureFindGroupFrameState(editBox.searchIcon)
    state.clearButton = CaptureFindGroupFrameState(editBox.clearButton)
    state.clearButtonOnClick = editBox.clearButton and editBox.clearButton.GetScript and editBox.clearButton:GetScript("OnClick") or nil
    state.instructions = CaptureFindGroupFontState(editBox.Instructions)
    editBox._mkFindGroupNativeSearchState = state
end

local function RestoreNativeFindGroupSearchBoxState(editBox)
    local state = editBox and editBox._mkFindGroupNativeSearchState
    if type(state) ~= "table" then
        return
    end

    RestoreFindGroupFontState(editBox, state, { restoreText = false })
    if editBox.SetMaxLetters and state.maxLetters then
        pcall(editBox.SetMaxLetters, editBox, state.maxLetters)
    end
    if editBox.SetTextInsets and state.textInsets then
        pcall(editBox.SetTextInsets, editBox, state.textInsets[1], state.textInsets[2], state.textInsets[3], state.textInsets[4])
    end
    if editBox.SetScript and state.scripts then
        for scriptName, script in pairs(state.scripts) do
            editBox:SetScript(scriptName, script)
        end
    end
    RestoreFindGroupFrameState(editBox.searchIcon, state.searchIcon)
    RestoreFindGroupFrameState(editBox.clearButton, state.clearButton)
    if editBox.clearButton and editBox.clearButton.SetScript then
        editBox.clearButton:SetScript("OnClick", state.clearButtonOnClick)
    end
    RestoreFindGroupFontState(editBox.Instructions, state.instructions)
    editBox._mkFindGroupNativeSearchState = nil
end

local function RestoreNativeFindGroupSearchBox(editBox)
    if not (editBox and editBox.mkFindGroupUsesNativeSearchBox) then
        return
    end
    if editBox.mkFindGroupNativeSearchRestored == true and type(editBox._mkFindGroupNativeSearchState) ~= "table" then
        return
    end
    if not (LFGListFrame and LFGListFrame.SearchPanel) then
        return
    end
    if editBox.mkFindGroupOwnerPanel and editBox.GetParent and editBox:GetParent() ~= editBox.mkFindGroupOwnerPanel then
        return
    end
    local hadCapturedState = type(editBox._mkFindGroupNativeSearchState) == "table"
    RestoreNativeFindGroupSearchBoxState(editBox)
    if editBox.GetParent and editBox:GetParent() ~= LFGListFrame.SearchPanel then
        editBox:ClearAllPoints()
        editBox:SetParent(LFGListFrame.SearchPanel)
        if LFGListFrame.SearchPanel.CategoryName then
            editBox:SetPoint("TOPLEFT", LFGListFrame.SearchPanel.CategoryName, "BOTTOMLEFT", 4, -7)
        end
        editBox:SetWidth(319)
    end
    if hadCapturedState then
        editBox.mkFindGroupOwnerPanel = nil
        editBox.mkFindGroupNativeSearchRestored = true
        return
    end
    if LFGListSearchPanelSearchBox_OnEnterPressed then
        editBox:SetScript("OnEnterPressed", LFGListSearchPanelSearchBox_OnEnterPressed)
    end
    if LFGListSearchPanelSearchBox_OnTextChanged then
        editBox:SetScript("OnTextChanged", LFGListSearchPanelSearchBox_OnTextChanged)
    end
    if LFGListSearchPanelSearchBox_OnEditFocusGained then
        editBox:SetScript("OnEditFocusGained", LFGListSearchPanelSearchBox_OnEditFocusGained)
    end
    if LFGListSearchPanelSearchBox_OnEditFocusLost then
        editBox:SetScript("OnEditFocusLost", LFGListSearchPanelSearchBox_OnEditFocusLost)
    end
    if editBox.clearButton then
        editBox.clearButton:SetScript("OnClick", function(btn)
            if PlaySound and SOUNDKIT then
                PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
            end
            if C_LFGList and C_LFGList.ClearSearchTextFields then
                pcall(C_LFGList.ClearSearchTextFields)
            end
            btn:GetParent():ClearFocus()
            if LFGListFrame and LFGListFrame.SearchPanel and LFGListSearchPanel_DoSearch then
                pcall(LFGListSearchPanel_DoSearch, LFGListFrame.SearchPanel)
            end
        end)
    end
    editBox.mkFindGroupOwnerPanel = nil
    editBox.mkFindGroupNativeSearchRestored = true
end

function FindGroupTab.DetachNativeSearchBox(owner)
    local widgets = owner and owner.widgets
    local editBox = widgets and widgets.findGroupSearchTextBox
    if not editBox then
        local nativeSearchBox = LFGListFrame and LFGListFrame.SearchPanel and LFGListFrame.SearchPanel.SearchBox
        if nativeSearchBox and nativeSearchBox.mkFindGroupUsesNativeSearchBox then
            editBox = nativeSearchBox
        end
    end
    RestoreNativeFindGroupSearchBox(editBox)
end

if core.MK then
    function core.MK:DetachFindGroupNativeSearchBox()
        FindGroupTab.DetachNativeSearchBox(self)
    end
end

local function GetNativeFindGroupSearchBox()
    if not LFGListFrame then
        if C_AddOns and C_AddOns.LoadAddOn then
            pcall(C_AddOns.LoadAddOn, "Blizzard_GroupFinder")
        elseif LoadAddOn then
            pcall(LoadAddOn, "Blizzard_GroupFinder")
        end
        if not LFGListFrame and PVEFrame_LoadUI then
            pcall(PVEFrame_LoadUI)
        end
    end
    return LFGListFrame and LFGListFrame.SearchPanel and LFGListFrame.SearchPanel.SearchBox or nil
end

local function CreateFindGroupSearchTextInput(parent, owner)
    local editBox = GetNativeFindGroupSearchBox()
    local usesNativeSearchBox = editBox ~= nil
    if editBox then
        CaptureNativeFindGroupSearchBoxState(editBox)
        editBox.mkFindGroupUsesNativeSearchBox = true
        editBox:SetParent(parent)
        editBox:ClearAllPoints()
    else
        editBox = CreateFrame("EditBox", nil, parent, "SearchBoxTemplate")
    end
    editBox:SetSize(math.max(1, FIND_GROUP_SEARCH_TEXT_WIDTH), FORM_CONTROL_HEIGHT)
    if not usesNativeSearchBox then
        editBox:SetAutoFocus(false)
        editBox:SetMaxLetters(64)
    end
    if not usesNativeSearchBox and StyleInputBox then
        StyleInputBox(editBox)
    end
    if not usesNativeSearchBox and editBox.SetTextInsets then
        editBox:SetTextInsets(FIND_GROUP_SEARCH_TEXT_TEXT_INSET_LEFT, FIND_GROUP_SEARCH_TEXT_TEXT_INSET_RIGHT, 0, 0)
    end
    if not usesNativeSearchBox and core.ApplyFont then
        core.ApplyFont(editBox, FORM_INPUT_TEXT_SIZE, "")
    end
    if editBox.searchIcon then
        editBox.searchIcon:ClearAllPoints()
        editBox.searchIcon:SetPoint("LEFT", editBox, "LEFT", FIND_GROUP_SEARCH_TEXT_ICON_INSET, 0)
        editBox.searchIcon:SetSize(FIND_GROUP_SEARCH_TEXT_ICON_SIZE, FIND_GROUP_SEARCH_TEXT_ICON_SIZE)
    end
    if editBox.clearButton then
        editBox.clearButton:ClearAllPoints()
        editBox.clearButton:SetPoint("RIGHT", editBox, "RIGHT", -FIND_GROUP_SEARCH_TEXT_CLEAR_INSET, 0)
    end
    if editBox.Instructions then
        editBox.Instructions:ClearAllPoints()
        editBox.Instructions:SetPoint("LEFT", editBox, "LEFT", FIND_GROUP_SEARCH_TEXT_TEXT_INSET_LEFT, 0)
        editBox.Instructions:SetPoint("RIGHT", editBox, "RIGHT", -FIND_GROUP_SEARCH_TEXT_TEXT_INSET_RIGHT, 0)
        editBox.Instructions:SetText(TRANSLATE("搜索队伍名称、层数、说明……"))
        editBox.Instructions:SetWordWrap(false)
        if not usesNativeSearchBox then
            ApplyFindGroupFontStringStyle(editBox.Instructions, FORM_INPUT_TEXT_SIZE, FORM_PLACEHOLDER_TEXT_COLOR)
        end
    end

    editBox:SetScript("OnTextChanged", function(input)
        if SearchBoxTemplate_OnTextChanged then
            SearchBoxTemplate_OnTextChanged(input)
        end
        ApplyFindGroupSearchText(owner, input)
    end)
    editBox:SetScript("OnEditFocusGained", function(input)
        if SearchBoxTemplate_OnEditFocusGained then
            SearchBoxTemplate_OnEditFocusGained(input)
        end
    end)
    editBox:SetScript("OnEditFocusLost", function(input)
        if SearchBoxTemplate_OnEditFocusLost then
            SearchBoxTemplate_OnEditFocusLost(input)
        end
    end)
    editBox:SetScript("OnEnterPressed", function(input)
        ApplyFindGroupSearchText(owner, input)
        TriggerFindGroupSearch(owner, "FindGroupSearchText")
        input:ClearFocus()
    end)
    editBox:SetScript("OnEscapePressed", function(input)
        input:ClearFocus()
    end)
    if editBox.clearButton then
        editBox.clearButton:SetScript("OnClick", function()
            if PlaySound and SOUNDKIT then
                PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
            end
            if C_LFGList and C_LFGList.ClearSearchTextFields then
                pcall(C_LFGList.ClearSearchTextFields)
            end
            if FindGroupSearch.SetSearchTextFilter then
                FindGroupSearch.SetSearchTextFilter(owner, "")
            end
            TriggerFindGroupSearch(owner, "FindGroupSearchTextClear")
        end)
    end
    return editBox
end

local function AttachNativeFindGroupSearchBox(editBox, parent, owner)
    if not (editBox and editBox.mkFindGroupUsesNativeSearchBox and parent) then
        return editBox
    end

    local nativeSearchBox = GetNativeFindGroupSearchBox()
    if not nativeSearchBox then
        return editBox
    end

    nativeSearchBox.mkFindGroupUsesNativeSearchBox = true
    nativeSearchBox.mkFindGroupOwnerPanel = parent
    nativeSearchBox = CreateFindGroupSearchTextInput(parent, owner)
    nativeSearchBox.mkFindGroupOwnerPanel = parent
    nativeSearchBox:ClearAllPoints()
    nativeSearchBox:SetPoint("BOTTOMLEFT", parent, "BOTTOMLEFT", FORM_INSET_X + FIND_GROUP_SEARCH_TEXT_LEFT_INSET, FORM_KEYWORD_BOTTOM_OFFSET)
    if nativeSearchBox.Show then
        nativeSearchBox:Show()
    end
    return nativeSearchBox
end

local function RefreshControls(owner, widgets)
    local settings = FindGroupSearch.GetSettings(owner) or {}
    if widgets.findGroupSearchTextBox then
        widgets.findGroupSearchTextBox = AttachNativeFindGroupSearchBox(
            widgets.findGroupSearchTextBox,
            widgets.findGroupLeftPanel,
            owner
        )
        if widgets.findGroupLeftPanel then
            widgets.findGroupLeftPanel.mkFindGroupSearchTextBox = widgets.findGroupSearchTextBox
        end
    end
    if widgets.findGroupDungeonDropdown then
        RefreshDropdown(widgets.findGroupDungeonDropdown)
    end
    SetCheckButtonValue(widgets.findGroupHaveHealerCheck, settings.haveHealer)
    SetCheckButtonValue(widgets.findGroupNoHealerCheck, settings.noHealer)
    SetCheckButtonValue(widgets.findGroupHaveTankCheck, settings.haveTank)
    SetCheckButtonValue(widgets.findGroupNoTankCheck, settings.noTank)
    SetCheckButtonValue(widgets.findGroupNoDpsCheck, settings.noDps)
    SetCheckButtonValue(widgets.findGroupSearchNewbieCheck, settings.searchNewbie)
    SetCheckButtonValue(widgets.findGroupQuickJoinCheck, settings.quickJoin)
    SetCheckButtonValue(widgets.findGroupAutoJoinCheck, settings.autoJoin)
    SetEditBoxNumber(widgets.findGroupMinSlotsBox, settings.minAvailableSlots or 1)
    SetEditBoxNumber(widgets.findGroupMinScoreBox, settings.minScore or 0)
    SetEditBoxText(widgets.findGroupSearchTextBox, settings.searchText or "")
    local state = FindGroupSearch.GetState and FindGroupSearch.GetState(owner) or {}
    local hasSelectedDungeons = FindGroupSearch.HasSelectedDungeons and FindGroupSearch.HasSelectedDungeons(owner)
    local hasSearch = state.searching == true
        or state.processing == true
        or state.newbieSearching == true
        or #(state.results or {}) > 0
    local controlsEnabled = hasSelectedDungeons == true
    SetFindGroupLabelEnabled(widgets.findGroupRoleLabel, controlsEnabled)
    SetFindGroupLabelEnabled(widgets.findGroupMinSlotsLabel, controlsEnabled)
    SetFindGroupLabelEnabled(widgets.findGroupMinScoreLabel, controlsEnabled)
    SetFindGroupLabelEnabled(widgets.findGroupQuickLabel, controlsEnabled)
    SetFindGroupControlEnabled(widgets.findGroupHaveHealerCheck, controlsEnabled)
    SetFindGroupControlEnabled(widgets.findGroupNoHealerCheck, controlsEnabled)
    SetFindGroupControlEnabled(widgets.findGroupHaveTankCheck, controlsEnabled)
    SetFindGroupControlEnabled(widgets.findGroupNoTankCheck, controlsEnabled)
    SetFindGroupControlEnabled(widgets.findGroupNoDpsCheck, controlsEnabled)
    SetFindGroupNumericInputEnabled(widgets.findGroupMinSlotsBox, controlsEnabled)
    SetFindGroupNumericInputEnabled(widgets.findGroupMinScoreBox, controlsEnabled)
    SetFindGroupControlEnabled(widgets.findGroupSearchNewbieCheck, controlsEnabled)
    SetFindGroupControlEnabled(widgets.findGroupQuickJoinCheck, controlsEnabled)
    SetFindGroupControlEnabled(widgets.findGroupAutoJoinCheck, controlsEnabled)
    SetFindGroupSearchBoxEnabled(widgets.findGroupSearchTextBox, controlsEnabled)
    SetFindGroupButtonText(widgets.findGroupSearchButton, TRANSLATE(hasSearch and "刷新" or "搜索"))
    SetFindGroupButtonEnabled(widgets.findGroupSearchButton, controlsEnabled)
    SetFindGroupButtonEnabled(widgets.findGroupResetButton, controlsEnabled)
end

local function AddLeftHeader(panel)
    local background = panel:CreateTexture(nil, "BACKGROUND", nil, 1)
    background:SetPoint("TOPLEFT", panel, "TOPLEFT", 0, HEADER_TOP_OFFSET)
    background:SetPoint("BOTTOMRIGHT", panel, "TOPRIGHT", 0, HEADER_BOTTOM_OFFSET)
    TrySetAtlas(background, "housefinder_header-bg-gradient", false)

    local title = panel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetPoint("TOPLEFT", background, "TOPLEFT", HEADER_CONTENT_INSET_X, HEADER_CONTENT_OFFSET_Y)
    title:SetPoint("BOTTOMRIGHT", background, "BOTTOMRIGHT", -HEADER_CONTENT_INSET_X, HEADER_CONTENT_OFFSET_Y)
    title:SetJustifyH("CENTER")
    title:SetJustifyV("MIDDLE")
    title:SetText(TRANSLATE("寻找队伍"))
    title:SetTextColor(HEADER_TEXT_COLOR[1], HEADER_TEXT_COLOR[2], HEADER_TEXT_COLOR[3], HEADER_TEXT_COLOR[4])
    if core.ApplyFont then
        core.ApplyFont(title, HEADER_TEXT_SIZE, "OUTLINE")
    end
    panel.HeaderBackground = background
    panel.HeaderTitle = title
end

local function CreateLeftPanel(owner, container)
    local panel = CreateFrame("Frame", nil, container, "BackdropTemplate")
    panel:SetPoint("TOPLEFT", container, "TOPLEFT", 0, 0)
    panel:SetPoint("BOTTOMLEFT", container, "BOTTOMLEFT", 0, 0)
    panel:SetWidth(LEFT_PANEL_WIDTH)

    local leftBackground = panel:CreateTexture(nil, "BACKGROUND")
    leftBackground:SetAllPoints(panel)
    leftBackground:SetTexture("Interface\\Buttons\\WHITE8X8")
    leftBackground:SetVertexColor(0, 0, 0, LEFT_PANEL_BACKGROUND_ALPHA)
    leftBackground:Hide()
    panel.Background = leftBackground

    AddLeftHeader(panel)

    local y = FORM_TOP_OFFSET
    local function addTitle(text)
        local label = CreateFindGroupLabel(panel, text)
        label:SetPoint("TOPLEFT", panel, "TOPLEFT", FORM_INSET_X, y)
        label:SetWidth(FORM_CONTROL_WIDTH)
        label:SetHeight(FORM_LABEL_HEIGHT)
        y = y - FORM_LABEL_HEIGHT - FIND_GROUP_LABEL_TO_CONTROL_GAP
        return label
    end
    local function addControl(control, height)
        control:SetPoint("TOPLEFT", panel, "TOPLEFT", FORM_INSET_X, y)
        y = y - (height or FORM_CONTROL_HEIGHT) - FIND_GROUP_ROW_GAP
        return control
    end
    local function addCheck(text, onClick)
        local check = CreateCheckButton(panel, text, FORM_CONTROL_WIDTH, onClick)
        check:SetPoint("TOPLEFT", panel, "TOPLEFT", FORM_INSET_X, y)
        y = y - FIND_GROUP_CHECK_ROW_HEIGHT
        return check
    end
    local function addTwoChecks(leftText, rightText, onLeft, onRight)
        local columnGap = FIND_GROUP_CHECK_COLUMN_GAP
        local columnWidth = math.floor((FORM_CONTROL_WIDTH - columnGap) / 2)
        local leftCheck = CreateCheckButton(panel, leftText, columnWidth, onLeft)
        leftCheck:SetPoint("TOPLEFT", panel, "TOPLEFT", FORM_INSET_X, y)
        local rightCheck
        if rightText then
            rightCheck = CreateCheckButton(panel, rightText, columnWidth, onRight)
            rightCheck:SetPoint("TOPLEFT", panel, "TOPLEFT", FORM_INSET_X + columnWidth + columnGap, y)
        end
        y = y - FIND_GROUP_CHECK_ROW_HEIGHT
        return leftCheck, rightCheck
    end
    local function addNumberInput(text, defaultValue, onChanged, options)
        local label = CreateFindGroupLabel(panel, text)
        label:SetPoint("LEFT", panel, "TOPLEFT", FORM_INSET_X, y - (FORM_CONTROL_HEIGHT / 2))
        label:SetWidth(FORM_INLINE_LABEL_WIDTH)
        label:SetHeight(FORM_CONTROL_HEIGHT)

        local minValue = options and options.minValue
        local maxValue = options and options.maxValue
        local box = options and options.spinner
            and CreateFindGroupNumericSpinner(panel, minValue, maxValue)
            or CreateFindGroupInput(panel, FORM_INLINE_INPUT_WIDTH, FORM_CONTROL_HEIGHT, true)
        if not box.mkFindGroupUsesNativeSpinner then
            box.mkFindGroupMinValue = minValue
            box.mkFindGroupMaxValue = maxValue
        end

        if box.mkFindGroupUsesNativeSpinner then
            box:SetPoint("TOPRIGHT", panel, "TOPLEFT", FORM_INSET_X + FORM_CONTROL_WIDTH + 8, y)
        else
            box:SetPoint("TOPRIGHT", panel, "TOPLEFT", FORM_INSET_X + FORM_CONTROL_WIDTH, y)
        end

        SetEditBoxNumber(box, defaultValue or 0)
        EnableSelectAllAfterMouseLeave(box.mkFindGroupEditBox or box)
        local function handleValueChanged(input, value)
            if input and input.mkFindGroupProgrammaticSetValue then
                return
            end
            if onChanged then
                onChanged(tonumber(value) or input and input.GetNumber and input:GetNumber() or 0)
            end
        end
        if box.SetOnValueChangedCallback then
            box:SetOnValueChangedCallback(handleValueChanged)
        else
            box:SetScript("OnTextChanged", function(self)
                handleValueChanged(self, self:GetNumber())
            end)
        end
        y = y - FORM_CONTROL_HEIGHT - FIND_GROUP_ROW_GAP
        return label, box
    end
    local dungeonLabel = addTitle(TRANSLATE("选择赛季副本"))
    local dungeonDropdown = addControl(CreateDungeonDropdown(panel, owner), FORM_CONTROL_HEIGHT)

    local roleLabel = addTitle(TRANSLATE("筛选职责"))
    local noHealerCheck, haveHealerCheck = addTwoChecks(
        TRANSLATE("缺治疗"),
        TRANSLATE("有治疗"),
        function(value)
            FindGroupSearch.SetBooleanFilter(owner, "noHealer", value)
            owner:RefreshMainWindow("FindGroupSettings")
        end,
        function(value)
            FindGroupSearch.SetBooleanFilter(owner, "haveHealer", value)
            owner:RefreshMainWindow("FindGroupSettings")
        end
    )
    local noTankCheck, haveTankCheck = addTwoChecks(
        TRANSLATE("缺坦克"),
        TRANSLATE("有坦克"),
        function(value)
            FindGroupSearch.SetBooleanFilter(owner, "noTank", value)
            owner:RefreshMainWindow("FindGroupSettings")
        end,
        function(value)
            FindGroupSearch.SetBooleanFilter(owner, "haveTank", value)
            owner:RefreshMainWindow("FindGroupSettings")
        end
    )
    local noDpsCheck = addTwoChecks(TRANSLATE("缺输出"), nil, function(value)
        FindGroupSearch.SetBooleanFilter(owner, "noDps", value)
        owner:RefreshMainWindow("FindGroupSettings")
    end)

    y = y - FIND_GROUP_SECTION_GAP
    local slotsLabel, slotsBox = addNumberInput(TRANSLATE("至少空位"), 1, function(value)
        FindGroupSearch.SetNumericFilter(owner, "minAvailableSlots", value)
        owner:RefreshMainWindow("FindGroupSettings")
    end, {
        spinner = true,
        minValue = FIND_GROUP_MIN_AVAILABLE_SLOTS,
        maxValue = FIND_GROUP_MAX_AVAILABLE_SLOTS,
    })

    local scoreLabel, scoreBox = addNumberInput(TRANSLATE("最低评分"), 0, function(value)
        FindGroupSearch.SetNumericFilter(owner, "minScore", value)
        owner:RefreshMainWindow("FindGroupSettings")
    end)

    y = y - FIND_GROUP_SECTION_GAP
    local quickLabel = addTitle(TRANSLATE("其他功能"))
    local searchNewbieCheck = addCheck(TRANSLATE("筛选大秘新兵队伍"), function(value)
        FindGroupSearch.SetBooleanFilter(owner, "searchNewbie", value)
        owner:RefreshMainWindow("FindGroupSettings")
    end)
    local quickJoinCheck, autoJoinCheck = addTwoChecks(
        TRANSLATE("双击加入"),
        TRANSLATE("自动进组"),
        function(value)
            FindGroupSearch.SetBooleanFilter(owner, "quickJoin", value)
            owner:RefreshMainWindow("FindGroupSettings")
        end,
        function(value)
            FindGroupSearch.SetBooleanFilter(owner, "autoJoin", value)
            FindGroupSearch.EnsureAutoJoinHooks(owner)
            owner:RefreshMainWindow("FindGroupSettings")
        end
    )
    AddFindGroupTooltip(quickJoinCheck, "双击加入", "按照队伍查找器所选的职责申请队伍")
    AddFindGroupTooltip(autoJoinCheck, "自动进组", "自动接受本插件申请过的队伍邀请")

    local leftBottomGradient = panel:CreateTexture(nil, "OVERLAY")
    leftBottomGradient:SetPoint("TOPLEFT", panel, "BOTTOMLEFT", 0, LEFT_BOTTOM_GRADIENT_TOP_OFFSET)
    leftBottomGradient:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", 0, 0)
    if TrySetAtlas(leftBottomGradient, "QuestLog-Frame-Gradient-bottom", false) then
        leftBottomGradient:SetAlpha(0.75)
    else
        leftBottomGradient:Hide()
    end
    panel.BottomGradient = leftBottomGradient

    local searchButton = CreateSystemButton(panel, nil, FORM_BUTTON_WIDTH, FORM_BUTTON_HEIGHT, TRANSLATE("搜索"), ACTION_BUTTON_TEXT_OFFSET_Y)
    searchButton:SetPoint("BOTTOMLEFT", panel, "BOTTOMLEFT", FORM_INSET_X, FORM_BUTTON_BOTTOM_OFFSET)
    searchButton:SetScript("OnClick", function()
        if core.PlayPanelButtonClickSound then
            core.PlayPanelButtonClickSound()
        end
        ApplyCurrentFindGroupSearchText(owner)
        TriggerFindGroupSearch(owner, "FindGroupSearch")
    end)
    local resetButton = CreateSystemButton(panel, nil, FORM_BUTTON_WIDTH, FORM_BUTTON_HEIGHT, TRANSLATE("重置"), ACTION_BUTTON_TEXT_OFFSET_Y)
    resetButton:SetPoint("LEFT", searchButton, "RIGHT", FORM_BUTTON_GAP, 0)
    resetButton:SetScript("OnClick", function()
        if core.PlayPanelButtonClickSound then
            core.PlayPanelButtonClickSound()
        end
        FindGroupSearch.Reset(owner)
        owner:RefreshMainWindow("FindGroupReset")
    end)

    local searchTextBox = CreateFindGroupSearchTextInput(panel, owner)
    if searchTextBox.mkFindGroupUsesNativeSearchBox then
        searchTextBox = AttachNativeFindGroupSearchBox(searchTextBox, panel, owner)
    else
        searchTextBox:SetPoint("BOTTOMLEFT", panel, "BOTTOMLEFT", FORM_INSET_X + FIND_GROUP_SEARCH_TEXT_LEFT_INSET, FORM_KEYWORD_BOTTOM_OFFSET)
    end
    panel.mkFindGroupSearchTextBox = searchTextBox
    panel:SetScript("OnShow", function()
        searchTextBox = AttachNativeFindGroupSearchBox(searchTextBox, panel, owner)
        panel.mkFindGroupSearchTextBox = searchTextBox
        if owner.widgets then
            owner.widgets.findGroupSearchTextBox = searchTextBox
        end
        SetFindGroupSearchBoxEnabled(
            searchTextBox,
            FindGroupSearch.HasSelectedDungeons and FindGroupSearch.HasSelectedDungeons(owner) == true
        )
    end)
    panel:SetScript("OnHide", function()
        RestoreNativeFindGroupSearchBox(panel.mkFindGroupSearchTextBox or searchTextBox)
    end)

    return panel, {
        findGroupDungeonLabel = dungeonLabel,
        findGroupDungeonDropdown = dungeonDropdown,
        findGroupRoleLabel = roleLabel,
        findGroupHaveHealerCheck = haveHealerCheck,
        findGroupNoHealerCheck = noHealerCheck,
        findGroupHaveTankCheck = haveTankCheck,
        findGroupNoTankCheck = noTankCheck,
        findGroupNoDpsCheck = noDpsCheck,
        findGroupMinSlotsLabel = slotsLabel,
        findGroupMinSlotsBox = slotsBox,
        findGroupMinScoreLabel = scoreLabel,
        findGroupMinScoreBox = scoreBox,
        findGroupSearchNewbieCheck = searchNewbieCheck,
        findGroupQuickLabel = quickLabel,
        findGroupQuickJoinCheck = quickJoinCheck,
        findGroupAutoJoinCheck = autoJoinCheck,
        findGroupSearchTextBox = searchTextBox,
        findGroupSearchButton = searchButton,
        findGroupResetButton = resetButton,
    }
end

function FindGroupTab.Refresh(owner, widgets, refreshSource)
    if not widgets then
        return
    end
    FindGroupSearch.EnsureEventFrame(owner)
    if refreshSource == "NetEaseAPI" and FindGroupSearch.RefreshAccountTypeStates then
        FindGroupSearch.RefreshAccountTypeStates(owner)
    end
    RefreshControls(owner, widgets)
    if FindGroupList.Refresh then
        FindGroupList.Refresh(owner, widgets)
    end
end

function FindGroupTab.BuildSnapshot(owner)
    if FindGroupSearch.BuildSnapshot then
        return FindGroupSearch.BuildSnapshot(owner)
    end
    return ""
end

function FindGroupTab.Build(owner, tabGroup)
    FindGroupSearch.EnsureEventFrame(owner)

    local container = CreateFrame("Frame", nil, UIParent)
    container.mkFillDecorFrame = true
    container.mkDecorFrameInsetLeft = PANEL_INSET_LEFT
    container.mkDecorFrameInsetTop = PANEL_INSET_TOP
    container.mkDecorFrameInsetRight = PANEL_INSET_RIGHT
    container.mkDecorFrameInsetBottom = PANEL_INSET_BOTTOM
    tabGroup:AddChild(container)

    local leftPanel, leftWidgets = CreateLeftPanel(owner, container)
    local rightPanel, rightWidgets = FindGroupList.CreateRightPanel(owner, container, leftPanel)
    AddFindGroupPanelDivider(container, leftPanel, rightPanel)

    local widgets = {
        tabGroup = owner.widgets and owner.widgets.tabGroup or tabGroup,
        root = container,
        findGroupLeftPanel = leftPanel,
        findGroupRightPanel = rightPanel,
        skipApplyFontToFrame = true,
    }
    for key, value in pairs(leftWidgets) do
        widgets[key] = value
    end
    for key, value in pairs(rightWidgets) do
        widgets[key] = value
    end

    owner.widgets = widgets
    owner.tabViews = owner.tabViews or {}
    owner.tabViews.findgroup = widgets
    if core.ApplyThemeToTabGroup then
        core.ApplyThemeToTabGroup(tabGroup)
    end
    container:Show()
    FindGroupTab.Refresh(owner, widgets)
end
