local AC = _G.ArenaCore
if not AC then return end

AC.Onboarding = AC.Onboarding or {}
local OB = AC.Onboarding

OB.VERSION = 1

-- Localization helper
local function L(key, fallback)
    if AC.UILocalization then
        return AC.UILocalization:Get("ONBOARDING", key, fallback or key)
    end
    return fallback or key
end

-- ============================================================================
-- Helpers (same as UpdateAlert)
-- ============================================================================
local function Clamp01(v)
    if v < 0 then return 0 end
    if v > 1 then return 1 end
    return v
end

local function ToHexColor(r, g, b)
    r = math.floor(Clamp01(r) * 255 + 0.5)
    g = math.floor(Clamp01(g) * 255 + 0.5)
    b = math.floor(Clamp01(b) * 255 + 0.5)
    return string.format("%02x%02x%02x", r, g, b)
end

local function GradientText(text, startColor, endColor)
    if type(text) ~= "string" or text == "" then
        return ""
    end
    local sr, sg, sb = startColor[1], startColor[2], startColor[3]
    local er, eg, eb = endColor[1], endColor[2], endColor[3]
    local out = {}
    local len = #text
    if len <= 1 then
        return "|cff" .. ToHexColor(sr, sg, sb) .. text .. "|r"
    end
    for i = 1, len do
        local ch = text:sub(i, i)
        local t = (i - 1) / (len - 1)
        local r = sr + (er - sr) * t
        local g = sg + (eg - sg) * t
        local b = sb + (eb - sb) * t
        out[#out + 1] = "|cff" .. ToHexColor(r, g, b) .. ch
    end
    out[#out + 1] = "|r"
    return table.concat(out)
end

local function EnsureGlobalDB()
    if not _G.ArenaCoreDB then
        _G.ArenaCoreDB = {}
    end
    _G.ArenaCoreDB.global = _G.ArenaCoreDB.global or {}
end

local function GetSeenVersion()
    EnsureGlobalDB()
    return _G.ArenaCoreDB.global.onboardingSeenVersion
end

local function SetSeenVersion(version)
    EnsureGlobalDB()
    _G.ArenaCoreDB.global.onboardingSeenVersion = version
end

-- ============================================================================
-- Scanlines (same pattern as changelog)
-- ============================================================================
local function AddScanlines(parent)
    local spacing = 4
    local count = math.floor(parent:GetHeight() / spacing)
    for i = 1, count do
        local line = parent:CreateTexture(nil, "BACKGROUND", nil, 2)
        line:SetColorTexture(0.08, 0.08, 0.08, 0.6)
        line:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, -(i - 1) * spacing)
        line:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, -(i - 1) * spacing)
        line:SetHeight(1)
    end
end

-- ============================================================================
-- Floating Dust Particle Overlay
-- ============================================================================
local PARTICLE_MAX = 45
local PARTICLE_COLORS = {
    {1.00, 0.84, 0.00},  -- gold
    {1.00, 0.95, 0.70},  -- warm white
    {1.00, 1.00, 1.00},  -- pure white
}

local function InitParticleSystem(window)
    if window.__particleInit then return end
    window.__particleInit = true

    local pool = {}
    local particles = {}

    for i = 1, PARTICLE_MAX do
        local t = window:CreateTexture(nil, "OVERLAY", nil, 7)
        t:SetBlendMode("ADD")
        t:Hide()
        pool[i] = t
    end

    local function SpawnParticle()
        for i = 1, PARTICLE_MAX do
            local t = pool[i]
            if not t.__active then
                local color = PARTICLE_COLORS[math.random(1, #PARTICLE_COLORS)]
                local size = math.random(1, 3)
                local x = math.random(0, 900)
                local y = math.random(-20, 600)
                local speed = math.random(3, 8)
                local drift = (math.random() - 0.5) * 0.8
                local life = math.random(3, 8)

                t:SetColorTexture(color[1], color[2], color[3], 1)
                t:SetSize(size, size)
                t:SetPoint("BOTTOMLEFT", window, "BOTTOMLEFT", x, y)
                t:SetAlpha(0)
                t:Show()

                t.__active = true
                t.__px = x
                t.__py = y
                t.__speed = speed
                t.__drift = drift
                t.__driftOffset = math.random() * math.pi * 2
                t.__life = 0
                t.__maxLife = life
                t.__fadeIn = math.random(0.4, 0.9)
                t.__fadeOutStart = life - math.random(0.8, 1.5)
                t.__size = size

                particles[#particles + 1] = t
                return
            end
        end
    end

    local elapsed = 0
    window:SetScript("OnUpdate", function(self, delta)
        elapsed = elapsed + delta
        if elapsed >= 0.05 then
            elapsed = 0
            -- Spawn new particles if below target density
            local activeCount = 0
            for i = 1, PARTICLE_MAX do
                if pool[i].__active then activeCount = activeCount + 1 end
            end
            if activeCount < PARTICLE_MAX then
                for i = 1, 3 do
                    SpawnParticle()
                end
            end
        end

        local i = 1
        while i <= #particles do
            local p = particles[i]
            p.__life = p.__life + delta
            p.__py = p.__py + p.__speed * delta * 6
            p.__px = p.__px + math.sin(p.__life + p.__driftOffset) * p.__drift

            local alpha = 0
            if p.__life < p.__fadeIn then
                alpha = p.__life / p.__fadeIn
            elseif p.__life > p.__fadeOutStart then
                alpha = 1 - ((p.__life - p.__fadeOutStart) / (p.__maxLife - p.__fadeOutStart))
            else
                alpha = 1
            end
            alpha = math.max(0, math.min(1, alpha)) * 0.45

            p:SetAlpha(alpha)
            p:SetPoint("BOTTOMLEFT", window, "BOTTOMLEFT", p.__px, p.__py)

            if p.__life >= p.__maxLife or p.__py > 650 then
                p.__active = false
                p:Hide()
                table.remove(particles, i)
            else
                i = i + 1
            end
        end
    end)

    window.__particles = particles
    window.__particlePool = pool

    -- Seed initial burst
    for i = 1, PARTICLE_MAX do
        SpawnParticle()
    end
end

local function StopParticleSystem(window)
    if not window.__particleInit then return end
    window:SetScript("OnUpdate", nil)
    for i = 1, PARTICLE_MAX do
        local t = window.__particlePool[i]
        if t then
            t.__active = false
            t:Hide()
        end
    end
    wipe(window.__particles)
end

-- ============================================================================
-- Page Data
-- ============================================================================
local PAGES = {
    {
        title = L("page1_title", "Welcome to the NEW Arena Core"),
        body = L("page1_body", "The core premise of Arena Core was always being an entire suite of features that help reduce the use of countless addons and being an isolated arena home with easy settings and options to navigate through.\n\nWhen Midnight came, it changed everything. Since then I have been working on ways to bring Arena Core back to its roots, being a one stop suite for PvPers.\n\nThis update brings just that and MORE, |cffffd700with even more on the way.|r\n\n|cffffaa00A new page has been added called|r |cff44ff44\"Arena Arsenal\"|r |cffffaa00which contains TONS of new features and customization options. Most of these are turned ON by default. You can make changes within the UI before hopping into arena.|r"),
        images = {},
    },
    {
        title = L("page2_title", "Misc Reminders"),
        body = L("page2_body", "The Arena Arsenal page now includes several reminder features to keep you prepared before every match:\n\n|cff44ff44• Buff Reminder|r -- reminds you about missing class buffs before arena.\n|cff44ff44• Talent Reminder|r -- alerts you to check your talents in prep room.\n|cff44ff44• Gear Reminder|r -- scans your gear and warns if any PvE items are equipped.\n|cff44ff44• Honor Cap Reminder|r -- warns when you are approaching your honor point threshold."),
        images = {
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\buffreminder.png", w = 200, h = 114},
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\gearreminder.png", w = 250, h = 144},
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\honorcap.png", w = 250, h = 98},
        },
    },
    {
        title = L("page3_title", "Big Debuffs / Buffs"),
        body = L("page3_body", "Crowd Control alerts and healer-in-CC warnings are now available. Show important spells on portraits and you can even edit all of these new features in real time using the new solo raid frame feature, showing your actual built in raid frame in real time even while NOT in a group.\n\nYou can customize border styles, color overlays, and more to suit your needs."),
        images = {
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\bigdebuffs.png", w = 220, h = 89},
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\bigdebuffs2.png", w = 220, h = 89},
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\healershaft.png", w = 250, h = 137},
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\portraits.png", w = 250, h = 137},
        },
    },
    {
        title = L("page4_title", "PvP Queue Notifier"),
        body = L("page4_body", "Never miss a queue pop again! A custom flashing colored overlay covers your entire screen and plays a sound when your selected queue types pop.\n\nYou can enable all brackets or just some of them. Solo Shuffle is turned ON by default."),
        images = {
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\queuenotifier.png", w = 380, h = 240},
        },
    },
    {
        title = L("page5_title", "Safe Queue System"),
        body = L("page5_body", "Adds a timer under the queue pop window so you do not miss the accept timer. Works for PvP and LFG queues.\n\nAlso adds a custom Safe Queue box on your screen that you can freely drag around and resize. Includes a chat notification showing how long you were in queue for when it pops."),
        images = {
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\safequeue.png", w = 333, h = 235},
        },
    },
    {
        title = L("page6_title", "Party Trinkets"),
        body = L("page6_body", "You can now track friendly party trinkets with various design and customization options. Fine tune them to your liking so you always know when your teammates have trinkets available or on cooldown."),
        images = {
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\trinket1.png", w = 239, h = 112},
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\trinket2.png", w = 244, h = 124},
        },
    },
    {
        title = L("page7_title", "Target of Targets"),
        body = L("page7_body", "Two new targeting features have been added:\n\n|cff44ff44• Friendly Targets on Arena Frames|r -- adds custom class icon indicators on enemy arena frames showing who your teammates are targeting. Includes naming options and class color outlines.\n\n|cff44ff44• Arena Targets on Party Frames|r -- shows class icons or names of enemy players targeting your teammates."),
        images = {
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\targetsoftarget.png", w = 265, h = 129},
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\targetframes.png", w = 320, h = 140},
        },
    },
    {
        title = L("page8_title", "Absorbs & Debuffs Are Back!"),
        body = L("page8_body", "Absorbs and Debuffs were disabled during Midnight restrictions and have since been restored.\n\nYou can find the settings in the same spot as before -- the Arena Frames page under Debuffs and Absorbs. Simplified settings are in place for Midnight compatibility, and various filters are available to fine tune what you see."),
        images = {
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\debuffsagain.png", w = 320, h = 140},
            {path = "Interface\\AddOns\\ArenaCore\\Media\\Onboarding\\absorbsnow.png", w = 320, h = 140},
        },
    },
}

-- ============================================================================
-- Core Frame Creation
-- ============================================================================
function OB:Create()
    -- Always recreate frame to ensure consistent behavior on every Show()
    -- This eliminates first-load vs cached-load differences
    if self.frame then
        self.frame:Hide()
        self.frame:SetParent(nil)
        self.frame = nil
    end

    -- print("|cffFFAA00ArenaCore:|r [Onboarding] Creating new frame")
    local f = CreateFrame("Frame", "ArenaCore_Onboarding", UIParent)
    if not f then
        -- print("|cffFFAA00ArenaCore:|r [Onboarding] ERROR: CreateFrame returned nil!")
        return nil
    end
    -- print("|cffFFAA00ArenaCore:|r [Onboarding] Frame created successfully")

    f:SetAllPoints(UIParent)
    f:SetFrameStrata("FULLSCREEN_DIALOG")
    f:SetFrameLevel(999)
    f:Hide()
    f:SetAlpha(0)
    -- print("|cffFFAA00ArenaCore:|r [Onboarding] Frame properties set")

    -- Backdrop
    local bg = AC:CreateFlatTexture(f, "BACKGROUND", 0, {0, 0, 0, 1}, 1)
    bg:SetAllPoints()
    bg:SetAlpha(0.88)
    f.bg = bg

    -- Main window (wide, not tall - increased height to fit header inside)
    local winW, winH = 900, 600
    local window = CreateFrame("Frame", nil, f)
    window:SetSize(winW, winH)
    window:SetPoint("CENTER", 0, 0)
    f.window = window

    -- Window background
    local winBg = AC:CreateFlatTexture(window, "BACKGROUND", 1, {0.10, 0.10, 0.10, 1}, 1)
    winBg:SetAllPoints()
    AddScanlines(window)
    AC:AddWindowEdge(window, 1, 0)

    -- Floating dust particles overlay
    InitParticleSystem(window)

    -- Purple accent line at top of window
    local accent = AC:CreateFlatTexture(window, "OVERLAY", 3, {0.545, 0.271, 1.000, 1}, 1)
    accent:SetPoint("TOPLEFT", window, "TOPLEFT", 1, -1)
    accent:SetPoint("TOPRIGHT", window, "TOPRIGHT", -1, -1)
    accent:SetHeight(2)

    -- Logo area (inside window, at top)
    local logo = window:CreateTexture(nil, "ARTWORK")
    logo:SetTexture("Interface/AddOns/ArenaCore/Media/Textures/aclogo.tga")
    logo:SetSize(70, 70)
    logo:SetPoint("TOP", window, "TOP", 0, -20)
    f.logo = logo

    local title = window:CreateFontString(nil, "OVERLAY")
    title:SetFont("Interface/AddOns/ArenaCore/Media/Fonts/arenacore.ttf", 32, "OUTLINE")
    title:SetTextColor(1, 1, 1, 1)
    title:SetPoint("TOP", logo, "BOTTOM", 0, -6)
    title:SetText(GradientText("ARENA CORE", {0.545, 0.271, 1.000}, {1.000, 0.478, 0.094}))
    f.title = title

    -- Subtitle
    local sub = window:CreateFontString(nil, "OVERLAY")
    sub:SetFont("Interface/AddOns/ArenaCore/Media/Fonts/arenacore.ttf", 12, "")
    sub:SetTextColor(0.7, 0.7, 0.7, 1)
    sub:SetPoint("TOP", title, "BOTTOM", 0, -3)
    sub:SetText("v" .. (AC.Version or "0.10.4") .. " -- " .. L("subtitle", "What is new"))
    f.subtitle = sub

    -- Content container (positioned below header)
    local content = CreateFrame("Frame", nil, window)
    content:SetPoint("TOPLEFT", window, "TOPLEFT", 24, -160)
    content:SetPoint("BOTTOMRIGHT", window, "BOTTOMRIGHT", -24, 68)
    -- Set explicit dimensions to ensure layout is computed immediately
    content:SetSize(852, 372)
    f.content = content

    -- Page title
    local pageTitle = content:CreateFontString(nil, "OVERLAY")
    pageTitle:SetFont("Interface/AddOns/ArenaCore/Media/Fonts/arenacore.ttf", 22, "OUTLINE")
    pageTitle:SetTextColor(1, 0.84, 0, 1)
    pageTitle:SetPoint("TOPLEFT", content, "TOPLEFT", 0, 0)
    pageTitle:SetPoint("TOPRIGHT", content, "TOPRIGHT", 0, 0)
    pageTitle:SetJustifyH("LEFT")
    f.pageTitle = pageTitle

    -- Page body
    local pageBody = content:CreateFontString(nil, "OVERLAY")
    pageBody:SetFont("Interface/AddOns/ArenaCore/Media/Fonts/arenacore.ttf", 15, "")
    pageBody:SetTextColor(0.85, 0.85, 0.85, 1)
    pageBody:SetPoint("TOPLEFT", pageTitle, "BOTTOMLEFT", 0, -16)
    pageBody:SetPoint("TOPRIGHT", pageTitle, "BOTTOMRIGHT", 0, -16)
    pageBody:SetWidth(content:GetWidth())
    pageBody:SetJustifyH("LEFT")
    pageBody:SetSpacing(6)
    f.pageBody = pageBody

    -- Image container (created on demand per page)
    f.imageContainer = nil

    -- Navigation bar at bottom of window
    local navBar = CreateFrame("Frame", nil, window)
    navBar:SetHeight(44)
    navBar:SetPoint("BOTTOMLEFT", window, "BOTTOMLEFT", 12, 8)
    navBar:SetPoint("BOTTOMRIGHT", window, "BOTTOMRIGHT", -12, 8)
    f.navBar = navBar

    -- Page counter text
    local counter = navBar:CreateFontString(nil, "OVERLAY")
    counter:SetFont("Interface/AddOns/ArenaCore/Media/Fonts/arenacore.ttf", 14, "")
    counter:SetTextColor(0.6, 0.6, 0.6, 1)
    counter:SetPoint("CENTER", navBar, "CENTER", 0, 0)
    f.counter = counter

    -- Back button
    local backBtn = AC:CreateProfileStyleButton(navBar, 110, 32, L("btn_back", "Back"), "primary", 13)
    backBtn:SetPoint("LEFT", navBar, "LEFT", 0, 0)
    backBtn:Hide()
    f.backBtn = backBtn

    -- Next button
    local nextBtn = AC:CreateProfileStyleButton(navBar, 110, 32, L("btn_next", "Next"), "primary", 13)
    nextBtn:SetPoint("RIGHT", navBar, "RIGHT", 0, 0)
    f.nextBtn = nextBtn

    -- Escape buttons (only on last page)
    local escapeBar = CreateFrame("Frame", nil, window)
    escapeBar:SetHeight(44)
    escapeBar:SetPoint("BOTTOMLEFT", window, "BOTTOMLEFT", 12, 8)
    escapeBar:SetPoint("BOTTOMRIGHT", window, "BOTTOMRIGHT", -12, 8)
    escapeBar:Hide()
    f.escapeBar = escapeBar

    local btnSkim = AC:CreateProfileStyleButton(escapeBar, 380, 36, L("btn_skim", "I skimmed everything, sounds dope"), "success", 13)
    btnSkim:SetPoint("LEFT", escapeBar, "LEFT", 0, 0)
    f.btnSkim = btnSkim

    local btnExit = AC:CreateProfileStyleButton(escapeBar, 380, 36, L("btn_exit", "Get me out of this screen, I didn't read anything"), "danger", 13)
    btnExit:SetPoint("RIGHT", escapeBar, "RIGHT", 0, 0)
    f.btnExit = btnExit

    -- Fade animations
    local fadeIn = f:CreateAnimationGroup()
    local a1 = fadeIn:CreateAnimation("Alpha")
    a1:SetFromAlpha(0)
    a1:SetToAlpha(1)
    a1:SetDuration(0.4)
    a1:SetSmoothing("OUT")
    f.fadeIn = fadeIn

    local fadeOut = f:CreateAnimationGroup()
    local a2 = fadeOut:CreateAnimation("Alpha")
    a2:SetFromAlpha(1)
    a2:SetToAlpha(0)
    a2:SetDuration(0.25)
    a2:SetSmoothing("OUT")
    fadeOut:SetScript("OnFinished", function()
        f:Hide()
        f:SetAlpha(0)
    end)
    f.fadeOut = fadeOut

    -- Keyboard support (arrow keys + Enter)
    f:SetScript("OnKeyDown", function(selfFrame, key)
        if key == "RIGHT" then
            OB:NextPage()
        elseif key == "LEFT" then
            OB:PrevPage()
        elseif key == "ESCAPE" then
            -- Only allow escape on last page
            if OB.currentPage == #PAGES then
                OB:Finish()
            end
        end
    end)
    f:SetPropagateKeyboardInput(false)

    -- Click handlers
    backBtn:SetScript("OnClick", function() OB:PrevPage() end)
    nextBtn:SetScript("OnClick", function() OB:NextPage() end)
    btnSkim:SetScript("OnClick", function() OB:Finish() end)
    btnExit:SetScript("OnClick", function() OB:Finish() end)

    self.frame = f
    self.currentPage = 0
    -- print("|cffFFAA00ArenaCore:|r [Onboarding] Frame creation complete, returning frame")
    return f
end

-- ============================================================================
-- Page Rendering
-- ============================================================================
function OB:RenderPage(idx)
    local f = self.frame
    if not f then return end

    local page = PAGES[idx]
    if not page then return end

    -- Title
    f.pageTitle:SetText(page.title)

    -- Body -- reset anchors first, then conditionally extend
    f.pageBody:ClearAllPoints()
    f.pageBody:SetPoint("TOPLEFT", f.pageTitle, "BOTTOMLEFT", 0, -14)
    f.pageBody:SetPoint("TOPRIGHT", f.pageTitle, "BOTTOMRIGHT", 0, -14)
    f.pageBody:SetText(page.body)

    -- Force text layout calculation by reading height
    -- This ensures pageBody has computed its wrapped text height before we anchor images to it
    local _ = f.pageBody:GetHeight()

    -- Clear old images
    if f.imageContainer then
        f.imageContainer:Hide()
        f.imageContainer:SetParent(nil)
        f.imageContainer = nil
    end

    local hasImages = #page.images > 0

    if hasImages then
        -- Defer image creation two ticks to ensure text layout has computed
        -- First load needs more time for WoW to process text wrapping
        C_Timer.After(0, function()
            C_Timer.After(0, function()
                if not f or not f:IsShown() then return end

                -- Create image container
                local imgContainer = CreateFrame("Frame", nil, f.content)
                imgContainer:SetPoint("TOPLEFT", f.pageBody, "BOTTOMLEFT", 0, -16)
                imgContainer:SetPoint("BOTTOMRIGHT", f.content, "BOTTOMRIGHT", 0, 0)
                f.imageContainer = imgContainer

                -- Fixed content width: 900px window - 24px left margin - 24px right margin = 852px
                local contentW = 852

                local imgCount = #page.images
                local totalImgW = 0
                for _, imgData in ipairs(page.images) do
                    totalImgW = totalImgW + (imgData.w or 200)
                end
                
                -- Calculate scale factor if images are too wide
                local minGap = 8
                local maxGap = 16
                local scale = 1.0
                local totalRequired = totalImgW + (minGap * (imgCount - 1))
                
                if totalRequired > contentW then
                    scale = (contentW - (minGap * (imgCount - 1))) / totalImgW
                end
                
                -- Calculate positioning
                local scaledTotalW = totalImgW * scale
                local xOff
                
                if imgCount == 1 then
                    -- Center single image
                    xOff = (contentW - scaledTotalW) / 2
                else
                    -- Multiple images: use dynamic gap
                    local totalGapWidth = contentW - scaledTotalW
                    local dynamicGap = totalGapWidth / (imgCount + 1)
                    
                    -- Clamp gap between min and max
                    if dynamicGap < minGap then dynamicGap = minGap end
                    if dynamicGap > maxGap then dynamicGap = maxGap end
                    xOff = dynamicGap
                end
                for i, imgData in ipairs(page.images) do
                    local scaledW = (imgData.w or 200) * scale
                    local scaledH = (imgData.h or 140) * scale
                    
                    local imgFrame = CreateFrame("Frame", nil, imgContainer)
                    imgFrame:SetSize(scaledW, scaledH)
                    imgFrame:SetPoint("TOPLEFT", imgContainer, "TOPLEFT", xOff, 0)

                    local tex = imgFrame:CreateTexture(nil, "ARTWORK")
                    tex:SetTexture(imgData.path)
                    tex:SetAllPoints(imgFrame)
                    tex:SetTexCoord(0, 1, 0, 1)

                    -- Subtle border around image
                    AC:AddHairlineRect(imgFrame, {0.3, 0.3, 0.3, 1}, 0.5, 5)

                    -- Update xOff for next image (only for multiple images)
                    if imgCount > 1 then
                        local totalGapWidth = contentW - scaledTotalW
                        local dynamicGap = totalGapWidth / (imgCount + 1)
                        if dynamicGap < minGap then dynamicGap = minGap end
                        if dynamicGap > maxGap then dynamicGap = maxGap end
                        xOff = xOff + scaledW + dynamicGap
                    end
                end

                imgContainer:Show()
            end)
        end)
    else
        -- No images: let body text fill more space
        f.pageBody:SetPoint("BOTTOMLEFT", f.content, "BOTTOMLEFT", 0, 10)
    end

    -- Update counter
    f.counter:SetText(string.format(L("page_counter", "Page %d / %d"), idx, #PAGES))

    -- Update nav visibility
    if idx == 1 then
        f.backBtn:Hide()
    else
        f.backBtn:Show()
    end

    if idx == #PAGES then
        f.nextBtn:Hide()
        f.counter:Hide()
        f.escapeBar:Show()
        -- Reposition back button above escape buttons on last page, aligned left
        f.backBtn:ClearAllPoints()
        f.backBtn:SetPoint("BOTTOMLEFT", f.escapeBar, "TOPLEFT", 0, 8)
    else
        f.nextBtn:Show()
        f.counter:Show()
        f.escapeBar:Hide()
        -- Reset back button to navBar position
        f.backBtn:ClearAllPoints()
        f.backBtn:SetPoint("LEFT", f.navBar, "LEFT", 0, 0)
    end
end

function OB:NextPage()
    if self.currentPage < #PAGES then
        self.currentPage = self.currentPage + 1
        self:RenderPage(self.currentPage)
    end
end

function OB:PrevPage()
    if self.currentPage > 1 then
        self.currentPage = self.currentPage - 1
        self:RenderPage(self.currentPage)
    end
end

function OB:Finish()
    SetSeenVersion(self.VERSION)
    self:Hide()
end

-- ============================================================================
-- Show / Hide
-- ============================================================================
function OB:Show(force)
    -- print("|cffFFAA00ArenaCore:|r [Onboarding] Show called, force:", tostring(force))
    local f = self:Create()
    if not f then
        -- print("|cffFFAA00ArenaCore:|r [Onboarding] Create returned nil!")
        return
    end

    if not force and GetSeenVersion() == self.VERSION then
        -- print("|cffFFAA00ArenaCore:|r [Onboarding] Already seen, skipping (force:", tostring(force), ")")
        return
    end

    -- print("|cffFFAA00ArenaCore:|r [Onboarding] About to show frame")
    f:Show()
    f.fadeOut:Stop()

    f:EnableMouse(true)
    f:EnableKeyboard(true)
    f:SetPropagateKeyboardInput(false)

    f:SetAlpha(1)
    f.fadeIn:Stop()
    f.fadeIn:Play()

    C_Timer.After(0.1, function()
        if f and f:IsShown() and f:GetAlpha() <= 0 then
            f:SetAlpha(1)
        end
    end)

    -- Start at page 1
    self.currentPage = 1
    self:RenderPage(1)
    -- print("|cffFFAA00ArenaCore:|r [Onboarding] Frame shown, page rendered")
end

function OB:Hide()
    local f = self.frame
    if not f or not f:IsShown() then
        return
    end
    f.fadeIn:Stop()
    f.fadeOut:Stop()
    f.fadeOut:Play()
    if f.window then
        StopParticleSystem(f.window)
    end
end

function OB:MaybeShowOnLoginReload()
    -- print("|cffFFAA00ArenaCore:|r [Onboarding] MaybeShowOnLoginReload called, seen version:", GetSeenVersion(), "current version:", self.VERSION)
    if GetSeenVersion() == self.VERSION then
        -- print("|cffFFAA00ArenaCore:|r [Onboarding] Already seen, skipping")
        return
    end
    if InCombatLockdown and InCombatLockdown() then
        -- print("|cffFFAA00ArenaCore:|r [Onboarding] In combat, retrying later")
        C_Timer.After(1.0, function()
            self:MaybeShowOnLoginReload()
        end)
        return
    end
    -- print("|cffFFAA00ArenaCore:|r [Onboarding] Showing onboarding")
    self:Show(false)
end

-- ============================================================================
-- Reset (dev helper)
-- ============================================================================
function OB:Reset()
    EnsureGlobalDB()
    _G.ArenaCoreDB.global.onboardingSeenVersion = nil
    -- print("|cffFFAA00ArenaCore:|r Onboarding reset.")
end

-- ============================================================================
-- Auto-show on login / reload (self-contained so it always fires)
-- ============================================================================
local onboardingEventFrame = CreateFrame("Frame")
onboardingEventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
onboardingEventFrame:SetScript("OnEvent", function(self, event, isInitialLogin, isReloadingUi)
    if event == "PLAYER_ENTERING_WORLD" then
        if isInitialLogin or isReloadingUi then
            C_Timer.After(1.5, function()
                if OB and OB.MaybeShowOnLoginReload then
                    OB:MaybeShowOnLoginReload()
                end
            end)
        end
        self:UnregisterEvent("PLAYER_ENTERING_WORLD")
    end
end)
