local _, ns = ...
local B, C, L, DB = unpack(ns)

local OrzUIPremadeFilter = {}
_G.OrzUIPremadeFilter = OrzUIPremadeFilter

-- Lua API cache
local format, tremove = string.format, table.remove
local floor = math.floor
local pairs, ipairs = pairs, ipairs
local tonumber, tostring = tonumber, tostring

-- Config helper
local cfgKey = "PremadeFilter"
local function CFG(k, v)
    if not C.db[cfgKey] then C.db[cfgKey] = {} end
    if v ~= nil then
        C.db[cfgKey][k] = v
    end
    return C.db[cfgKey][k]
end

-- Difficulty constants
local DIFFICULTY_NORMAL = 1
local DIFFICULTY_HEROIC = 2
local DIFFICULTY_MYTHIC = 3
local DIFFICULTY_MYTHICPLUS = 4

local DIFFICULTY_MAP = {
    [DIFFICULTY_NORMAL] = "normal",
    [DIFFICULTY_HEROIC] = "heroic",
    [DIFFICULTY_MYTHIC] = "mythic",
    [DIFFICULTY_MYTHICPLUS] = "mythicplus",
}

-- Class properties (bloodlust / battle rez)
local CLASS_PROPERTIES = {
    ["DEATHKNIGHT"] = { br = true,  bl = false },
    ["DEMONHUNTER"] = { br = false, bl = false },
    ["DRUID"]       = { br = true,  bl = false },
    ["EVOKER"]      = { br = false, bl = true  },
    ["HUNTER"]      = { br = false, bl = true  },
    ["PALADIN"]     = { br = true,  bl = false },
    ["PRIEST"]      = { br = false, bl = false },
    ["MAGE"]        = { br = false, bl = true  },
    ["MONK"]        = { br = false, bl = false },
    ["ROGUE"]       = { br = false, bl = false },
    ["SHAMAN"]      = { br = false, bl = true  },
    ["WARLOCK"]     = { br = true,  bl = false },
    ["WARRIOR"]     = { br = false, bl = false },
}

-- Role remaining keys
local ROLE_REMAINING_KEYS = {
    ["TANK"] = "TANK_REMAINING",
    ["HEALER"] = "HEALER_REMAINING",
    ["DAMAGER"] = "DAMAGER_REMAINING",
}

-- Helper: check if unit/class has a property
local function UnitHasProperty(unit, prop)
    local class = select(2, UnitClass(unit))
    return class and CLASS_PROPERTIES[class] and CLASS_PROPERTIES[class][prop]
end

local function PlayerOrGroupHasProperty(prop)
    local numGroupMembers = GetNumGroupMembers()
    if numGroupMembers == 0 then
        return UnitHasProperty("player", prop)
    end
    local groupType = IsInRaid() and "raid" or "party"
    for i = 1, numGroupMembers do
        local unit = (i == 1) and "player" or (groupType .. (i - 1))
        if UnitHasProperty(unit, prop) then
            return true
        end
    end
    return false
end

local function PlayerOrGroupHasBloodlust()
    return PlayerOrGroupHasProperty("bl")
end

-- Helper: get party roles
local function GetPartyRoles()
    local numGroupMembers = GetNumGroupMembers()
    local groupType = IsInRaid() and "raid" or "party"
    local partyRoles = { TANK = 0, HEALER = 0, DAMAGER = 0 }
    if numGroupMembers == 0 then
        local playerRole = GetSpecializationRole(GetSpecialization())
        if playerRole then partyRoles[playerRole] = 1 end
    else
        for i = 1, numGroupMembers do
            local unit = (i == 1) and "player" or (groupType .. (i - 1))
            local role = UnitGroupRolesAssigned(unit)
            if role == "NONE" then role = "DAMAGER" end
            partyRoles[role] = partyRoles[role] + 1
        end
    end
    return partyRoles
end

-- Helper: remaining slots for local player party roles
local function HasRemainingSlotsForLocalPlayerRole(memberCounts)
    local playerRole = GetSpecializationRole(GetSpecialization())
    if not playerRole then return false end
    return (memberCounts[ROLE_REMAINING_KEYS[playerRole]] or 0) > 0
end

local function HasRemainingSlotsForLocalPlayerPartyRoles(memberCounts)
    if not memberCounts then return false end
    if GetNumGroupMembers() == 0 then
        return HasRemainingSlotsForLocalPlayerRole(memberCounts)
    end
    local partyRoles = GetPartyRoles()
    for role, remainingKey in pairs(ROLE_REMAINING_KEYS) do
        if (memberCounts[remainingKey] or 0) < partyRoles[role] then
            return false
        end
    end
    return true
end

-- Helper: member counts after join
local function GetMemberCountsAfterJoin(memberCounts)
    local counts = {}
    for k, v in pairs(memberCounts) do
        counts[k] = v
    end
    local numGroupMembers = GetNumGroupMembers()
    local groupType = IsInRaid() and "raid" or "party"
    if numGroupMembers == 0 then
        local role = GetSpecializationRole(GetSpecialization())
        if not role then role = "DAMAGER" end
        local remainingKey = ROLE_REMAINING_KEYS[role]
        counts[role] = (counts[role] or 0) + 1
        counts[remainingKey] = (counts[remainingKey] or 0) - 1
    else
        for i = 1, numGroupMembers do
            local unit = (i == 1) and "player" or (groupType .. (i - 1))
            local role = UnitGroupRolesAssigned(unit)
            if not role or role == "NONE" then role = "DAMAGER" end
            local remainingKey = ROLE_REMAINING_KEYS[role]
            counts[role] = (counts[role] or 0) + 1
            counts[remainingKey] = (counts[remainingKey] or 0) - 1
        end
    end
    return counts
end

local function HasRemainingSlotsForBloodlustAfterJoin(memberCounts)
    local counts = GetMemberCountsAfterJoin(memberCounts)
    return (counts.HEALER_REMAINING or 0) > 0 or (counts.DAMAGER_REMAINING or 0) > 0
end

local function SearchResultHasBloodlust(resultID, numMembers)
    local blClasses = { SHAMAN = true, EVOKER = true, HUNTER = true, MAGE = true }
    for i = 1, numMembers do
        local _, class = C_LFGList.GetSearchResultMemberInfo(resultID, i)
        if class and blClasses[class] then
            return true
        end
    end
    return false
end

-- Dungeon data
local NUM_DUNGEON_CHECKBOXES = 8
local CMID_MAP = {}
local function InitChallengeModes()
    if not C_ChallengeMode or not C_ChallengeMode.GetMapTable then return end
    local mapTable = C_ChallengeMode.GetMapTable()
    if not mapTable then return end
    for i, cmID in ipairs(mapTable) do
        local mapInfo = C_ChallengeMode.GetMapUIInfo(cmID)
        if mapInfo then
            CMID_MAP[i] = { cmID = cmID, name = mapInfo }
        end
    end
end

-- Main filter logic
function OrzUIPremadeFilter:DoFilter(results)
    if not CFG("Enable") then return results end
    if not results or #results == 0 then return results end

    local cfg = C.db[cfgKey]
    local filtered = {}
    for i = 1, #results do
        filtered[i] = results[i]
    end

    for i = #filtered, 1, -1 do
        local resultID = filtered[i]
        local searchResultInfo = C_LFGList.GetSearchResultInfo(resultID)
        if not searchResultInfo then
            tremove(filtered, i)
        else
            local pass = true
            local activityID = searchResultInfo.activityIDs and searchResultInfo.activityIDs[1] or searchResultInfo.activityID
            local activityInfo = C_LFGList.GetActivityInfoTable(activityID)
            local memberCounts = C_LFGList.GetSearchResultMemberCounts(resultID)
            local _, appStatus = C_LFGList.GetApplicationInfo(resultID)

            -- Difficulty
            if cfg.Difficulty and cfg.Difficulty > 0 and pass then
                local diffKey = DIFFICULTY_MAP[cfg.Difficulty]
                if diffKey == "normal" then
                    pass = activityInfo and activityInfo.isNormalModeActivity
                elseif diffKey == "heroic" then
                    pass = activityInfo and activityInfo.isHeroicModeActivity
                elseif diffKey == "mythic" then
                    pass = activityInfo and activityInfo.isMythicModeActivity and not activityInfo.isMythicPlusActivity
                elseif diffKey == "mythicplus" then
                    pass = activityInfo and activityInfo.isMythicPlusActivity
                end
            end

            -- Mythic+ Rating (mprating)
            local mprating = searchResultInfo.leaderOverallDungeonScore or 0
            if cfg.MPRatingMin and cfg.MPRatingMin > 0 and pass then
                pass = mprating >= cfg.MPRatingMin
            end
            if cfg.MPRatingMax and cfg.MPRatingMax > 0 and pass then
                pass = mprating <= cfg.MPRatingMax
            end

            -- Members
            if cfg.MembersMin and cfg.MembersMin > 0 and pass then
                pass = searchResultInfo.numMembers >= cfg.MembersMin
            end
            if cfg.MembersMax and cfg.MembersMax > 0 and pass then
                pass = searchResultInfo.numMembers <= cfg.MembersMax
            end

            -- Age
            if cfg.AgeMax and cfg.AgeMax > 0 and pass then
                pass = (searchResultInfo.age or 0) <= cfg.AgeMax * 60
            end

            -- Tanks
            local tanks = memberCounts.TANK or 0
            if cfg.TanksMin and cfg.TanksMin > 0 and pass then
                pass = tanks >= cfg.TanksMin
            end
            if cfg.TanksMax and cfg.TanksMax > 0 and pass then
                pass = tanks <= cfg.TanksMax
            end

            -- Heals
            local heals = memberCounts.HEALER or 0
            if cfg.HealsMin and cfg.HealsMin > 0 and pass then
                pass = heals >= cfg.HealsMin
            end
            if cfg.HealsMax and cfg.HealsMax > 0 and pass then
                pass = heals <= cfg.HealsMax
            end

            -- DPS
            local dps = (memberCounts.DAMAGER or 0) + (memberCounts.NOROLE or 0)
            if cfg.DPSMin and cfg.DPSMin > 0 and pass then
                pass = dps >= cfg.DPSMin
            end
            if cfg.DPSMax and cfg.DPSMax > 0 and pass then
                pass = dps <= cfg.DPSMax
            end

            -- Party fit
            if cfg.PartyFit and pass then
                pass = HasRemainingSlotsForLocalPlayerPartyRoles(memberCounts)
            end

            -- BL fit (if player/group does not have BL, filter for groups that will have BL after join)
            if cfg.BLFit and pass then
                if not PlayerOrGroupHasBloodlust() then
                    pass = HasRemainingSlotsForBloodlustAfterJoin(memberCounts)
                end
            end

            -- Not declined
            if cfg.NotDeclined and pass then
                local declined = appStatus == "declined" or appStatus == "declined_delisted" or appStatus == "declined_full"
                if not declined and LFGListFrame.declines and searchResultInfo.partyGUID then
                    declined = not not LFGListFrame.declines[searchResultInfo.partyGUID]
                end
                pass = not declined
            end

            -- Dungeon selection
            local hasDungeonFilter = false
            local dungeonMatch = false
            for d = 1, NUM_DUNGEON_CHECKBOXES do
                if cfg["Dungeon" .. d] then
                    hasDungeonFilter = true
                    if CMID_MAP[d] then
                        local dungeonName = CMID_MAP[d].name
                        local activityName = activityInfo and activityInfo.fullName or ""
                        if activityName:find(dungeonName) then
                            dungeonMatch = true
                        end
                    end
                end
            end
            if hasDungeonFilter and pass then
                pass = dungeonMatch
            end

            if not pass then
                tremove(filtered, i)
            end
        end
    end

    return filtered
end

-- Hook search result update
local function OnUpdateResultList(self)
    if not CFG("Enable") then return end
    if not self.results or #self.results == 0 then return end

    local filtered = OrzUIPremadeFilter:DoFilter(self.results)
    if #filtered ~= #self.results then
        self.results = filtered
        self.totalResults = #filtered
        LFGListSearchPanel_UpdateResults(self)
    end
end

hooksecurefunc("LFGListSearchPanel_UpdateResultList", OnUpdateResultList)

-- UI Helpers
local filterPanel
local controls = {}
local originalPVEWidth
local PANEL_WIDTH = 360
local layoutPending

local function RefreshSearch()
    local searchPanel = LFGListFrame.SearchPanel
    if searchPanel and searchPanel:IsVisible() then
        if searchPanel.results and #searchPanel.results > 0 then
            local filtered = OrzUIPremadeFilter:DoFilter(searchPanel.results)
            if #filtered ~= #searchPanel.results then
                searchPanel.results = filtered
                searchPanel.totalResults = #filtered
                LFGListSearchPanel_UpdateResults(searchPanel)
            end
        end
        LFGListSearchPanel_DoSearch(searchPanel)
    end
end

local ROW_HEIGHT = 30

local function CreateMinMaxRow(parent, label, key, y)
    local fs = B.CreateFS(parent, 14, label, false, "TOPLEFT", 15, -y)

    local minBox = B.CreateEditBox(parent, 40, 22)
    minBox:SetPoint("TOPLEFT", 90, -y+2)
    minBox:SetText(CFG(key .. "Min") or "")
    minBox:SetScript("OnTextChanged", function(self)
        local val = tonumber(self:GetText())
        CFG(key .. "Min", val or 0)
        if CFG("Enable") then RefreshSearch() end
    end)

    B.CreateFS(parent, 14, "-", false, "TOPLEFT", 134, -y)

    local maxBox = B.CreateEditBox(parent, 40, 22)
    maxBox:SetPoint("TOPLEFT", 144, -y + 2)
    maxBox:SetText(CFG(key .. "Max") or "")
    maxBox:SetScript("OnTextChanged", function(self)
        local val = tonumber(self:GetText())
        CFG(key .. "Max", val or 0)
        if CFG("Enable") then RefreshSearch() end
    end)

    controls[key .. "Min"] = minBox
    controls[key .. "Max"] = maxBox
    return y + ROW_HEIGHT
end

local function CreateEditRow(parent, label, key, y, width, labelWidth, valueFunc, tooltipText)
    width = width or 90
    labelWidth = labelWidth or 90
    local labelFS = B.CreateFS(parent, 14, label, false, "TOPLEFT", 15, -y)

    local editBox = B.CreateEditBox(parent, width, 22)
    editBox:SetPoint("TOPLEFT", labelWidth, -y + 2)
    editBox:SetText(CFG(key) or "")
    editBox:SetScript("OnTextChanged", function(self)
        local text = self:GetText()
        CFG(key, valueFunc and valueFunc(text) or text)
        if CFG("Enable") then RefreshSearch() end
    end)

    if tooltipText then
        editBox:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:AddLine(label, 1, .82, 0)
            GameTooltip:AddLine(tooltipText, 1, 1, 1, true)
            GameTooltip:Show()
        end)
        editBox:SetScript("OnLeave", GameTooltip_Hide)

        labelFS:SetScript("OnEnter", function(self)
            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
            GameTooltip:AddLine(label, 1, .82, 0)
            GameTooltip:AddLine(tooltipText, 1, 1, 1, true)
            GameTooltip:Show()
        end)
        labelFS:SetScript("OnLeave", GameTooltip_Hide)
    end

    controls[key] = editBox
    return y + ROW_HEIGHT
end

local function CreateCheckOption(parent, label, key, y, xOffset)
    xOffset = xOffset or 10
    local cb = B.CreateCheckBox(parent)
    cb:SetPoint("TOPLEFT", xOffset, -y)
    cb:SetChecked(CFG(key) or false)
    cb:SetScript("OnClick", function(self)
        CFG(key, self:GetChecked())
        if CFG("Enable") then RefreshSearch() end
    end)
    B.ReskinCheck(cb)
    B.CreateFS(cb, 14, label, false, "LEFT", 22, 0)
    controls[key] = cb
    return y + ROW_HEIGHT
end

local function UpdatePanelFromConfig()
    for key, widget in pairs(controls) do
        if widget.Type == "CheckBox" then
            widget:SetChecked(CFG(key) or false)
        elseif widget.Type == "EditBox" then
            widget:SetText(CFG(key) or "")
        elseif widget.Type == "DropDown" then
            local val = CFG(key) or 0
            if widget.options[val] then
                widget.Text:SetText(widget.options[val].text)
                for i, opt in ipairs(widget.options) do
                    if i == val then
                        opt:SetBackdropColor(1, .8, 0, .3)
                        opt.selected = true
                    else
                        opt:SetBackdropColor(0, 0, 0, .3)
                        opt.selected = false
                    end
                end
            end
        end
    end
end

-- Dungeon strip prefixes
local stripPrefixes = {
    "^Dawn of the Infinite: ",
    "^Tazavesh: ",
    "^Operation: ",
    "^The ",
    "^Der ", "^Die ", "^Das ",
    "^El ", "^La ", "^Los ", "^Las ",
}

local function GetShortDungeonName(name)
    local short = name
    for _, prefix in ipairs(stripPrefixes) do
        short = short:gsub(prefix, "", 1)
    end
    return short
end

local function CreateFilterPanel()
    filterPanel = CreateFrame("Frame", "OrzUIPremadeFilterPanel", PVEFrame, "BackdropTemplate")
    filterPanel:SetSize(PANEL_WIDTH, PVEFrame:GetHeight() - 28)
    filterPanel:SetPoint("TOPRIGHT", PVEFrame, "TOPRIGHT", -4, -24)
    filterPanel.bg = CreateFrame("Frame", nil, filterPanel, "BackdropTemplate")
    filterPanel.bg:SetPoint("TOPLEFT", 0, -32)
    filterPanel.bg:SetPoint("BOTTOMRIGHT", 0, 20)
    filterPanel.bg:SetBackdrop({
        bgFile = "Interface\\FrameGeneral\\UI-Background-Marble",
        edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
        tile = true, tileSize = 256, edgeSize = 16,
        insets = { left = 4, right = 4, top = 4, bottom = 4 }
    })
    filterPanel.bg:SetBackdropColor(1, 1, 1, 1)
    filterPanel.bg:SetFrameLevel(math.max(filterPanel:GetFrameLevel() - 1, 0))
    filterPanel:Hide()

    -- Title
    local title = B.CreateFS(filterPanel, 15, "预创建队伍过滤", true, "TOPLEFT", 8, -12)
    title:SetTextColor(1, 0.82, 0, 1)

    -- Enable checkbox (same row as title, right side)
    local enableCB = B.CreateCheckBox(filterPanel)
    enableCB:SetPoint("TOPRIGHT", filterPanel, "TOPRIGHT", -8, -10)
    enableCB:SetChecked(CFG("Enable") or false)
    enableCB:SetScript("OnClick", function(self)
        CFG("Enable", self:GetChecked())
        if self:GetChecked() then RefreshSearch() end
    end)
    B.ReskinCheck(enableCB)
    B.CreateFS(enableCB, 14, "启用过滤", false, "RIGHT", -22, 0)
    controls["Enable"] = enableCB

    local y = 60

    -- Difficulty dropdown
    B.CreateFS(filterPanel, 14, "难度", false, "TOPLEFT", 15, -y)
    local diffData = { [0] = "全部", "普通", "英雄", "史诗", "史诗钥石" }
    local diffDD = B.CreateDropDown(filterPanel, 100, 24, diffData)
    diffDD:SetPoint("TOPLEFT", 70, -y + 5)
    diffDD.Text:SetText(diffData[CFG("Difficulty") or 0])
    for i, opt in ipairs(diffDD.options) do
        opt:SetScript("OnClick", function(self)
            for j, o in ipairs(diffDD.options) do
                o:SetBackdropColor(0, 0, 0, .3)
                o.selected = false
            end
            self:SetBackdropColor(1, .8, 0, .3)
            self.selected = true
            diffDD.Text:SetText(self.text)
            CFG("Difficulty", self.index)
            diffDD.button.__list:Hide()
            if CFG("Enable") then RefreshSearch() end
        end)
    end
    controls["Difficulty"] = diffDD
    y = y + 38

    -- Min/Max rows
    y = CreateMinMaxRow(filterPanel, "钥石评分", "MPRating", y)
    y = CreateMinMaxRow(filterPanel, "队伍人数", "Members", y)
    y = CreateEditRow(filterPanel, "组建时间", "AgeMax", y, 40, 90, function(text) return tonumber(text) or 0 end, "单位：分钟。填写大于 0 的数值后，隐藏组建时间超过该分钟数的队伍；填写 0 或留空则不按组建时间过滤。")
    y = CreateMinMaxRow(filterPanel, "坦克", "Tanks", y)
    y = CreateMinMaxRow(filterPanel, "治疗", "Heals", y)
    y = CreateMinMaxRow(filterPanel, "输出", "DPS", y)

    -- Checkboxes
    y = y + 4
    y = CreateCheckOption(filterPanel, "适配职责", "PartyFit", y, 10)

    y = CreateCheckOption(filterPanel, "需要嗜血", "BLFit", y, 10)

    y = CreateCheckOption(filterPanel, "未被拒绝", "NotDeclined", y, 10)

    -- Dungeons section (right side)
    local dungeonTitle = B.CreateFS(filterPanel, 15, "地下城", true, "TOPLEFT", 220, -60)
    dungeonTitle:SetTextColor(1, 0.82, 0, 1)

    local dungeonY = 90
    for i = 1, NUM_DUNGEON_CHECKBOXES do
        local cb = B.CreateCheckBox(filterPanel)
        cb:SetPoint("TOPLEFT", 220, -dungeonY)
        cb:SetScript("OnClick", function(self)
            CFG("Dungeon" .. i, self:GetChecked())
            if CFG("Enable") then RefreshSearch() end
        end)
        B.ReskinCheck(cb)
        local fs = B.CreateFS(cb, 14, "...", false, "LEFT", 22, 0)
        fs:SetWidth(130)
        fs:SetJustifyH("LEFT")
        controls["Dungeon" .. i] = cb
        controls["Dungeon" .. i .. "Text"] = fs
        dungeonY = dungeonY + ROW_HEIGHT
    end

    -- Reset / Refresh buttons (bottom), Blizzard default red style
    local resetBtn = CreateFrame("Button", nil, filterPanel, "UIPanelButtonTemplate")
    resetBtn:SetSize(90, 22)
    resetBtn:SetText("重置")
    resetBtn:SetPoint("BOTTOMLEFT", 15, 0)
    resetBtn:SetScript("OnClick", function()
        CFG("Enable", true)
        CFG("Difficulty", 0)
        CFG("MPRatingMin", 0)
        CFG("MPRatingMax", 0)
        CFG("MembersMin", 0)
        CFG("MembersMax", 0)
        CFG("AgeMax", 0)
        CFG("TanksMin", 0)
        CFG("TanksMax", 0)
        CFG("HealsMin", 0)
        CFG("HealsMax", 0)
        CFG("DPSMin", 0)
        CFG("DPSMax", 0)
        CFG("PartyFit", false)
        CFG("BLFit", false)
        CFG("NotDeclined", false)
        for i = 1, NUM_DUNGEON_CHECKBOXES do
            CFG("Dungeon" .. i, false)
        end
        UpdatePanelFromConfig()
        RefreshSearch()
    end)

    local refreshBtn = CreateFrame("Button", nil, filterPanel, "UIPanelButtonTemplate")
    refreshBtn:SetSize(90, 22)
    refreshBtn:SetText("刷新")
    refreshBtn:SetPoint("BOTTOMRIGHT", -15, 0)
    refreshBtn:SetScript("OnClick", RefreshSearch)

    -- Update dungeon names
    filterPanel:SetScript("OnShow", function()
        for i = 1, NUM_DUNGEON_CHECKBOXES do
            if CMID_MAP[i] then
                controls["Dungeon" .. i .. "Text"]:SetText(GetShortDungeonName(CMID_MAP[i].name))
            end
        end
    end)
end

-- Toggle panel visibility and adjust PVEFrame width
local function ShouldShowPanel()
    return PVEFrame:IsShown()
        and LFGListFrame.activePanel == LFGListFrame.SearchPanel
        and LFGListFrame.SearchPanel:IsShown()
end

local function ApplyPanelLayout(show)
    if not filterPanel then return end
    -- 战斗或 PVEFrame 处于安全路径时直接跳过，避免 ADDON_ACTION_BLOCKED
    if InCombatLockdown() then return end

    if show then
        if not originalPVEWidth then
            originalPVEWidth = PVEFrame:GetWidth()
        end
        PVEFrame:SetWidth(originalPVEWidth + PANEL_WIDTH)
        filterPanel:SetHeight(PVEFrame:GetHeight() - 28)
        filterPanel:ClearAllPoints()
        filterPanel:SetPoint("TOPRIGHT", PVEFrame, "TOPRIGHT", -4, -24)
        filterPanel:Show()
    else
        if originalPVEWidth then
            PVEFrame:SetWidth(originalPVEWidth)
        end
        filterPanel:Hide()
    end
end

local function TogglePanel()
    if layoutPending then return end
    layoutPending = true
    -- 延迟一帧以上，等暴雪安全路径执行完毕再修改 PVEFrame 尺寸
    C_Timer.After(0.1, function()
        layoutPending = nil
        ApplyPanelLayout(ShouldShowPanel())
    end)
end

-- Init
local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_LOGIN")
frame:SetScript("OnEvent", function(_, event)
    if event == "PLAYER_LOGIN" then
        InitChallengeModes()

        if not C.db[cfgKey] then C.db[cfgKey] = {} end
        local defaults = {
            Enable = true,
            Difficulty = 0,
            MPRatingMin = 0,
            MPRatingMax = 0,
            MembersMin = 0,
            MembersMax = 0,
            AgeMax = 0,
            TanksMin = 0,
            TanksMax = 0,
            HealsMin = 0,
            HealsMax = 0,
            DPSMin = 0,
            DPSMax = 0,
            PartyFit = false,
            BLFit = false,
            NeedsBL = false,
            NotDeclined = false,
        }
        for k, v in pairs(defaults) do
            if C.db[cfgKey][k] == nil then
                C.db[cfgKey][k] = v
            end
        end
        for i = 1, NUM_DUNGEON_CHECKBOXES do
            if C.db[cfgKey]["Dungeon" .. i] == nil then
                C.db[cfgKey]["Dungeon" .. i] = false
            end
        end

        CreateFilterPanel()

        hooksecurefunc("LFGListFrame_SetActivePanel", TogglePanel)
        PVEFrame:HookScript("OnShow", TogglePanel)
        PVEFrame:HookScript("OnHide", TogglePanel)
        if LFGListFrame.SearchPanel then
            LFGListFrame.SearchPanel:HookScript("OnShow", TogglePanel)
            LFGListFrame.SearchPanel:HookScript("OnHide", TogglePanel)
        end
    end
end)
