---@diagnostic disable: undefined-field, undefined-global
local _, namespace = ...

local Theme = namespace.Theme

-- Approximate line height of the WildThingsFont13 title label EditBox/Dropdown
-- render above themselves (Widgets/EditBox.lua:65-69, Widgets/Dropdown.lua:23-28).
-- Neither widget exposes that label's own frame height, so this is a fixed
-- reserved-space constant rather than a measured value -- a cosmetic follow-up
-- if the real font metrics ever drift from this estimate.
local TITLE_H = 16

-- WR-04 review fix: GetInviteGuildRanks() reads live guild data independent of the
-- by_whisper/autopromote toggles, but Shell.lua's ShowScreen builds a screen once and
-- caches the frame forever -- so "Guild ranks unavailable" got baked in permanently if
-- the screen was opened before GetNumGuildMembers()/GuildControlGetNumRanks() had data
-- (Pitfall 3). Track whether the checklist was empty at last build, and invalidate +
-- rebuild the cached "invite" screen (re-showing it if currently active) the next time
-- GUILD_ROSTER_UPDATE actually has ranks to offer. Self-limiting: once a rebuild finds
-- ranks, this flag flips false and the listener goes quiet.
local ranksWereEmptyAtBuild = false

local rankRefreshFrame = CreateFrame("Frame")
rankRefreshFrame:RegisterEvent("GUILD_ROSTER_UPDATE")
rankRefreshFrame:SetScript("OnEvent", function()
    if not ranksWereEmptyAtBuild then return end
    if #namespace:GetInviteGuildRanks() == 0 then return end

    local shell = namespace.rosterShell
    if not shell or not shell.screens or not shell.screens.invite then return end

    local entry = shell.screens.invite
    if entry.frame then
        entry.frame:Hide()
    end

    shell:RegisterScreen("invite", function(host) return namespace:CreateInviteScreen(host) end)

    if shell.activeScreenID == "invite" then
        shell:ShowScreen("invite")
    end
end)

-- namespace:CreateInviteScreen(contentHost)
-- Invite tools screen (INV-UI-01): three sections -- Whisper Invites, Auto-promote
-- to Assistant, Accept Invites, Raid Difficulty -- built from WTUI widgets and
-- modeled on SettingsScreen.lua's structure. Every control reads its live
-- SavedVariable on build and writes it back (plus applies its effect) on
-- change. Ships in packaged builds; every namespace.RosterAlts read below is
-- nil-guarded so the battletag-dependent rows degrade cleanly when RosterAlts
-- is stripped.
function namespace:CreateInviteScreen(contentHost)
    local root = CreateFrame("Frame", nil, contentHost)
    root:SetPoint("TOPLEFT", contentHost, "TOPLEFT", 0, 0)
    root:SetPoint("TOPRIGHT", contentHost, "TOPRIGHT", 0, 0)
    root:SetPoint("BOTTOMRIGHT", contentHost, "BOTTOMRIGHT", 0, 0)

    -- Scroll the body (mirrors SettingsScreen.lua/RosterScreen.lua): the guild-rank
    -- checklist below adds one row per rank, which can run taller than the window on a
    -- guild with many ranks.
    local scrollFrame = namespace:CreateScrollFrame(root)
    scrollFrame:SetPoint("TOPLEFT", root, "TOPLEFT", Theme.PAD_WINDOW, -Theme.PAD_WINDOW)
    scrollFrame:SetPoint("BOTTOMRIGHT", root, "BOTTOMRIGHT", -Theme.PAD_WINDOW, Theme.PAD_WINDOW)

    -- Content width must be bound before any row is built (Pitfall 1, see
    -- SettingsScreen.lua): AddDimLabel's wrapping FontString only reports a correct
    -- GetStringHeight() once its width is resolved.
    local viewportWidth = scrollFrame.scrollFrame:GetWidth()
    if not viewportWidth or viewportWidth < 1 then viewportWidth = 1 end
    scrollFrame.contentFrame:SetWidth(viewportWidth)

    -- Running vertical cursor (negative, growing downward from the content top). Every
    -- row/header anchors directly to scrollFrame.contentFrame with an explicit (0, y)
    -- offset rather than chain-anchoring to the previous element, so horizontal
    -- alignment never drifts across the three mixed-height section types below.
    local y = 0

    local function AddSectionHeader(title)
        local header = namespace:CreateSectionHeader(scrollFrame.contentFrame, title)
        header:SetPoint("TOPLEFT", scrollFrame.contentFrame, "TOPLEFT", 0, y)
        header:SetPoint("TOPRIGHT", scrollFrame.contentFrame, "TOPRIGHT", 0, y)
        y = y - header:GetHeight() - Theme.GAP
        return header
    end

    -- Toggle + label row, mirrors SettingsScreen.lua's CreateSettingRow.
    local function AddToggleRow(label, initialChecked, onChange)
        local row = CreateFrame("Frame", nil, scrollFrame.contentFrame)
        row:SetPoint("TOPLEFT", scrollFrame.contentFrame, "TOPLEFT", 0, y)
        row:SetPoint("TOPRIGHT", scrollFrame.contentFrame, "TOPRIGHT", 0, y)
        row:SetHeight(Theme.H_ROW)

        local toggle = namespace:CreateToggle(row, onChange)
        toggle:SetPoint("LEFT", row, "LEFT", 0, 0)
        toggle:SetChecked(initialChecked, true)

        local text = row:CreateFontString(nil, "ARTWORK")
        text:SetFontObject(WildThingsFont13)
        text:SetText(label)
        text:SetTextColor(Theme.TEXT.r, Theme.TEXT.g, Theme.TEXT.b, Theme.TEXT.a)
        text:SetPoint("LEFT", toggle, "RIGHT", Theme.GAP, 0)

        y = y - Theme.H_ROW
        return row, toggle
    end

    -- EditBox row: the widget renders its own title label ABOVE itself (outside
    -- its own frame bounds), so the row reserves TITLE_H of extra top space.
    local function AddEditBoxRow(title, initialText, onChange)
        local row = CreateFrame("Frame", nil, scrollFrame.contentFrame)
        local rowHeight = TITLE_H + Theme.H_INPUT -- title strip + EditBox.lua's DEFAULT_HEIGHT
        row:SetPoint("TOPLEFT", scrollFrame.contentFrame, "TOPLEFT", 0, y)
        row:SetPoint("TOPRIGHT", scrollFrame.contentFrame, "TOPRIGHT", 0, y)
        row:SetHeight(rowHeight)

        local editBox = namespace:CreateEditBox(row, title, onChange)
        editBox:SetPoint("TOPLEFT", row, "TOPLEFT", 0, -TITLE_H)
        editBox:SetPoint("TOPRIGHT", row, "TOPRIGHT", 0, -TITLE_H)
        editBox:SetText(initialText or "")

        y = y - rowHeight - Theme.GAP
        return row, editBox
    end

    local function AddDropdownRow(title, infoTable, initialValue, onChange)
        local row = CreateFrame("Frame", nil, scrollFrame.contentFrame)
        local rowHeight = TITLE_H + Theme.H_DROPDOWN
        row:SetPoint("TOPLEFT", scrollFrame.contentFrame, "TOPLEFT", 0, y)
        row:SetPoint("TOPRIGHT", scrollFrame.contentFrame, "TOPRIGHT", 0, y)
        row:SetHeight(rowHeight)

        local dropdown = namespace:CreateDropdown(row, title, infoTable, onChange, initialValue)
        dropdown:SetPoint("TOPLEFT", row, "TOPLEFT", 0, -TITLE_H)

        y = y - rowHeight - Theme.GAP
        return row, dropdown
    end

    -- Static dim helper text (rank-list-empty fallback, battletag-inactive note).
    local function AddDimLabel(msg)
        local label = scrollFrame.contentFrame:CreateFontString(nil, "ARTWORK")
        label:SetFontObject(WildThingsFont13)
        label:SetText(msg)
        label:SetTextColor(Theme.TEXT_DIM.r, Theme.TEXT_DIM.g, Theme.TEXT_DIM.b, Theme.TEXT_DIM.a)
        label:SetJustifyH("LEFT")
        label:SetPoint("TOPLEFT", scrollFrame.contentFrame, "TOPLEFT", 0, y)
        label:SetPoint("TOPRIGHT", scrollFrame.contentFrame, "TOPRIGHT", 0, y)
        y = y - label:GetStringHeight() - Theme.GAP
        return label
    end

    ------------------------------------------------------------------
    -- Whisper Invites
    ------------------------------------------------------------------
    AddSectionHeader("Whisper Invites")

    AddToggleRow("Invite on whisper keyword", WildThingsSavedVars.invite.by_whisper, function(checked)
        WildThingsSavedVars.invite.by_whisper = checked
        namespace:RefreshInviteEvents()
    end)

    -- INVTOOL-03: eligibility-only, not a registration change -- writes the
    -- SavedVar and returns, no RefreshInviteEvents() call.
    AddToggleRow("Also invite Battle.net and character friends", WildThingsSavedVars.invite.whisper_from_friends, function(checked)
        WildThingsSavedVars.invite.whisper_from_friends = checked
    end)

    AddEditBoxRow("Keywords (space-separated)", WildThingsSavedVars.invite.words, function(text)
        WildThingsSavedVars.invite.words = text
        namespace:RebuildInviteKeywords()
    end)

    ------------------------------------------------------------------
    -- Accept Invites (INVTOOL-04/05/06)
    ------------------------------------------------------------------
    AddSectionHeader("Accept Invites")

    -- Master toggle: changes which events must be registered, so it calls
    -- RefreshInviteEvents like every other master toggle on this screen.
    AddToggleRow("Auto-accept invites from friends", WildThingsSavedVars.invite.accept_from_friends, function(checked)
        WildThingsSavedVars.invite.accept_from_friends = checked
        namespace:RefreshInviteEvents()
    end)

    -- Master toggle: also registers GUILD_ROSTER_UPDATE (guildMemberKeys must
    -- be populated for the guild branch), so it calls RefreshInviteEvents too.
    AddToggleRow("Auto-accept invites from guild members", WildThingsSavedVars.invite.accept_from_guild, function(checked)
        WildThingsSavedVars.invite.accept_from_guild = checked
        namespace:RefreshInviteEvents()
    end)

    -- Guard toggle only: changes a value the PARTY_INVITE_REQUEST handler reads,
    -- not which events are registered, so it does not call RefreshInviteEvents.
    AddToggleRow("Only when not already in a group", WildThingsSavedVars.invite.accept_only_when_solo, function(checked)
        WildThingsSavedVars.invite.accept_only_when_solo = checked
    end)

    AddDimLabel("Friends means anyone on your Battle.net or character friend list -- a wider, less curated set than the guild roster.")

    ------------------------------------------------------------------
    -- Auto-promote to Assistant
    ------------------------------------------------------------------
    AddSectionHeader("Auto-promote to Assistant")

    -- Assigned once the rank checklist below is built; toggling Enable greys out /
    -- re-enables the guild-rank toggles so they can't be edited while auto-promote is off.
    local SetRankTogglesEnabled

    AddToggleRow("Enable auto-promote", WildThingsSavedVars.invite.autopromote, function(checked)
        WildThingsSavedVars.invite.autopromote = checked
        namespace:RefreshInviteEvents()
        if SetRankTogglesEnabled then SetRankTogglesEnabled(checked) end
    end)

    -- Gap between the Enable toggle and the guild-rank checklist.
    y = y - Theme.GAP

    -- Guild-rank checklist, built once at screen-construction time from live
    -- guild data (Pitfall 3) -- never errors, degrades to a dim fallback label
    -- when guild data (or a guild) is unavailable. Laid out in two columns.
    local ranks = namespace:GetInviteGuildRanks()
    ranksWereEmptyAtBuild = (#ranks == 0)
    local rankToggles = {}
    if #ranks == 0 then
        AddDimLabel("Guild ranks unavailable")
    else
        local halfWidth = viewportWidth / 2
        for i = 1, #ranks, 2 do
            local row = CreateFrame("Frame", nil, scrollFrame.contentFrame)
            row:SetPoint("TOPLEFT", scrollFrame.contentFrame, "TOPLEFT", 0, y)
            row:SetPoint("TOPRIGHT", scrollFrame.contentFrame, "TOPRIGHT", 0, y)
            row:SetHeight(Theme.H_ROW)

            for col = 0, 1 do
                local rankIndex1 = i + col
                local rankName = ranks[rankIndex1]
                if rankName then
                    local toggle = namespace:CreateToggle(row, function(checked)
                        WildThingsSavedVars.invite.promote_ranks[rankIndex1] = checked
                    end)
                    toggle:SetPoint("LEFT", row, "LEFT", col * halfWidth, 0)
                    toggle:SetChecked(WildThingsSavedVars.invite.promote_ranks[rankIndex1] == true, true)

                    local text = row:CreateFontString(nil, "ARTWORK")
                    text:SetFontObject(WildThingsFont13)
                    -- Prefix the 0-based WoW guild-rank index, e.g. "(0) Guild Master".
                    text:SetText("(" .. (rankIndex1 - 1) .. ") " .. rankName)
                    text:SetTextColor(Theme.TEXT.r, Theme.TEXT.g, Theme.TEXT.b, Theme.TEXT.a)
                    text:SetPoint("LEFT", toggle, "RIGHT", Theme.GAP, 0)

                    rankToggles[#rankToggles + 1] = { toggle = toggle, text = text }
                end
            end

            y = y - Theme.H_ROW
        end
    end

    -- Grey out and block the rank toggles when auto-promote is disabled.
    function SetRankTogglesEnabled(enabled)
        for _, entry in ipairs(rankToggles) do
            entry.toggle:EnableMouse(enabled)
            entry.toggle:SetAlpha(enabled and 1 or 0.4)
            local c = enabled and Theme.TEXT or Theme.TEXT_DIM
            entry.text:SetTextColor(c.r, c.g, c.b, c.a)
        end
    end
    SetRankTogglesEnabled(WildThingsSavedVars.invite.autopromote == true)

    -- GATE-05: dev-build only (not rank-gated) -- battletag input never ships in a
    -- packaged build, same sentinel used throughout (RosterScreen.lua/Roster.lua tier).
    if namespace.CreateRosterScreen then
        AddEditBoxRow("Promote battletags (space-separated)", WildThingsSavedVars.invite.promote_bnets, function(text)
            WildThingsSavedVars.invite.promote_bnets = text
        end)
    end

    -- Battletag-rule availability note (T-pj1-04): nil-guarded RosterAlts check --
    -- reads as inactive-but-harmless in a packaged build, never errors.
    if namespace.RosterAlts then
        AddDimLabel("Battletag rules apply to any character on a listed battletag.")
    else
        AddDimLabel("Battletag rules are inactive in this build (guild/name rules still apply).")
    end

    AddEditBoxRow("Promote names (space-separated)", WildThingsSavedVars.invite.promote_names, function(text)
        WildThingsSavedVars.invite.promote_names = text
    end)

    ------------------------------------------------------------------
    -- Raid Difficulty
    ------------------------------------------------------------------
    AddSectionHeader("Raid Difficulty")

    AddToggleRow("Auto-apply on convert to raid", WildThingsSavedVars.invite.auto_raid_diff, function(checked)
        WildThingsSavedVars.invite.auto_raid_diff = checked
        namespace:RefreshInviteEvents()
    end)

    local diffEntries = {
        {text = "Normal", value = 14},
        {text = "Heroic", value = 15},
        {text = "Mythic", value = 16},
    }
    local initialIndex = 3 -- default to Mythic if the stored value is somehow unrecognized
    for i, entry in ipairs(diffEntries) do
        if entry.value == WildThingsSavedVars.invite.raid_diff then
            initialIndex = i
            break
        end
    end
    -- WR-02 review fix: CreateDropdown's SetValue(initialValue) at construction always
    -- invokes onChange (no suppress-initial flag, unlike Toggle:SetChecked(checked, true)).
    -- Guard the live-apply so opening the screen doesn't re-apply the stored raid
    -- difficulty as an unintended side effect; the flag flips true only after
    -- construction, once the synchronous initial SetValue call has already fired.
    local raidDiffDropdownReady = false
    AddDropdownRow("Raid difficulty", diffEntries, {initialIndex}, function(value)
        if not raidDiffDropdownReady then return end
        namespace:SetInviteDifficulty(value)
    end)
    raidDiffDropdownReady = true

    -- Size the scrollable content to fit exactly what was built (mirrors
    -- RosterScreen.lua's scrollFrame.contentFrame:SetHeight(...) + FullUpdate step).
    scrollFrame.contentFrame:SetHeight(-y)
    scrollFrame:FullUpdate()

    return root
end
