-- MythicMate "List a group": a leader-side window to CREATE/list your own premade group from
-- inside the addon (the counterpart to the LFG browser + the applicant sorter). Pairs with
-- MythicMate_Applicants.lua, which manages applicants once you're listed.
--
-- Listing uses C_LFGList.CreateListing({ activityIDs = {...}, ... }) which is HARDWARE-EVENT
-- protected (patch 7.2+), so the actual create call runs straight from the button's OnClick.
-- Activities come from C_LFGList.GetAvailableActivities (not protected), so we can enumerate them
-- freely to build the picker.
--
-- Split file so its locals get a fresh Lua chunk. Exposes MythicMateNS.ListGroup =
-- { Show, Hide, Toggle, IsListed }.

local MythicMateNS = _G.MythicMate
if not MythicMateNS then return end

------------------------------------------------------------
-- Small helpers
------------------------------------------------------------
local function accentRGB()
    if type(_G.MythicMate_GetLFGAccent) == "function" then
        local r, g, b = _G.MythicMate_GetLFGAccent()
        if r then return r, g, b end
    end
    return 0.66, 0.40, 1.0
end

local DUNGEON_CATEGORY = 2 -- Group Finder category id for Dungeons

-- Load Blizzard's Group Finder so C_LFGList activity data is fully populated (load-on-demand addon).
local function ensureGroupFinderLoaded()
    if _G.LFGListFrame then return end
    local load = (C_AddOns and C_AddOns.LoadAddOn) or _G.LoadAddOn
    if type(load) == "function" then pcall(load, "Blizzard_GroupFinder") end
end

local function isInDungeon()
    if type(IsInInstance) ~= "function" then return false end
    local inInstance, instanceType = IsInInstance()
    if not inInstance then return false end
    return instanceType == "party" or instanceType == "raid" or instanceType == "scenario"
end

local function hasListing()
    return type(C_LFGList) == "table" and type(C_LFGList.HasActiveEntryInfo) == "function"
        and C_LFGList.HasActiveEntryInfo() and true or false
end

-- The keystone the player currently owns → { level, mapID, name }. Used to default the create
-- dialog to your own key (dungeon + level). Returns nil when you hold no keystone.
local function ownedKeystone()
    if type(C_MythicPlus) ~= "table" then return nil end
    local level = (type(C_MythicPlus.GetOwnedKeystoneLevel) == "function") and C_MythicPlus.GetOwnedKeystoneLevel() or nil
    if not level or level <= 0 then return nil end
    local mapID = nil
    if type(C_MythicPlus.GetOwnedKeystoneChallengeMapID) == "function" then
        mapID = C_MythicPlus.GetOwnedKeystoneChallengeMapID()
    elseif type(C_MythicPlus.GetOwnedKeystoneMapID) == "function" then
        mapID = C_MythicPlus.GetOwnedKeystoneMapID()
    end
    local name = nil
    if mapID and type(C_ChallengeMode) == "table" and type(C_ChallengeMode.GetMapUIInfo) == "function" then
        local ok, n = pcall(C_ChallengeMode.GetMapUIInfo, mapID)
        if ok and type(n) == "string" and n ~= "" then name = n end
    end
    return { level = level, mapID = mapID, name = name }
end

-- GENERAL playstyle (Enum.LFGEntryGeneralPlaystyle: Learning/FunRelaxed/FunSerious/Expert).
-- Since 12.0 CreateListing wants this in `generalPlaystyle`; the legacy `playstyle` field must be
-- Enum.LFGEntryPlaystyle.None or the server rejects the listing — silently, because CreateListing
-- returns true once the REQUEST is sent and the rejection only arrives later as a
-- LFG_LIST_ENTRY_CREATION_FAILED event. Labels match the browser filter's PLAYSTYLES
-- (MythicMate_PremadeFilter.lua). Default to Competitive per user preference.
local GENERAL_PS = (type(_G.Enum) == "table" and _G.Enum.LFGEntryGeneralPlaystyle) or {}
local PLAYSTYLES = {
    { GENERAL_PS.Learning or 1, "Learning" },
    { GENERAL_PS.FunRelaxed or 2, "Relaxed" },
    { GENERAL_PS.FunSerious or 3, "Competitive" },
    { GENERAL_PS.Expert or 4, "Carry offered" },
}
local DEFAULT_PLAYSTYLE = GENERAL_PS.FunSerious or 3
local function playstyleName(v)
    for _, ps in ipairs(PLAYSTYLES) do if ps[1] == v then return ps[2] end end
    return "Competitive"
end

------------------------------------------------------------
-- State
------------------------------------------------------------
local frame = nil
local MAX_ROWS = 9
local ROW_H = 22
local scrollOffset = 0
local activities = {}       -- sorted { id, name, mplus }
local selectedActivityID = nil
local selectedActivityName = nil
local currentPlaystyle = DEFAULT_PLAYSTYLE

local refreshDialog -- forward
local showDialog    -- forward (quickToggle falls back to the full dialog)

-- Enumerate the current dungeon activities (per-difficulty, per-dungeon). Names come from the
-- activity's fullName so the picker reads like Blizzard's own list. Mythic+ first, then A→Z.
local function collectActivities()
    for i = #activities, 1, -1 do activities[i] = nil end
    if type(C_LFGList) ~= "table" or type(C_LFGList.GetAvailableActivities) ~= "function" then return end
    local ok, ids = pcall(C_LFGList.GetAvailableActivities, DUNGEON_CATEGORY)
    if not ok or type(ids) ~= "table" then return end
    for _, id in ipairs(ids) do
        local info
        if type(C_LFGList.GetActivityInfoTable) == "function" then
            local okA, a = pcall(C_LFGList.GetActivityInfoTable, id)
            if okA and type(a) == "table" then info = a end
        end
        local name = (info and (info.fullName or info.shortName)) or ("Activity " .. tostring(id))
        activities[#activities + 1] = {
            id = id, name = tostring(name),
            mplus = (info and info.isMythicPlusActivity) and true or false,
        }
    end
    table.sort(activities, function(a, b)
        if a.mplus ~= b.mplus then return a.mplus end -- Mythic+ activities first
        return tostring(a.name):lower() < tostring(b.name):lower()
    end)
end

-- Loose name match so an activity like "Ara-Kara, City of Echoes" pairs with the keystone map
-- "Ara-Kara, City of Echoes" (ignore punctuation/spacing/case).
local function nameKey(s)
    return tostring(s or ""):lower():gsub("[^%a%d]", "")
end

-- The Mythic+ activity matching your OWNED keystone → id, name, keystone. Requires
-- collectActivities() to have run. Returns nil when you hold no key or no activity matches.
local function findKeystoneActivity()
    local ks = ownedKeystone()
    if not (ks and ks.name) then return nil, nil, ks end
    local target = nameKey(ks.name)
    if target == "" then return nil, nil, ks end
    for _, e in ipairs(activities) do
        if e.mplus then
            local an = nameKey(e.name)
            if an:find(target, 1, true) or target:find(an, 1, true) then
                return e.id, e.name, ks
            end
        end
    end
    return nil, nil, ks
end

-- The M+ activity for YOUR keystone. Candidates in order: Blizzard's own
-- GetOwnedKeystoneActivityAndGroupAndLevel (the call their entry-creation UI uses; plain then
-- timewalking), then name-matching the activity list as a last resort. When
-- GetKeystoneForActivity is available, the first candidate the game confirms you hold a
-- keystone for wins — that's the exact validity check Blizzard's Create button gates on, so a
-- confirmed ID can't be rejected for a keystone mismatch. Returns activityID, name, keyLevel.
local function keystoneActivity()
    local candidates = {}
    if type(C_LFGList) == "table"
        and type(C_LFGList.GetOwnedKeystoneActivityAndGroupAndLevel) == "function" then
        local ok, activityID, _groupID, level = pcall(C_LFGList.GetOwnedKeystoneActivityAndGroupAndLevel)
        if ok and activityID and activityID ~= 0 then
            candidates[#candidates + 1] = { id = activityID, level = tonumber(level) }
        end
        ok, activityID, _groupID, level = pcall(C_LFGList.GetOwnedKeystoneActivityAndGroupAndLevel, true)
        if ok and activityID and activityID ~= 0 then
            candidates[#candidates + 1] = { id = activityID, level = tonumber(level) }
        end
    end
    local mid, mname, ks = findKeystoneActivity()
    if mid then
        candidates[#candidates + 1] = { id = mid, name = mname, level = ks and ks.level }
    end
    if not candidates[1] then return nil end

    local chosen
    if type(C_LFGList) == "table" and type(C_LFGList.GetKeystoneForActivity) == "function" then
        for _, c in ipairs(candidates) do
            local okK, keyLvl = pcall(C_LFGList.GetKeystoneForActivity, c.id)
            if okK and keyLvl then
                c.level = c.level or tonumber(keyLvl)
                chosen = c
                break
            end
        end
    end
    chosen = chosen or candidates[1]

    local name = chosen.name
    if not name and type(C_LFGList) == "table" and type(C_LFGList.GetActivityInfoTable) == "function" then
        local okA, a = pcall(C_LFGList.GetActivityInfoTable, chosen.id)
        if okA and type(a) == "table" then name = a.fullName or a.shortName end
    end
    if not name then name = (ks and ks.name) or "your keystone" end
    return chosen.id, tostring(name), chosen.level or (ks and ks.level)
end

------------------------------------------------------------
-- Actions
------------------------------------------------------------
-- Forward decl: the create paths hand off to Blizzard's native panel (defined below), because
-- C_LFGList.CreateListing can't be driven from an addon (restricted API — every addon-initiated
-- call is rejected server-side no matter the payload; only Blizzard's own secure button lists).
local openBlizzardCreate

-- Hand the dialog's chosen activity to Blizzard's create panel and close our window.
local function createListing()
    if not selectedActivityID then
        print("|cffc868ffMythicMate:|r pick a dungeon/activity first.")
        return
    end
    print("|cffc868ffMythicMate:|r opening Premade Groups with |cffffd000"
        .. tostring(selectedActivityName or "your pick")
        .. "|r selected — set any requirements there, then click the green |cff40ff40List Group|r button.")
    if frame then frame:Hide() end
    openBlizzardCreate(selectedActivityID)
end

local function delist()
    if type(C_LFGList) == "table" and type(C_LFGList.RemoveListing) == "function" then
        pcall(C_LFGList.RemoveListing)
    end
    if refreshDialog then refreshDialog() end
end

------------------------------------------------------------
-- UI
------------------------------------------------------------
local function skinElvUI(f)
    local st = MythicMateDB and MythicMateDB.settings
    if st and st.useElvUISkinDefaults == false then return end
    if type(_G.MythicMate_GetElvUISkinsModule) ~= "function" then return end
    local okS, S = pcall(_G.MythicMate_GetElvUISkinsModule)
    if not okS or not S then return end
    pcall(function()
        if S.HandleFrame then S:HandleFrame(f) end
        if S.HandleCloseButton and f.close then S:HandleCloseButton(f.close) end
        if S.HandleButton then
            if f.listBtn then S:HandleButton(f.listBtn) end
            if f.cancelBtn then S:HandleButton(f.cancelBtn) end
            if f.delistBtn then S:HandleButton(f.delistBtn) end
            if f.appsBtn then S:HandleButton(f.appsBtn) end
        end
        if S.HandleCheckBox and f.autoAccept then S:HandleCheckBox(f.autoAccept) end
        if S.HandleButton and f.playstyleBtn then S:HandleButton(f.playstyleBtn) end
        if S.HandleEditBox then
            if f.note then S:HandleEditBox(f.note) end
            if f.ilvl then S:HandleEditBox(f.ilvl) end
            if f.score then S:HandleEditBox(f.score) end
            if f.keyLevel then S:HandleEditBox(f.keyLevel) end
        end
    end)
end

local function applyAccent()
    if not frame then return end
    local r, g, b = accentRGB()
    if frame.SetBackdropBorderColor then
        frame:SetBackdropBorderColor(r * 0.7 + 0.05, g * 0.7 + 0.05, b * 0.7 + 0.05, 0.95)
    end
    if frame.titleBar and frame.titleBar.SetGradient then
        frame.titleBar:SetGradient("VERTICAL",
            CreateColor(r * 0.45, g * 0.45, b * 0.45, 1), CreateColor(r * 0.14, g * 0.14, b * 0.14, 1))
    end
end

-- A small labelled numeric edit box.
local function makeNumBox(parent, label, x, y, width)
    local fs = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    fs:SetPoint("TOPLEFT", x, y)
    fs:SetText(label)
    local eb = CreateFrame("EditBox", nil, parent, "InputBoxTemplate")
    eb:SetSize(width or 60, 20)
    eb:SetPoint("TOPLEFT", x, y - 16)
    eb:SetAutoFocus(false)
    eb:SetNumeric(true)
    eb:SetText("0")
    eb:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
    return eb
end

local function buildFrame()
    if frame then return frame end

    local f = CreateFrame("Frame", "MythicMate_ListGroupFrame", UIParent, "BackdropTemplate")
    f:SetSize(380, 40 + MAX_ROWS * ROW_H + 262)
    f:SetPoint("CENTER", UIParent, "CENTER", 0, 40)
    f:SetFrameStrata("DIALOG")
    f:SetToplevel(true)
    f:EnableMouse(true)
    f:SetClampedToScreen(true)
    -- Remember where the user drags this window (persists across reloads).
    if type(_G.MythicMate_PersistFrame) == "function" then
        _G.MythicMate_PersistFrame(f, "listgroup")
    else
        f:SetMovable(true); f:RegisterForDrag("LeftButton")
        f:SetScript("OnDragStart", f.StartMoving); f:SetScript("OnDragStop", f.StopMovingOrSizing)
    end
    f:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8x8",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = true, tileSize = 16, edgeSize = 12,
        insets = { left = 3, right = 3, top = 3, bottom = 3 },
    })
    f:SetBackdropColor(0.035, 0.04, 0.075, 0.97)
    f:SetBackdropBorderColor(0.5, 0.4, 0.85, 0.95)

    f.titleBar = f:CreateTexture(nil, "ARTWORK")
    f.titleBar:SetPoint("TOPLEFT", 4, -4)
    f.titleBar:SetPoint("TOPRIGHT", -4, -4)
    f.titleBar:SetHeight(24)
    f.titleBar:SetTexture("Interface\\Buttons\\WHITE8x8")

    f.title = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    f.title:SetPoint("LEFT", f.titleBar, "LEFT", 10, 0)
    f.title:SetText("List a group")

    f.close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    f.close:SetPoint("TOPRIGHT", 2, 2)
    f.close:SetScript("OnClick", function() f:Hide() end)

    ------------------------------------------------------------
    -- CREATE view (shown when you have no active listing)
    ------------------------------------------------------------
    f.createGroup = CreateFrame("Frame", nil, f)
    f.createGroup:SetPoint("TOPLEFT", 0, -30)
    f.createGroup:SetPoint("BOTTOMRIGHT", 0, 0)
    local cg = f.createGroup

    local pick = cg:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    pick:SetPoint("TOPLEFT", 12, -6)
    pick:SetTextColor(1, 0.82, 0)
    pick:SetText("Choose a dungeon / difficulty")

    -- Activity list (scroll via mousewheel).
    f.rows = {}
    local ry = -26
    for i = 1, MAX_ROWS do
        local row = CreateFrame("Button", nil, cg)
        row:SetPoint("TOPLEFT", 10, ry)
        row:SetPoint("TOPRIGHT", -10, ry)
        row:SetHeight(ROW_H)
        row.bg = row:CreateTexture(nil, "BACKGROUND")
        row.bg:SetAllPoints(row)
        row.bg:SetColorTexture(1, 1, 1, (i % 2 == 0) and 0.05 or 0.015)
        row.sel = row:CreateTexture(nil, "ARTWORK")
        row.sel:SetAllPoints(row)
        row.sel:SetColorTexture(0.5, 0.4, 1.0, 0.28)
        row.sel:Hide()
        row.hl = row:CreateTexture(nil, "HIGHLIGHT")
        row.hl:SetAllPoints(row)
        row.hl:SetColorTexture(1, 1, 1, 0.10)
        row.label = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        row.label:SetPoint("LEFT", 8, 0)
        row.label:SetPoint("RIGHT", -8, 0)
        row.label:SetJustifyH("LEFT")
        if row.label.SetWordWrap then row.label:SetWordWrap(false) end
        row:SetScript("OnClick", function(self)
            if self._id then
                selectedActivityID = self._id
                selectedActivityName = self._name
                if refreshDialog then refreshDialog() end
            end
        end)
        row:Hide()
        f.rows[i] = row
        ry = ry - ROW_H
    end

    f.emptyMsg = cg:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    f.emptyMsg:SetPoint("TOP", cg, "TOP", 0, -26 - (MAX_ROWS * ROW_H) / 2)
    f.emptyMsg:SetText("Open the LFG tab once so the game loads the activity list, then reopen this.")
    f.emptyMsg:Hide()

    local optsY = -26 - MAX_ROWS * ROW_H - 8

    -- Note / comment.
    local noteLbl = cg:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    noteLbl:SetPoint("TOPLEFT", 12, optsY)
    noteLbl:SetText("Note")
    f.note = CreateFrame("EditBox", nil, cg, "InputBoxTemplate")
    f.note:SetSize(344, 20)
    f.note:SetPoint("TOPLEFT", 16, optsY - 16)
    f.note:SetAutoFocus(false)
    f.note:SetMaxLetters(60)
    f.note:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)

    -- Row A: requirements + your key level (auto-filled from your owned keystone; editable).
    f.ilvl = makeNumBox(cg, "Min ilvl", 16, optsY - 46, 66)
    f.score = makeNumBox(cg, "Min Rio", 92, optsY - 46, 66)
    f.keyLevel = makeNumBox(cg, "Key lvl", 168, optsY - 46, 50)

    -- Row B: auto-accept + playstyle (defaults to Competitive; click to cycle).
    f.autoAccept = CreateFrame("CheckButton", nil, cg, "UICheckButtonTemplate")
    f.autoAccept:SetSize(22, 22)
    f.autoAccept:SetPoint("TOPLEFT", 16, optsY - 92)
    f.autoAccept.text = f.autoAccept:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    f.autoAccept.text:SetPoint("LEFT", f.autoAccept, "RIGHT", 2, 0)
    f.autoAccept.text:SetText("Auto-accept")

    local psLbl = cg:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    psLbl:SetPoint("TOPLEFT", 168, optsY - 90)
    psLbl:SetText("Playstyle")
    f.playstyleBtn = CreateFrame("Button", nil, cg, "UIPanelButtonTemplate")
    f.playstyleBtn:SetSize(120, 22)
    f.playstyleBtn:SetPoint("TOPLEFT", 168, optsY - 106)
    f.playstyleBtn:SetText(playstyleName(currentPlaystyle))
    f.playstyleBtn:SetScript("OnClick", function(self)
        -- Cycle Learning → Relaxed → Competitive → (wrap).
        local idx = 1
        for i, ps in ipairs(PLAYSTYLES) do if ps[1] == currentPlaystyle then idx = i break end end
        idx = (idx % #PLAYSTYLES) + 1
        currentPlaystyle = PLAYSTYLES[idx][1]
        self:SetText(playstyleName(currentPlaystyle))
    end)

    f.listBtn = CreateFrame("Button", nil, cg, "UIPanelButtonTemplate")
    f.listBtn:SetSize(120, 24)
    f.listBtn:SetPoint("BOTTOMLEFT", 14, 12)
    f.listBtn:SetText("List Group")
    f.listBtn:SetScript("OnClick", createListing) -- hardware event → CreateListing allowed

    f.cancelBtn = CreateFrame("Button", nil, cg, "UIPanelButtonTemplate")
    f.cancelBtn:SetSize(90, 24)
    f.cancelBtn:SetPoint("BOTTOMRIGHT", -14, 12)
    f.cancelBtn:SetText("Cancel")
    f.cancelBtn:SetScript("OnClick", function() f:Hide() end)

    ------------------------------------------------------------
    -- LISTED view (shown when you already have an active listing)
    ------------------------------------------------------------
    f.listedGroup = CreateFrame("Frame", nil, f)
    f.listedGroup:SetPoint("TOPLEFT", 0, -30)
    f.listedGroup:SetPoint("BOTTOMRIGHT", 0, 0)
    local lg = f.listedGroup

    f.listedText = lg:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    f.listedText:SetPoint("TOP", lg, "TOP", 0, -20)
    f.listedText:SetPoint("LEFT", lg, "LEFT", 16, 0)
    f.listedText:SetPoint("RIGHT", lg, "RIGHT", -16, 0)
    f.listedText:SetJustifyH("CENTER")

    f.appsBtn = CreateFrame("Button", nil, lg, "UIPanelButtonTemplate")
    f.appsBtn:SetSize(150, 26)
    f.appsBtn:SetPoint("TOP", lg, "TOP", 0, -70)
    f.appsBtn:SetText("Open applicants")
    f.appsBtn:SetScript("OnClick", function()
        if MythicMateNS.Applicants and type(MythicMateNS.Applicants.Show) == "function" then
            pcall(MythicMateNS.Applicants.Show)
        end
    end)

    f.delistBtn = CreateFrame("Button", nil, lg, "UIPanelButtonTemplate")
    f.delistBtn:SetSize(150, 26)
    f.delistBtn:SetPoint("TOP", f.appsBtn, "BOTTOM", 0, -10)
    f.delistBtn:SetText("Delist group")
    f.delistBtn:SetScript("OnClick", delist)

    f:EnableMouseWheel(true)
    f:SetScript("OnMouseWheel", function(_, delta)
        local maxOff = math.max(0, #activities - MAX_ROWS)
        scrollOffset = math.max(0, math.min(maxOff, scrollOffset - (delta or 0) * 2))
        if refreshDialog then refreshDialog() end
    end)

    frame = f
    applyAccent()
    skinElvUI(f)
    f:Hide()
    return f
end

function refreshDialog()
    local f = buildFrame()
    if not f then return end
    applyAccent()

    if hasListing() then
        f.createGroup:Hide()
        f.listedGroup:Show()
        local name = "your group"
        if type(C_LFGList) == "table" and type(C_LFGList.GetActiveEntryInfo) == "function" then
            local ok, info = pcall(C_LFGList.GetActiveEntryInfo)
            if ok and type(info) == "table" then
                name = (info.name and info.name ~= "" and info.name) or name
            end
        end
        f.listedText:SetText("You are listed:\n|cffffd000" .. tostring(name) .. "|r")
        return
    end

    f.listedGroup:Hide()
    f.createGroup:Show()

    local maxOff = math.max(0, #activities - MAX_ROWS)
    if scrollOffset > maxOff then scrollOffset = maxOff end
    f.emptyMsg:SetShown(#activities == 0)

    for i = 1, MAX_ROWS do
        local row = f.rows[i]
        local e = activities[scrollOffset + i]
        if e then
            row._id = e.id
            row._name = e.name
            local txt = e.name
            if e.mplus then txt = "|cff66ff66[M+]|r " .. txt end
            row.label:SetText(txt)
            row.sel:SetShown(selectedActivityID == e.id)
            row:Show()
        else
            row._id = nil
            row:Hide()
        end
    end

    if f.listBtn then
        f.listBtn:SetEnabled(selectedActivityID ~= nil and not isInDungeon())
    end
end

------------------------------------------------------------
-- Show / hide / toggle
------------------------------------------------------------
function showDialog()
    ensureGroupFinderLoaded()
    collectActivities()

    -- Default to your OWNED keystone: Blizzard's own keystone-activity API picks the exact
    -- activity, and the key level is prefilled. Falls back to keeping any still-valid selection,
    -- else the first activity.
    local autoID, autoName, ksLevel = keystoneActivity()
    if autoID then
        selectedActivityID, selectedActivityName = autoID, autoName
    else
        local stillValid = false
        for _, e in ipairs(activities) do if e.id == selectedActivityID then stillValid = true break end end
        if not stillValid then
            selectedActivityID = activities[1] and activities[1].id or nil
            selectedActivityName = activities[1] and activities[1].name or nil
        end
    end

    -- Playstyle defaults to Competitive every time the window opens (user can cycle it).
    currentPlaystyle = DEFAULT_PLAYSTYLE

    local f = buildFrame()
    if not f then return end
    if f.playstyleBtn then f.playstyleBtn:SetText(playstyleName(currentPlaystyle)) end
    if f.keyLevel then f.keyLevel:SetText(tostring(ksLevel or 0)) end

    -- Scroll the auto-selected activity into view.
    scrollOffset = 0
    if selectedActivityID then
        for i, e in ipairs(activities) do
            if e.id == selectedActivityID then
                local maxOff = math.max(0, #activities - MAX_ROWS)
                scrollOffset = math.min(maxOff, math.max(0, i - 1))
                break
            end
        end
    end

    refreshDialog()
    f:Show()
    f:Raise()
end

local function hideDialog()
    if frame then frame:Hide() end
end

local function toggleDialog()
    if frame and frame:IsShown() then hideDialog() else showDialog() end
end

-- Open Blizzard's OWN Premade Groups create panel, pre-selected to your keystone activity, with
-- playstyle defaulted to Competitive. With autoList=true, we then call Blizzard's own
-- LFGListEntryCreation_ListGroup (the exact function their green button runs) from within our
-- hardware event — the panel is fully set up by that point, so this lists directly. If the game
-- still refuses it (hardware-event/taint), the panel stays open fully filled so you can press the
-- green List Group button yourself.
function openBlizzardCreate(activityID, autoList)
    ensureGroupFinderLoaded()
    local catID = _G.GROUP_FINDER_CATEGORY_ID_DUNGEONS or DUNGEON_CATEGORY

    if type(_G.PVEFrame_ShowFrame) == "function" then
        pcall(_G.PVEFrame_ShowFrame, "GroupFinderFrame", _G.LFGListPVEStub)
    end
    local lf = _G.LFGListFrame
    if not lf or not lf.EntryCreation then
        print("|cffc868ffMythicMate:|r couldn't open the Group Finder — open Premade Groups manually.")
        return false
    end

    -- Category → Dungeons, then show the entry-creation panel.
    if lf.CategorySelection and type(_G.LFGListCategorySelection_SelectCategory) == "function" then
        pcall(_G.LFGListCategorySelection_SelectCategory, lf.CategorySelection, catID, nil)
    end
    if type(_G.LFGListEntryCreation_Show) == "function" then
        local pveFilter = (_G.Enum and _G.Enum.LFGListFilter and _G.Enum.LFGListFilter.PvE)
            or (_G.LE_LFG_LIST_FILTER_PVE) or 1
        pcall(_G.LFGListEntryCreation_Show, lf.EntryCreation, pveFilter, catID, nil)
    end

    -- Pre-select the activity. An explicit activityID (from our picker) wins; otherwise fall back
    -- to your owned keystone (plain key, then timewalking), exactly like Blizzard's SetEditMode.
    local groupID
    if not activityID and type(C_LFGList.GetOwnedKeystoneActivityAndGroupAndLevel) == "function" then
        local ok, a, g = pcall(C_LFGList.GetOwnedKeystoneActivityAndGroupAndLevel)
        if not (ok and a) then ok, a, g = pcall(C_LFGList.GetOwnedKeystoneActivityAndGroupAndLevel, true) end
        if ok and a then activityID, groupID = a, g end
    end
    if activityID and type(_G.LFGListEntryCreation_Select) == "function" then
        pcall(_G.LFGListEntryCreation_Select, lf.EntryCreation,
            lf.EntryCreation.selectedFilters, catID, groupID, activityID)
    end

    -- Default the playstyle to Competitive (Enum.LFGEntryGeneralPlaystyle.FunSerious).
    local ec = lf.EntryCreation
    local competitive = (_G.Enum and _G.Enum.LFGEntryGeneralPlaystyle
        and _G.Enum.LFGEntryGeneralPlaystyle.FunSerious)
    if competitive and type(_G.LFGListEntryCreation_OnPlayStyleSelectedInternal) == "function" then
        pcall(_G.LFGListEntryCreation_OnPlayStyleSelectedInternal, ec, competitive)
        -- Reflect the choice in the dropdown text.
        if ec.PlayStyleDropdown then
            if type(ec.PlayStyleDropdown.GenerateMenu) == "function" then
                pcall(function() ec.PlayStyleDropdown:GenerateMenu() end)
            elseif type(ec.PlayStyleDropdown.Update) == "function" then
                pcall(function() ec.PlayStyleDropdown:Update() end)
            end
        end
    end

    -- Auto-list: run Blizzard's own list function directly. Same call their green button makes,
    -- and we're still inside the toolbar-click hardware event, so the CreateListing inside it has
    -- a valid hardware event. Wrapped in pcall; on failure the filled panel remains for a manual
    -- click.
    if autoList and type(_G.LFGListEntryCreation_ListGroup) == "function" then
        local ok = pcall(_G.LFGListEntryCreation_ListGroup, ec)
        if ok and type(C_Timer) == "table" and type(C_Timer.After) == "function" then
            C_Timer.After(0.5, function()
                if hasListing() then
                    -- Listed successfully — close Blizzard's window; the applicant sorter opens
                    -- via our LFG_LIST_ACTIVE_ENTRY_UPDATE handler.
                    if _G.PVEFrame and _G.PVEFrame:IsShown() and type(_G.HideUIPanel) == "function" then
                        pcall(_G.HideUIPanel, _G.PVEFrame)
                    end
                else
                    print("|cffc868ffMythicMate:|r couldn't auto-list — press the green"
                        .. " |cff40ff40List Group|r button in the open window.")
                end
            end)
        end
    end
    return true
end

-- "List Key": already listed → delist (RemoveListing is not restricted). Not listed → open
-- Blizzard's create panel pre-filled with your key; you press their green List button once.
-- MUST run from a real hardware event (toolbar click / slash+Enter).
local function quickToggle()
    if hasListing() then
        delist()
        print("|cffc868ffMythicMate:|r group delisted.")
        return
    end
    print("|cffc868ffMythicMate:|r listing your key as |cffffd000Competitive|r...")
    openBlizzardCreate(nil, true) -- autoList
end

-- Public wrapper: surface ANY runtime error to chat. OnClick script errors are silently
-- swallowed when scriptErrors is off, which reads as "the button does nothing".
local function quickToggleSafe()
    local ok, err = pcall(quickToggle)
    if not ok then
        print("|cffc868ffMythicMate:|r quick-list error: |cffff6060" .. tostring(err) .. "|r")
    end
end

------------------------------------------------------------
-- Keep the window in sync when the listing state changes.
------------------------------------------------------------
local evt = CreateFrame("Frame")
evt:RegisterEvent("LFG_LIST_ACTIVE_ENTRY_UPDATE")
evt:SetScript("OnEvent", function()
    -- When the listing goes live via Blizzard's panel, refresh our window and pop the applicant
    -- sorter so the addon flow continues.
    if frame and frame:IsShown() then refreshDialog() end
    if hasListing() and MythicMateNS.Applicants and type(MythicMateNS.Applicants.Show) == "function"
        and type(C_Timer) == "table" and type(C_Timer.After) == "function" then
        C_Timer.After(0.3, function() if hasListing() then pcall(MythicMateNS.Applicants.Show) end end)
    end
end)

------------------------------------------------------------
-- Slash + public interface
------------------------------------------------------------
_G.SLASH_MYTHICMATELIST1 = "/wllist"
_G.SlashCmdList = _G.SlashCmdList or {}
_G.SlashCmdList["MYTHICMATELIST"] = function() toggleDialog() end

MythicMateNS.ListGroup = {
    Show = showDialog,
    Hide = hideDialog,
    Toggle = toggleDialog,
    IsListed = hasListing,
    QuickToggle = quickToggleSafe,
}
