local addonName, TOAST = ...

local GOLD = { 0.88, 0.75, 0.36 }
local BORDER = { 1, 1, 1, 0.12 }
local COMMUNITY_INVITE = "https://www.worldofwarcraft.com/invite/pPmMXpyCPoX?region=EU&faction=Horde"
-- The bare code from the invite URL above. The community gate pastes this
-- straight into the in-game "Join Community" dialog, which wants the code,
-- not the web link.
local COMMUNITY_INVITE_CODE = "pPmMXpyCPoX"
local DISCORD_INVITE = "https://discord.gg/tide-crew"

-- Layout constants: the popup height is derived from these (see ApplyMode)
-- instead of a fixed frame size, so short modes (community-only) don't leave
-- a dead zone between the button and the checkbox.
local W = 440
local PAD = 16
-- Same header geometry as Dashboard, Leaderboard, and release notes. The
-- popup stays anchored at the same top point, so this added height grows
-- downward rather than moving the community prompt up the screen.
local HEADER_H = 100
local DIVIDER_Y = -(HEADER_H + 1)  -- same "+1" convention as dashboard/leaderboard
local DIVIDER_MEDALLION = 32
local DIVIDER_GAP = 6
local GAP_DIVIDER_BODY = 14
local GAP_BODY_SECTION = 16
local GAP_LABEL_INSTR = 4
local GAP_INSTR_BOX = 8
local GAP_SECTION = 20
-- Top edge of both onboarding windows, measured up from the centre of the
-- screen. Sits low on purpose: the Communities window opens above it, so the
-- gate stays readable next to it instead of behind it. Relative to CENTER
-- rather than an absolute Y, which would drift with UI scale and resolution.
--
-- Anchored by the top instead of SetPoint("CENTER") because the windows have
-- different heights (400 vs the gate's derived ~300); a centred frame puts
-- its top at centre + height/2, so their headers would never line up. Top
-- anchoring also lets a taller popup grow downward rather than pushing its
-- own header up.
local ONBOARD_TOP_Y = 25

local BOX_W = W - PAD * 2
local BOX_H = 30
local BUTTON_W, BUTTON_H = 240, 44
local GAP_BUTTON_CHECK = 18
local CHECK_SIZE = 18
local BOTTOM_PAD = 16

local BUTTON_STYLES = {
    gold = {
        bg      = { 0.88, 0.75, 0.36, 0.15 },
        bgHover = { 0.88, 0.75, 0.36, 0.28 },
        border  = { 0.88, 0.75, 0.36, 1 },
        text    = { 0.88, 0.75, 0.36 },
    },
    grey = {
        bg      = { 1, 1, 1, 0.06 },
        bgHover = { 1, 1, 1, 0.12 },
        border  = { 1, 1, 1, 0.18 },
        text    = { 1, 1, 1 },
    },
}

local MODES = {
    community = {
        title       = "Join TIDE and Crew",
        body        = "Join the in-game community to unlock full TOAST functionality.\nKeys, scores and crew features work best when you are part of TIDE and Crew.",
        showCommunity = true,
        showDiscord   = false,
        communityLabel = "Community Invite Code",
        communityInstruction = "Open the Communities panel, click 'Join or Create Community', and paste this code.",
        communityValue = COMMUNITY_INVITE_CODE,
        actionLabel = "Open Communities",
        showCheckbox = false,
    },
    discord = {
        title       = "Join The SEAMEN",
        body        = "You're in TIDE and Crew but not yet linked in Discord.\nJoin our Discord to show up on the leaderboard.\nIf you just joined, TOAST may not recognize you until the next daily roster update.",
        showCommunity = false,
        showDiscord   = true,
        actionLabel = "Let's TOAST!",
        showCheckbox = true,
    },
}

local frame
local header
local title, body
local communityLabel, communityInstruction, communityBox, communityEditBox
local discordLabel, discordBox, discordEditBox
local hopBtn, hopBtnText
local checkbox, checkboxLabel
local activeTargetCommand = "dashboard"
local onboardingWatcher
local watcherArmed = false
local HighlightJoinButton, HookCommunitiesCleanup

local function OpenTargetCommand(targetCommand)
    targetCommand = targetCommand or "dashboard"
    if targetCommand == "dashboard" and TOAST.ShowDashboard then
        TOAST.ShowDashboard()
        return
    end
    if targetCommand == "lb" and TOAST.ShowLeaderboard then
        TOAST.ShowLeaderboard()
        return
    end
    if targetCommand == "trends" and TOAST.ShowTrends then
        TOAST.ShowTrends()
        return
    end

    local cmd = TOAST.commands and TOAST.commands[targetCommand]
    if cmd then
        cmd()
    end
end

local function IsRecognized()
    return TOAST.me ~= nil or TOAST_DB.me ~= nil
end

local function ScheduleOnboardingCheck(delay, forceDiscord)
    if not C_Timer then return end
    C_Timer.After(delay, function()
        if TOAST.CheckOnboardingProgress then
            TOAST.CheckOnboardingProgress(activeTargetCommand, { forceDiscord = forceDiscord })
        end
    end)
end

local function ArmOnboardingWatcher()
    if not onboardingWatcher then
        onboardingWatcher = CreateFrame("Frame")
        onboardingWatcher:RegisterEvent("CLUB_ADDED")
        onboardingWatcher:RegisterEvent("CLUB_MEMBERS_UPDATED")
        onboardingWatcher:RegisterEvent("CLUB_STREAMS_LOADED")
        onboardingWatcher:SetScript("OnEvent", function()
            if not watcherArmed then return end
            ScheduleOnboardingCheck(0.25, true)
            ScheduleOnboardingCheck(1.0, true)
        end)
    end
    watcherArmed = true
end

local function DisarmOnboardingWatcher()
    watcherArmed = false
end

local function OpenCommunitiesPanel()
    if TOAST.CanOpenMajorWindow and not TOAST.CanOpenMajorWindow() then return end

    ArmOnboardingWatcher()
    if ToggleCommunitiesFrame then
        ToggleCommunitiesFrame()
    end
    ScheduleOnboardingCheck(0.5, true)
    ScheduleOnboardingCheck(2.0, true)
    C_Timer.After(0.5, function()
        if HighlightJoinButton then
            HighlightJoinButton()
        end
        if HookCommunitiesCleanup then
            pcall(HookCommunitiesCleanup)
        end
    end)
end

local function LockEditBox(eb, text)
    eb:SetAutoFocus(false)
    eb:SetText(text)
    eb:SetScript("OnEditFocusGained", function(self) self:HighlightText() end)
    eb:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
    eb:SetScript("OnChar", function(self)
        self:SetText(text)
        self:HighlightText()
    end)
end

local function CreateActionButton(parent, label, style, width, height)
    local s = BUTTON_STYLES[style]

    local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
    Mixin(btn, BackdropTemplateMixin)
    btn:SetSize(width, height)

    local bg = btn:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(unpack(s.bg))

    local top = btn:CreateTexture(nil, "BORDER")
    top:SetColorTexture(unpack(s.border))
    top:SetPoint("TOPLEFT", btn, "TOPLEFT", 0, 0)
    top:SetPoint("TOPRIGHT", btn, "TOPRIGHT", 0, 0)
    top:SetHeight(1)

    local bottom = btn:CreateTexture(nil, "BORDER")
    bottom:SetColorTexture(unpack(s.border))
    bottom:SetPoint("BOTTOMLEFT", btn, "BOTTOMLEFT", 0, 0)
    bottom:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 0, 0)
    bottom:SetHeight(1)

    local left = btn:CreateTexture(nil, "BORDER")
    left:SetColorTexture(unpack(s.border))
    left:SetPoint("TOPLEFT", btn, "TOPLEFT", 0, 0)
    left:SetPoint("BOTTOMLEFT", btn, "BOTTOMLEFT", 0, 0)
    left:SetWidth(1)

    local right = btn:CreateTexture(nil, "BORDER")
    right:SetColorTexture(unpack(s.border))
    right:SetPoint("TOPRIGHT", btn, "TOPRIGHT", 0, 0)
    right:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 0, 0)
    right:SetWidth(1)

    local text = btn:CreateFontString(nil, "OVERLAY", "TOASTFontNormal")
    text:SetPoint("CENTER")
    text:SetText(label)
    text:SetTextColor(unpack(s.text))

    btn:SetScript("OnEnter", function() bg:SetColorTexture(unpack(s.bgHover)) end)
    btn:SetScript("OnLeave", function() bg:SetColorTexture(unpack(s.bg)) end)

    return btn, text
end

TOAST.LockEditBox = LockEditBox
TOAST.CreateActionButton = CreateActionButton

-- Dark, bordered link box with a gold-on-focus border, replacing the default
-- Blizzard InputBoxTemplate look. Border texture pattern mirrors the
-- leaderboard search box so both speak the same "custom input" language.
local function CreateInviteBox(parent, width, height)
    local box = CreateFrame("Frame", nil, parent)
    box:SetSize(width, height)

    local bg = box:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(0.04, 0.05, 0.07, 0.9)

    local topB = box:CreateTexture(nil, "BORDER")
    topB:SetPoint("TOPLEFT", box, "TOPLEFT", 0, 0)
    topB:SetPoint("TOPRIGHT", box, "TOPRIGHT", 0, 0)
    topB:SetHeight(1)

    local bottomB = box:CreateTexture(nil, "BORDER")
    bottomB:SetPoint("BOTTOMLEFT", box, "BOTTOMLEFT", 0, 0)
    bottomB:SetPoint("BOTTOMRIGHT", box, "BOTTOMRIGHT", 0, 0)
    bottomB:SetHeight(1)

    local leftB = box:CreateTexture(nil, "BORDER")
    leftB:SetPoint("TOPLEFT", box, "TOPLEFT", 0, 0)
    leftB:SetPoint("BOTTOMLEFT", box, "BOTTOMLEFT", 0, 0)
    leftB:SetWidth(1)

    local rightB = box:CreateTexture(nil, "BORDER")
    rightB:SetPoint("TOPRIGHT", box, "TOPRIGHT", 0, 0)
    rightB:SetPoint("BOTTOMRIGHT", box, "BOTTOMRIGHT", 0, 0)
    rightB:SetWidth(1)

    local function SetBoxBorder(r, g, b, a)
        topB:SetColorTexture(r, g, b, a)
        bottomB:SetColorTexture(r, g, b, a)
        leftB:SetColorTexture(r, g, b, a)
        rightB:SetColorTexture(r, g, b, a)
    end
    SetBoxBorder(1, 1, 1, 0.14)

    local eb = CreateFrame("EditBox", nil, box)
    eb:SetAllPoints()
    eb:SetFont(TOAST.FONT_PATH, 12, "")
    eb:SetTextColor(0.92, 0.92, 0.90, 1)
    eb:SetTextInsets(10, 10, 0, 0)
    eb:SetAutoFocus(false)

    eb:HookScript("OnEditFocusGained", function(self)
        SetBoxBorder(GOLD[1], GOLD[2], GOLD[3], 0.9)
        -- LockEditBox also selects on focus, but hooking it here means a box
        -- created without LockEditBox still behaves like a copy field.
        self:HighlightText()
    end)
    eb:HookScript("OnEditFocusLost", function() SetBoxBorder(1, 1, 1, 0.14) end)

    -- Sits above the box's right edge. Callers that have something else there
    -- can reposition or hide it via the third return value.
    local copyHint = box:CreateFontString(nil, "OVERLAY", "TOASTFontHighlightSmall")
    copyHint:SetPoint("TOPRIGHT", box, "TOPRIGHT", 0, 12)
    copyHint:SetTextColor(1, 1, 1, 0.35)
    copyHint:SetText("Ctrl+C to copy")

    return box, eb, copyHint
end

-- Small checkbox: dark fill, muted grey border, gold fill when checked —
-- replaces the oversized default UICheckButtonTemplate art.
local function CreateStyledCheckbox(parent, size)
    local cb = CreateFrame("CheckButton", nil, parent)
    cb:SetSize(size, size)

    local bg = cb:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(0.04, 0.05, 0.07, 1)

    local topB = cb:CreateTexture(nil, "BORDER")
    topB:SetColorTexture(1, 1, 1, 0.25)
    topB:SetPoint("TOPLEFT", cb, "TOPLEFT", 0, 0)
    topB:SetPoint("TOPRIGHT", cb, "TOPRIGHT", 0, 0)
    topB:SetHeight(1)

    local bottomB = cb:CreateTexture(nil, "BORDER")
    bottomB:SetColorTexture(1, 1, 1, 0.25)
    bottomB:SetPoint("BOTTOMLEFT", cb, "BOTTOMLEFT", 0, 0)
    bottomB:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 0, 0)
    bottomB:SetHeight(1)

    local leftB = cb:CreateTexture(nil, "BORDER")
    leftB:SetColorTexture(1, 1, 1, 0.25)
    leftB:SetPoint("TOPLEFT", cb, "TOPLEFT", 0, 0)
    leftB:SetPoint("BOTTOMLEFT", cb, "BOTTOMLEFT", 0, 0)
    leftB:SetWidth(1)

    local rightB = cb:CreateTexture(nil, "BORDER")
    rightB:SetColorTexture(1, 1, 1, 0.25)
    rightB:SetPoint("TOPRIGHT", cb, "TOPRIGHT", 0, 0)
    rightB:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", 0, 0)
    rightB:SetWidth(1)

    local checked = cb:CreateTexture(nil, "ARTWORK")
    checked:SetPoint("TOPLEFT", cb, "TOPLEFT", 3, -3)
    checked:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", -3, 3)
    checked:SetColorTexture(GOLD[1], GOLD[2], GOLD[3], 1)
    cb:SetCheckedTexture(checked)

    return cb
end

-- Every element below the header divider is re-anchored here using a running
-- cursor (in frame-local Y), and the frame height is derived from where the
-- cursor ends up. This lets the community and Discord steps each use only
-- the height needed for their own content.
local function ApplyMode(mode)
    local cfg = MODES[mode]

    title:SetText(cfg.title)
    body:SetText(cfg.body)

    local y = DIVIDER_Y - GAP_DIVIDER_BODY
    body:ClearAllPoints()
    body:SetPoint("TOP", frame, "TOPLEFT", W / 2, y)
    y = y - body:GetStringHeight()

    if cfg.showCommunity then
        y = y - GAP_BODY_SECTION
        communityLabel:SetText(cfg.communityLabel or "Community Invite")
        communityLabel:ClearAllPoints()
        communityLabel:SetPoint("TOP", frame, "TOPLEFT", W / 2, y)
        communityLabel:Show()
        y = y - communityLabel:GetStringHeight()

        y = y - GAP_LABEL_INSTR
        communityInstruction:SetText(cfg.communityInstruction or "Copy this link into your browser to get the in-game join code.")
        communityInstruction:ClearAllPoints()
        communityInstruction:SetPoint("TOP", frame, "TOPLEFT", W / 2, y)
        communityInstruction:Show()
        y = y - communityInstruction:GetStringHeight()

        y = y - GAP_INSTR_BOX
        LockEditBox(communityEditBox, cfg.communityValue or COMMUNITY_INVITE)
        communityBox:ClearAllPoints()
        communityBox:SetPoint("TOP", frame, "TOPLEFT", W / 2, y)
        communityBox:Show()
        y = y - BOX_H
    else
        communityLabel:Hide()
        communityInstruction:Hide()
        communityBox:Hide()
    end

    if cfg.showDiscord then
        y = y - GAP_SECTION
        discordLabel:ClearAllPoints()
        discordLabel:SetPoint("TOP", frame, "TOPLEFT", W / 2, y)
        discordLabel:Show()
        y = y - discordLabel:GetStringHeight()

        y = y - GAP_LABEL_INSTR
        discordBox:ClearAllPoints()
        discordBox:SetPoint("TOP", frame, "TOPLEFT", W / 2, y)
        discordBox:Show()
        y = y - BOX_H
    else
        discordLabel:Hide()
        discordBox:Hide()
    end

    y = y - GAP_SECTION
    hopBtnText:SetText(cfg.actionLabel or "Let's TOAST!")
    hopBtn:SetScript("OnClick", function()
        if mode == "community" then
            OpenCommunitiesPanel()
        else
            frame:Hide()
            OpenTargetCommand(activeTargetCommand)
        end
    end)
    hopBtn:ClearAllPoints()
    hopBtn:SetPoint("TOP", frame, "TOPLEFT", W / 2, y)
    y = y - BUTTON_H

    if cfg.showCheckbox then
        y = y - GAP_BUTTON_CHECK
        checkbox:ClearAllPoints()
        checkbox:SetPoint("TOPLEFT", frame, "TOPLEFT", PAD, y)
        checkbox:Show()
        checkboxLabel:Show()
        frame:SetHeight(-y + CHECK_SIZE + BOTTOM_PAD)
    else
        checkbox:Hide()
        checkboxLabel:Hide()
        frame:SetHeight(-y + BOTTOM_PAD)
    end

end

local function BuildWelcome()
    frame = CreateFrame("Frame", "TOASTWelcome", UIParent, "BackdropTemplate")
    Mixin(frame, BackdropTemplateMixin)
    frame:SetSize(W, 400)
    frame:SetPoint("TOP", UIParent, "CENTER", 0, ONBOARD_TOP_Y)
    frame:SetMovable(true)
    frame:EnableMouse(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetScript("OnDragStart", frame.StartMoving)
    frame:SetScript("OnDragStop", frame.StopMovingOrSizing)
    frame:SetFrameStrata("DIALOG")
    frame:Hide()
    tinsert(UISpecialFrames, "TOASTWelcome")

    local bg = frame:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(0.06, 0.08, 0.10, 1.0)

    local topBorder = frame:CreateTexture(nil, "BORDER")
    topBorder:SetColorTexture(BORDER[1], BORDER[2], BORDER[3], BORDER[4])
    topBorder:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
    topBorder:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
    topBorder:SetHeight(1)

    local bottomBorder = frame:CreateTexture(nil, "BORDER")
    bottomBorder:SetColorTexture(BORDER[1], BORDER[2], BORDER[3], BORDER[4])
    bottomBorder:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 0)
    bottomBorder:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
    bottomBorder:SetHeight(1)

    local leftBorder = frame:CreateTexture(nil, "BORDER")
    leftBorder:SetColorTexture(BORDER[1], BORDER[2], BORDER[3], BORDER[4])
    leftBorder:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
    leftBorder:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 0)
    leftBorder:SetWidth(1)

    local rightBorder = frame:CreateTexture(nil, "BORDER")
    rightBorder:SetColorTexture(BORDER[1], BORDER[2], BORDER[3], BORDER[4])
    rightBorder:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
    rightBorder:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
    rightBorder:SetWidth(1)

    -- Subtle top gold line — shouldn't compete with the header divider below it.
    local accent = frame:CreateTexture(nil, "ARTWORK")
    accent:SetColorTexture(GOLD[1], GOLD[2], GOLD[3], 0.85)
    accent:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
    accent:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
    accent:SetHeight(1)

    TOAST.CreateCloseButton(frame, function() frame:Hide() end)

    -- ── Header: compact banner + logo + title, same language as
    -- dashboard/leaderboard headers (left logo, title/subtitle to its right).
    header = CreateFrame("Frame", nil, frame)
    header:SetSize(W, HEADER_H)
    header:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)

    local banner = header:CreateTexture(nil, "BACKGROUND")
    banner:SetTexture("Interface\\AddOns\\TOAST\\media\\banners\\tentakel_2.png")
    banner:SetTexCoord(0, 1, 0, 1)
    banner:SetAllPoints()
    banner:SetVertexColor(1, 1, 1, 0.35)

    local logo = header:CreateTexture(nil, "ARTWORK")
    logo:SetTexture("Interface\\AddOns\\TOAST\\media\\discord\\toast_128.tga")
    logo:SetSize(64, 64)
    logo:SetPoint("TOPLEFT", header, "TOPLEFT", 12, -8)

    title = header:CreateFontString(nil, "OVERLAY", "TOASTFontNormalHuge")
    title:SetPoint("TOPLEFT", logo, "TOPRIGHT", 10, -8)
    title:SetTextColor(GOLD[1], GOLD[2], GOLD[3])

    local subtitle = header:CreateFontString(nil, "OVERLAY", "TOASTFontHighlightSmall")
    subtitle:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
    subtitle:SetJustifyH("LEFT")
    subtitle:SetTextColor(1, 1, 1, 0.45)
    subtitle:SetText("Totally Overengineered Aquatic Support Tech\nAn open Mythic+ community")

    -- Divider levels above the header so the medallion draws over the banner.
    TOAST.CreateAnchorDivider(frame, PAD, W - PAD, DIVIDER_Y, {
        medallionSize = DIVIDER_MEDALLION,
        gap = DIVIDER_GAP,
        lineAlpha = 0.85,
        aboveFrame = header,
    })

    -- ── Body copy ───────────────────────────────────────────────────
    body = frame:CreateFontString(nil, "OVERLAY", "TOASTFontHighlight")
    body:SetWidth(W - PAD * 2)
    body:SetJustifyH("CENTER")
    body:SetTextColor(1, 1, 1, 0.6)

    -- ── Community invite section ────────────────────────────────────
    communityLabel = frame:CreateFontString(nil, "OVERLAY", "TOASTFontNormal")
    communityLabel:SetTextColor(GOLD[1], GOLD[2], GOLD[3])
    communityLabel:SetText("Community Invite")

    communityInstruction = frame:CreateFontString(nil, "OVERLAY", "TOASTFontHighlight")
    -- Constrained like the body copy: unbounded it renders as one line, which
    -- at 12pt would run past the frame edge. ApplyMode reads GetStringHeight,
    -- so wrapping to a second line is absorbed by the layout.
    communityInstruction:SetWidth(W - PAD * 2)
    communityInstruction:SetJustifyH("CENTER")
    communityInstruction:SetTextColor(1, 1, 1, 0.45)
    communityInstruction:SetText("Copy this link into your browser to get the in-game join code.")

    local communityHint
    communityBox, communityEditBox, communityHint = CreateInviteBox(frame, BOX_W, BOX_H)
    LockEditBox(communityEditBox, COMMUNITY_INVITE)
    -- This is the one box with a full-width instruction line sitting 8px above
    -- it, so the default hint position would land on top of that text. Moved
    -- under the box instead; the gap to the next section absorbs it.
    communityHint:ClearAllPoints()
    communityHint:SetPoint("TOPRIGHT", communityBox, "BOTTOMRIGHT", 0, -2)

    -- ── Discord section ──────────────────────────────────────────────
    discordLabel = frame:CreateFontString(nil, "OVERLAY", "TOASTFontNormal")
    discordLabel:SetTextColor(GOLD[1], GOLD[2], GOLD[3])
    discordLabel:SetText("Discord")

    discordBox, discordEditBox = CreateInviteBox(frame, BOX_W, BOX_H)
    LockEditBox(discordEditBox, DISCORD_INVITE)

    -- ── Main action button ───────────────────────────────────────────
    hopBtn, hopBtnText = CreateActionButton(frame, "Let's TOAST!", "gold", BUTTON_W, BUTTON_H)

    -- ── Dismiss checkbox ─────────────────────────────────────────────
    checkbox = CreateStyledCheckbox(frame, CHECK_SIZE)
    checkbox:SetScript("OnClick", function()
        TOAST_DB.dismissedWelcome = TOAST_DB.dismissedWelcome or {}
        TOAST_DB.dismissedWelcome[UnitName("player")] = true
    end)

    checkboxLabel = frame:CreateFontString(nil, "OVERLAY", "TOASTFontHighlightSmall")
    checkboxLabel:SetPoint("LEFT", checkbox, "RIGHT", 6, 0)
    checkboxLabel:SetTextColor(1, 1, 1, 0.5)
    checkboxLabel:SetText("Don't show this again for this character")
end

function TOAST.ShowWelcome(mode)
    -- Onboarding is deliberately sequential: callers may show one step or
    -- the other, but never a combined Community + Discord screen.
    mode = mode == "discord" and "discord" or "community"
    if not frame then
        BuildWelcome()
    end
    ApplyMode(mode)
    frame:Show()
    frame:Raise()
end

function TOAST.CheckOnboardingProgress(targetCommand, opts)
    opts = opts or {}
    activeTargetCommand = targetCommand or activeTargetCommand or "dashboard"

    if TOAST.CanOpenMajorWindow and not TOAST.CanOpenMajorWindow() then
        return false
    end

    if TOAST.IsInCommunity then
        TOAST.inCommunity = TOAST.IsInCommunity()
    end

    if not TOAST.inCommunity then
        ArmOnboardingWatcher()
        TOAST.ShowWelcome("community")
        return false
    end

    if IsRecognized() then
        DisarmOnboardingWatcher()
        if frame and frame:IsShown() then
            frame:Hide()
        end
        OpenTargetCommand(activeTargetCommand)
        return true
    end

    local dismissed = TOAST_DB.dismissedWelcome or {}
    local charName = UnitName("player")
    if not opts.forceDiscord and dismissed[charName] == true then
        DisarmOnboardingWatcher()
        if frame and frame:IsShown() then
            frame:Hide()
        end
        OpenTargetCommand(activeTargetCommand)
        return true
    end

    DisarmOnboardingWatcher()
    TOAST.ShowWelcome("discord")
    return false
end

-- ═══════════════════════════════════════════════════════════════════════
-- Community gate
--
-- Blocking screen shown instead of the dashboard when the player is not in
-- TIDE and Crew. Deliberately not dismissable: no close button and no
-- "don't show again" checkbox, so /toast keeps landing here until the
-- player actually joins. Escape still closes it (UISpecialFrames) — that's
-- an escape hatch for a stuck frame, not a way past the gate, since the
-- next /toast rebuilds it.
-- ═══════════════════════════════════════════════════════════════════════

local GATE_W = 440
local GATE_HEADER_H = 50
local GATE_DIVIDER_Y = -(GATE_HEADER_H + 1)
local GATE_LOGO = 24
local GATE_GAP_DIVIDER_BODY = 16
local GATE_GAP_PARAGRAPH = 12
local GATE_GAP_BODY_LABEL = 18
local GATE_GAP_LABEL_BOX = 8
local GATE_GAP_BOX_BUTTON = 20
local GATE_GAP_BUTTON_HINT = 10
local GATE_BOTTOM_PAD = 16
local GATE_CODE_W = 200

local GLOW_TEXTURE = "Interface\\AddOns\\TOAST\\media\\effects\\Glow.tga"

-- Pulsing gold glow on the "Join or Create Community" entry in the communities
-- list. ActionButton_ShowOverlayGlow is deliberately avoided: it pulls the
-- Blizzard action-button code path into our execution context and taints it.
-- A plain texture with ADD blending gets the same look with no secure code.
--
-- Everything here is wrapped in pcall because it reaches into
-- Blizzard_Communities internals (ScrollBox, entry .Name), which are
-- load-on-demand and change between patches. A broken highlight must never
-- break /toast.

-- Armed by the gate's "Open Communities" button, disarmed when the player
-- closes the panel. The Update hook below is registered permanently (you
-- can't unhook a secure hook), so without this flag the glow would come back
-- on every future re-render, including panels the player opened for their own
-- reasons long after they used the gate.
local highlightActive = false

local function ApplyJoinGlow(btn)
    btn.Name:SetTextColor(GOLD[1], GOLD[2], GOLD[3])

    if btn._toastGlow then
        btn._toastGlow:Show()
        return
    end

    local glow = btn:CreateTexture(nil, "OVERLAY")
    glow:SetTexture(GLOW_TEXTURE)
    glow:SetPoint("TOPLEFT", btn, "TOPLEFT", -10, 10)
    glow:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 10, -10)
    glow:SetVertexColor(GOLD[1], GOLD[2], GOLD[3], 0.85)
    glow:SetBlendMode("ADD")

    local ag = glow:CreateAnimationGroup()
    local pulse = ag:CreateAnimation("Alpha")
    pulse:SetFromAlpha(0.85)
    pulse:SetToAlpha(0.15)
    pulse:SetDuration(0.6)
    pulse:SetSmoothing("IN_OUT")
    ag:SetLooping("BOUNCE")
    ag:Play()

    btn._toastGlow = glow
end

-- Only touches entries we glowed ourselves (_toastGlow), so a recycled frame
-- never gets its text reset to a colour it didn't have before we found it.
local function ClearJoinGlow(btn)
    if not btn._toastGlow then return end
    btn._toastGlow:Hide()
    if btn.Name then
        btn.Name:SetTextColor(1, 1, 1)
    end
end

-- Re-applies the highlight across the whole visible list. The "else clear"
-- branch is the important half: the ScrollBox pools its frames, so the frame
-- that held "Join or Create Community" before a scroll can be reused for a
-- guild row afterwards, and its glow texture would otherwise stay visible on
-- the wrong entry.
local function RefreshJoinHighlight(scrollBox)
    scrollBox:ForEachFrame(function(btn)
        local name = btn.Name and btn.Name:GetText()
        if name and name:find("Join") then
            ApplyJoinGlow(btn)
        else
            ClearJoinGlow(btn)
        end
    end)
end

HighlightJoinButton = function()
    pcall(function()
        if not CommunitiesFrameCommunitiesList then return end
        local scrollBox = CommunitiesFrameCommunitiesList.ScrollBox
        if not scrollBox then return end

        highlightActive = true
        RefreshJoinHighlight(scrollBox)

        -- The list initializer resets every visual property when it re-renders
        -- a pooled frame, wiping our text colour. Re-apply after each update.
        if not scrollBox._toastUpdateHooked then
            hooksecurefunc(scrollBox, "Update", function(self)
                if not highlightActive then return end
                pcall(RefreshJoinHighlight, self)
            end)
            scrollBox._toastUpdateHooked = true
        end
    end)
end

-- Hides the glow and restores the entry text when the player closes the
-- Communities panel, so the highlight doesn't linger the next time they open
-- it for an unrelated reason. Registered once per session (_toastHooked);
-- CommunitiesFrame only exists after Blizzard_Communities has loaded, which is
-- why this is called from the button handler rather than at file scope.
HookCommunitiesCleanup = function()
    if not CommunitiesFrame then return end
    if CommunitiesFrame._toastHooked then return end

    CommunitiesFrame:HookScript("OnHide", function()
        highlightActive = false
        pcall(function()
            if not CommunitiesFrameCommunitiesList then return end
            CommunitiesFrameCommunitiesList.ScrollBox:ForEachFrame(ClearJoinGlow)
        end)
    end)

    CommunitiesFrame._toastHooked = true
end

local gateFrame

local function BuildCommunityGate()
    gateFrame = CreateFrame("Frame", "TOASTCommunityGate", UIParent, "BackdropTemplate")
    Mixin(gateFrame, BackdropTemplateMixin)
    gateFrame:SetSize(GATE_W, 280)
    gateFrame:SetMovable(true)
    gateFrame:EnableMouse(true)
    gateFrame:SetClampedToScreen(true)
    gateFrame:RegisterForDrag("LeftButton")
    gateFrame:SetScript("OnDragStart", gateFrame.StartMoving)
    gateFrame:SetScript("OnDragStop", gateFrame.StopMovingOrSizing)
    gateFrame:SetFrameStrata("DIALOG")
    gateFrame:Hide()
    tinsert(UISpecialFrames, "TOASTCommunityGate")

    local bg = gateFrame:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(0.06, 0.08, 0.10, 1.0)

    local topBorder = gateFrame:CreateTexture(nil, "BORDER")
    topBorder:SetColorTexture(BORDER[1], BORDER[2], BORDER[3], BORDER[4])
    topBorder:SetPoint("TOPLEFT", gateFrame, "TOPLEFT", 0, 0)
    topBorder:SetPoint("TOPRIGHT", gateFrame, "TOPRIGHT", 0, 0)
    topBorder:SetHeight(1)

    local bottomBorder = gateFrame:CreateTexture(nil, "BORDER")
    bottomBorder:SetColorTexture(BORDER[1], BORDER[2], BORDER[3], BORDER[4])
    bottomBorder:SetPoint("BOTTOMLEFT", gateFrame, "BOTTOMLEFT", 0, 0)
    bottomBorder:SetPoint("BOTTOMRIGHT", gateFrame, "BOTTOMRIGHT", 0, 0)
    bottomBorder:SetHeight(1)

    local leftBorder = gateFrame:CreateTexture(nil, "BORDER")
    leftBorder:SetColorTexture(BORDER[1], BORDER[2], BORDER[3], BORDER[4])
    leftBorder:SetPoint("TOPLEFT", gateFrame, "TOPLEFT", 0, 0)
    leftBorder:SetPoint("BOTTOMLEFT", gateFrame, "BOTTOMLEFT", 0, 0)
    leftBorder:SetWidth(1)

    local rightBorder = gateFrame:CreateTexture(nil, "BORDER")
    rightBorder:SetColorTexture(BORDER[1], BORDER[2], BORDER[3], BORDER[4])
    rightBorder:SetPoint("TOPRIGHT", gateFrame, "TOPRIGHT", 0, 0)
    rightBorder:SetPoint("BOTTOMRIGHT", gateFrame, "BOTTOMRIGHT", 0, 0)
    rightBorder:SetWidth(1)

    local accent = gateFrame:CreateTexture(nil, "ARTWORK")
    accent:SetColorTexture(GOLD[1], GOLD[2], GOLD[3], 0.85)
    accent:SetPoint("TOPLEFT", gateFrame, "TOPLEFT", 0, 0)
    accent:SetPoint("TOPRIGHT", gateFrame, "TOPRIGHT", 0, 0)
    accent:SetHeight(1)

    -- ── Header: small logo + community name ─────────────────────────
    local gateHeader = CreateFrame("Frame", nil, gateFrame)
    gateHeader:SetSize(GATE_W, GATE_HEADER_H)
    gateHeader:SetPoint("TOPLEFT", gateFrame, "TOPLEFT", 0, 0)

    local logo = gateHeader:CreateTexture(nil, "ARTWORK")
    logo:SetTexture("Interface\\AddOns\\TOAST\\media\\discord\\toast_128.tga")
    logo:SetSize(GATE_LOGO, GATE_LOGO)
    logo:SetPoint("LEFT", gateHeader, "LEFT", PAD, 0)

    local gateTitle = gateHeader:CreateFontString(nil, "OVERLAY", "TOASTFontNormalHuge")
    gateTitle:SetPoint("LEFT", logo, "RIGHT", 8, 0)
    gateTitle:SetTextColor(GOLD[1], GOLD[2], GOLD[3])
    gateTitle:SetText("TIDE and Crew")

    if TOAST.CreateAnchorDivider then
        TOAST.CreateAnchorDivider(gateFrame, PAD, GATE_W - PAD, GATE_DIVIDER_Y, {
            medallionSize = DIVIDER_MEDALLION,
            gap = DIVIDER_GAP,
            lineAlpha = 0.85,
            aboveFrame = gateHeader,
        })
    else
        local line = gateFrame:CreateTexture(nil, "ARTWORK")
        line:SetColorTexture(GOLD[1], GOLD[2], GOLD[3], 0.85)
        line:SetPoint("TOPLEFT", gateFrame, "TOPLEFT", PAD, GATE_DIVIDER_Y)
        line:SetPoint("TOPRIGHT", gateFrame, "TOPLEFT", GATE_W - PAD, GATE_DIVIDER_Y)
        line:SetHeight(2)
    end

    -- ── Layout below the divider, running cursor in frame-local Y ───
    local y = GATE_DIVIDER_Y - GATE_GAP_DIVIDER_BODY

    -- Two separate FontStrings rather than one string with "\n\n": the
    -- paragraph gap is then a layout constant instead of a blank line whose
    -- height depends on the font, which matters now that TOAST ships its own.
    local bodyOne = gateFrame:CreateFontString(nil, "OVERLAY", "TOASTFontHighlight")
    bodyOne:SetWidth(GATE_W - PAD * 2)
    bodyOne:SetJustifyH("CENTER")
    bodyOne:SetTextColor(1, 1, 1, 0.7)
    bodyOne:SetText("To unlock TOAST you need to join the TIDE and Crew in-game community.")
    bodyOne:SetPoint("TOP", gateFrame, "TOPLEFT", GATE_W / 2, y)
    y = y - bodyOne:GetStringHeight()

    y = y - GATE_GAP_PARAGRAPH
    local bodyTwo = gateFrame:CreateFontString(nil, "OVERLAY", "TOASTFontHighlight")
    bodyTwo:SetWidth(GATE_W - PAD * 2)
    bodyTwo:SetJustifyH("CENTER")
    bodyTwo:SetTextColor(1, 1, 1, 0.7)
    bodyTwo:SetText("Open the Communities panel and paste this code after clicking 'Join or Create Community'.")
    bodyTwo:SetPoint("TOP", gateFrame, "TOPLEFT", GATE_W / 2, y)
    y = y - bodyTwo:GetStringHeight()

    y = y - GATE_GAP_BODY_LABEL
    local codeLabel = gateFrame:CreateFontString(nil, "OVERLAY", "TOASTFontNormal")
    codeLabel:SetTextColor(GOLD[1], GOLD[2], GOLD[3])
    codeLabel:SetText("Invite Code")
    codeLabel:SetPoint("TOP", gateFrame, "TOPLEFT", GATE_W / 2, y)
    y = y - codeLabel:GetStringHeight()

    -- Narrower than the welcome popup's link box: the payload is an 11-char
    -- code, not a URL, so a full-width box would read as an empty field.
    y = y - GATE_GAP_LABEL_BOX
    local codeBox, codeEditBox = CreateInviteBox(gateFrame, GATE_CODE_W, BOX_H)
    codeBox:SetPoint("TOP", gateFrame, "TOPLEFT", GATE_W / 2, y)
    codeEditBox:SetJustifyH("CENTER")
    codeEditBox:SetTextInsets(6, 6, 0, 0)
    LockEditBox(codeEditBox, COMMUNITY_INVITE_CODE)
    y = y - BOX_H

    y = y - GATE_GAP_BOX_BUTTON
    local openBtn = CreateActionButton(gateFrame, "Open Communities", "gold", BUTTON_W, BUTTON_H)
    openBtn:SetPoint("TOP", gateFrame, "TOPLEFT", GATE_W / 2, y)
    openBtn:SetScript("OnClick", function()
        ToggleCommunitiesFrame()
        C_Timer.After(0.5, function()
            HighlightJoinButton()
            pcall(HookCommunitiesCleanup)
        end)
    end)
    y = y - BUTTON_H

    y = y - GATE_GAP_BUTTON_HINT
    local hint = gateFrame:CreateFontString(nil, "OVERLAY", "TOASTFontHighlightSmall")
    hint:SetWidth(GATE_W - PAD * 2)
    hint:SetJustifyH("CENTER")
    hint:SetTextColor(1, 1, 1, 0.35)
    hint:SetText("Click the highlighted button and paste the invite code")
    hint:SetPoint("TOP", gateFrame, "TOPLEFT", GATE_W / 2, y)
    y = y - hint:GetStringHeight()

    gateFrame:SetHeight(-y + GATE_BOTTOM_PAD)

    -- Anchored only here, not on every Show: the frame is movable, and
    -- re-anchoring on open would throw away wherever the player dragged it.
    gateFrame:SetPoint("TOP", UIParent, "CENTER", 0, ONBOARD_TOP_Y)
end

function TOAST.ShowCommunityGate()
    activeTargetCommand = "dashboard"
    ArmOnboardingWatcher()
    TOAST.ShowWelcome("community")
end
