local _, ns = ...

do
    local WIZARD_WIDTH = 520
    local WIZARD_FRAME_HORIZONTAL_PADDING = 8
    local WIZARD_FRAME_VERTICAL_PADDING = 36
    local WIZARD_FIXED_HEIGHT = 448
    local WIZARD_NAV_BOTTOM = 14
    local POST_SETUP_SETTINGS_PROMPT_PENDING_KEY = "_postSetupSettingsPromptPending"

    local wizardFrame
    local settingsPromptFrame
    local setupWizardShownThisSession = false
    local settingsPromptShownThisSession = false
    local settingsPromptPendingThisSession = false
    local wizardExplicitHidePending = false
    local settingsPromptExplicitHidePending = false
    local wizardReopenScheduled = false
    local settingsPromptReopenScheduled = false
    local previousSetupComplete

    local WIZARD_RELEASE_TAGS = {
        alpha = {
            text = "(Alpha)",
            color = { 0.96, 0.40, 0.40 },
            tooltip = "May not work as intended or may have significant functional or performance issues.",
        },
        beta = {
            text = "(Beta)",
            color = { 0.49, 0.78, 1.00 },
            tooltip = "May contain minor to medium level functional or performance issues.",
        },
        experimental = {
            text = "(Experimental)",
            color = { 1.00, 0.63, 0.26 },
            tooltip = "May not be functional or behave unexpectedly.",
        },
        prerelease = {
            text = "(Pre-Release)",
            color = { 0.63, 0.95, 0.55 },
            tooltip = "Mostly stable but may contain minor issues.",
        },
    }

    local WIZARD_MODULES = {
        {
            key = "objectiveTracker",
            dbKey = "objectiveTracker",
            name = "Objective Tracker",
            releaseTag = "beta",
            desc = "Adds a scrollbar and typography, colors, search, filtering, organization customization and more to the quest tracker.",
        },
        {
            key = "consumables",
            dbKey = "consumables",
            name = "Consumables Reminder",
            releaseTag = "prerelease",
            desc = "Clickable icons for flasks, food, weapon buffs, runes and poisons.",
        },
        {
            key = "housing",
            dbKey = "housing",
            name = "Housing",
            releaseTag = "prerelease",
            desc = "Tracks newly obtained housing decor and improves inventory sorting.",
        },
        {
            key = "worldQuests",
            dbKey = "worldQuests",
            name = "World Quests",
            releaseTag = "beta",
            desc = "Adds a world quest tab to your map with filters, search, and sorting.",
        },
        {
            key = "classesMonk",
            dbPath = { "classes" },
            name = "Classes",
            releaseTag = "prerelease",
            desc = "Enable only the class modules you want. You can fine-tune each module later in settings.",
        },
        {
            key = "reminders",
            dbPath = { "reminders" },
            name = "Reminders",
            releaseTag = "",
            desc = "Various reminders for Talent Loadouts, Great Vault Specialization, Dungeon Difficulty in groups, and similar.",
        },
        {
            key = "miscellaneous",
            dbKey = "miscellaneous",
            name = "Miscellaneous",
            releaseTag = "prerelease",
            desc = "Micro menu, character stats, shopping list, and cutscene automation. All of these share the Miscellaneous module switch.",
        },
    }

    local REMINDER_FEATURES = {
        {
            stateKey = "dungeonDifficulty",
            dbKey = "dungeonDifficulty",
            name = "Dungeon Difficulty",
            desc = "Shows a reminder when your group’s dungeon difficulty does not match the dungeon you are entering.",
        },
        {
            stateKey = "greatVault",
            dbKey = "greatVault",
            name = "Great Vault",
            desc = "Reminds you to pick a Great Vault reward specialization before opening the vault.",
        },
        {
            stateKey = "talentLoadout",
            dbKey = "talentLoadout",
            name = "Talent Loadouts",
            desc = "Reminds you when your active talent loadout does not match your saved preference for the content you are in.",
        },
    }

    local CLASS_MODULES = {
        {
            stateKey = "classMonk",
            name = "Monk",
            desc = "Brewmaster: Expel Harm Tracker bar anchored to your resource UI.",
        },
    }

    local MISC_FEATURES = {
        {
            stateKey = "menuBar",
            name = "Micro Menu and Queue Eye",
            desc = "Separates the Micro Menu Bar from the bag bar and anchors the queue eye to the minimap.",
        },
        {
            stateKey = "characterStats",
            name = "Character Stats",
            desc = "Adds a customizable character stats display.",
        },
        {
            stateKey = "shoppingList",
            name = "Shopping List",
            desc = "Tracks items you want to buy and shows a compact shopping list.",
        },
        {
            stateKey = "cutscenes",
            name = "Skip Seen Cutscenes",
            desc = "Automatically skips in-game movies and cinematics you have already watched on this character.",
        },
    }

    local function GetReleaseTagInfo(tagKey)
        if type(tagKey) ~= "string" or tagKey == "" then
            return nil
        end

        return WIZARD_RELEASE_TAGS[string.lower(tagKey)]
    end

    local function ShowReleaseTagTooltip(owner, tagInfo)
        if not owner or not tagInfo or not GameTooltip then
            return
        end

        GameTooltip:SetOwner(owner, "ANCHOR_RIGHT")
        GameTooltip:SetText(tagInfo.text, tagInfo.color[1], tagInfo.color[2], tagInfo.color[3])
        GameTooltip:AddLine(tagInfo.tooltip, 1, 1, 1, true)
        GameTooltip:Show()
    end

    local function HideReleaseTagTooltip()
        if GameTooltip then
            GameTooltip:Hide()
        end
    end

    -- BigWigs registers this clip in LibSharedMedia as "BigWigs: Info" (BigWigs Media/Media.lua).
    -- LibSharedMedia stores mediatype keys lowercased; always use "sound" for Fetch.
    -- If Fetch is empty (LSM not ready / override edge cases), fall back to the file path only while BigWigs is loaded.
    local BIGWIGS_INFO_SOUND_KEY = "BigWigs: Info"
    local BIGWIGS_INFO_SOUND_PATH = [[Interface\AddOns\BigWigs\Media\Sounds\Info.ogg]]

    local function IsBigWigsAddOnLoaded()
        local fn = (C_AddOns and C_AddOns.IsAddOnLoaded) or rawget(_G, "IsAddOnLoaded")
        return type(fn) == "function" and fn("BigWigs") == true
    end

    local function PlayWizardForwardSound()
        local path
        local libStub = rawget(_G, "LibStub")
        if type(libStub) == "function" then
            local lsm = libStub("LibSharedMedia-3.0", true)
            if lsm and type(lsm.Fetch) == "function" then
                path = lsm:Fetch("sound", BIGWIGS_INFO_SOUND_KEY, true)
            end
        end

        if type(path) == "number" then
            local playSound = rawget(_G, "PlaySound")
            if type(playSound) == "function" then
                pcall(playSound, path, "Master", false)
            end
            return
        end

        if type(path) ~= "string" or path == "" then
            path = nil
        end
        if not path and IsBigWigsAddOnLoaded() then
            path = BIGWIGS_INFO_SOUND_PATH
        end
        if not path then
            return
        end

        local playSoundFile = rawget(_G, "PlaySoundFile")
        if type(playSoundFile) ~= "function" then
            return
        end
        -- Master so it is audible when Sound Effects volume is muted; try slash style Blizzard also accepts.
        if not pcall(playSoundFile, path, "Master") then
            pcall(playSoundFile, (path:gsub("\\", "/")), "Master")
        end
    end

    local function CreateDefaultSelectionState()
        return {
            objectiveTracker = false,
            consumables = false,
            housing = false,
            worldQuests = false,
            classMonk = false,
            dungeonDifficulty = false,
            greatVault = false,
            talentLoadout = false,
            menuBar = false,
            characterStats = false,
            shoppingList = false,
            cutscenes = false,
        }
    end

    local function BuildQuickSetupSelectionState()
        local s = CreateDefaultSelectionState()
        for key in pairs(s) do
            s[key] = true
        end
        return s
    end

    ---@param state table
    local function ApplyWizardSelectionState(state)
        if not ns.db or type(state) ~= "table" then
            return
        end

        if type(ns.db.objectiveTracker) ~= "table" then
            ns.db.objectiveTracker = {}
        end
        ns.db.objectiveTracker.enabled = state.objectiveTracker and true or false

        local consumableSettings = ns.GetConsumablesModuleSettings and ns.GetConsumablesModuleSettings()
        if consumableSettings then
            consumableSettings.enabled = state.consumables and true or false
        end

        if type(ns.db.housing) ~= "table" then
            ns.db.housing = {}
        end
        ns.db.housing.enabled = state.housing and true or false

        if type(ns.db.worldQuests) ~= "table" then
            ns.db.worldQuests = {}
        end
        ns.db.worldQuests.enabled = state.worldQuests and true or false

        if type(ns.db.classes) ~= "table" then
            ns.db.classes = {}
        end
        if type(ns.db.classes.monk) ~= "table" then
            ns.db.classes.monk = {}
        end

        local anyClassModule = state.classMonk and true or false
        ns.db.classes.enabled = anyClassModule

        if state.classMonk then
            ns.db.classes.monk.enabled = true
            ns.db.classes.monk.moduleEnabled = true
        else
            ns.db.classes.monk.enabled = false
            ns.db.classes.monk.moduleEnabled = false
        end

        if type(ns.db.dungeonDifficulty) ~= "table" then
            ns.db.dungeonDifficulty = {}
        end
        if type(ns.db.greatVault) ~= "table" then
            ns.db.greatVault = {}
        end
        if type(ns.db.talentLoadout) ~= "table" then
            ns.db.talentLoadout = {}
        end
        ns.db.dungeonDifficulty.enabled = state.dungeonDifficulty and true or false
        ns.db.greatVault.enabled = state.greatVault and true or false
        ns.db.talentLoadout.enabled = state.talentLoadout and true or false

        local anyReminder = state.dungeonDifficulty or state.greatVault or state.talentLoadout
        if type(ns.db.reminders) ~= "table" then
            ns.db.reminders = {}
        end
        ns.db.reminders.enabled = anyReminder and true or false

        if type(ns.db.menuBar) ~= "table" then
            ns.db.menuBar = {}
        end
        ns.db.menuBar.enabled = state.menuBar and true or false

        if type(ns.db.characterStats) ~= "table" then
            ns.db.characterStats = {}
        end
        ns.db.characterStats.enabled = state.characterStats and true or false

        local miscSettings = ns.GetMiscellaneousSettings and ns.GetMiscellaneousSettings()
        if miscSettings then
            local shoppingSettings = ns.GetShoppingListSettings and ns.GetShoppingListSettings()
            if shoppingSettings then
                shoppingSettings.enabled = state.shoppingList and true or false
            end

            local miscParentNeeded = state.menuBar or state.characterStats or state.shoppingList or state.cutscenes
            miscSettings.enabled = miscParentNeeded and true or false
        end

        local automationSettings = ns.GetAutomationSettings and ns.GetAutomationSettings()
        if automationSettings then
            automationSettings.skipSeenCutscenes = state.cutscenes and true or false
        end
    end

    local function FinalizeWizardComplete()
        if not ns.db then
            return
        end

        local shouldPromptSettingsAfterReload = previousSetupComplete ~= true and ns.db.setupComplete ~= true

        wizardExplicitHidePending = true
        ns.db.setupComplete = true
        ns.db[POST_SETUP_SETTINGS_PROMPT_PENDING_KEY] = shouldPromptSettingsAfterReload and true or nil
        ReloadUI()
    end

    local function ApplySelectionsAndReload(selection)
        ApplyWizardSelectionState(selection)
        FinalizeWizardComplete()
    end

    function ns.ResetSetupWizard()
        if wizardFrame then
            wizardFrame.restoreSetupOnHide = false
            wizardFrame:Hide()
            wizardFrame = nil
        end
        if ns.db then
            previousSetupComplete = ns.db.setupComplete
            ns.db.setupComplete = false
        end
        setupWizardShownThisSession = false
        settingsPromptShownThisSession = false
        settingsPromptPendingThisSession = false
        wizardExplicitHidePending = false
        settingsPromptExplicitHidePending = false
        wizardReopenScheduled = false
        settingsPromptReopenScheduled = false
    end

    local function RestorePreviousSetupState()
        if previousSetupComplete and ns.db then
            ns.db.setupComplete = previousSetupComplete
        end
        previousSetupComplete = nil
    end

    local function ResetWizardSelections()
        if not wizardFrame then
            return
        end

        wizardFrame.selection = CreateDefaultSelectionState()

        if type(wizardFrame.controlRefs) == "table" then
            for _, cb in ipairs(wizardFrame.controlRefs) do
                if cb and cb.SetChecked then
                    cb:SetChecked(false)
                end
            end
        end

        if wizardFrame.reviewSummary then
            wizardFrame.reviewSummary:SetText("")
        end

        if wizardFrame.SetStep then
            wizardFrame.SetStep(0)
        end
    end

    local function AttachFrameDragHandle(frame)
        if not frame or frame.nomToolsDragHandle then
            return
        end

        frame:EnableMouse(true)
        frame:SetMovable(true)

        local dragHandle = CreateFrame("Frame", nil, frame)
        dragHandle:SetPoint("TOPLEFT", frame, "TOPLEFT", 24, -4)
        dragHandle:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -44, -4)
        dragHandle:SetHeight(24)
        dragHandle:EnableMouse(true)
        dragHandle:RegisterForDrag("LeftButton")
        dragHandle:SetScript("OnDragStart", function()
            frame:StartMoving()
        end)
        dragHandle:SetScript("OnDragStop", function()
            frame:StopMovingOrSizing()
        end)

        frame.nomToolsDragHandle = dragHandle
    end

    local function ResetStandaloneFramePosition(frame)
        if not frame then
            return
        end

        if frame.StopMovingOrSizing then
            frame:StopMovingOrSizing()
        end

        frame:ClearAllPoints()
        frame:SetPoint("CENTER")
    end

    local function EnsureSpecialFrameRegistered(frameName)
        if type(frameName) ~= "string" or frameName == "" or type(UISpecialFrames) ~= "table" then
            return
        end

        for _, existingName in ipairs(UISpecialFrames) do
            if existingName == frameName then
                return
            end
        end

        UISpecialFrames[#UISpecialFrames + 1] = frameName
    end

    local function ScheduleWizardReopen()
        if wizardReopenScheduled then
            return
        end

        if ns.IsSetupComplete and ns.IsSetupComplete() then
            return
        end

        wizardReopenScheduled = true

        local function reopenWizard()
            wizardReopenScheduled = false
            if ns.IsSetupComplete and ns.IsSetupComplete() then
                return
            end
            if wizardFrame and wizardFrame:IsShown() then
                return
            end
            ns.ShowSetupWizard()
        end

        if C_Timer and C_Timer.After then
            C_Timer.After(0, reopenWizard)
        else
            reopenWizard()
        end
    end

    local function ScheduleSettingsPromptReopen()
        if settingsPromptReopenScheduled or not settingsPromptPendingThisSession then
            return
        end

        settingsPromptReopenScheduled = true

        local function reopenSettingsPrompt()
            settingsPromptReopenScheduled = false
            if not settingsPromptPendingThisSession then
                return
            end
            if settingsPromptFrame and settingsPromptFrame:IsShown() then
                return
            end
            ns.ShowPostSetupSettingsPrompt()
        end

        if C_Timer and C_Timer.After then
            C_Timer.After(0, reopenSettingsPrompt)
        else
            reopenSettingsPrompt()
        end
    end

    local function HideWizard(restorePreviousState, isExplicit)
        if not wizardFrame then
            return
        end

        wizardExplicitHidePending = isExplicit == true
        wizardFrame.restoreSetupOnHide = restorePreviousState == true
        wizardFrame:Hide()
    end

    local function HideSettingsPrompt(isExplicit)
        if settingsPromptFrame then
            settingsPromptExplicitHidePending = isExplicit == true
            if isExplicit then
                settingsPromptPendingThisSession = false
            end
            settingsPromptFrame:Hide()
        end
    end

    local function OpenSettingsFromPrompt()
        HideSettingsPrompt(true)
        if ns.OpenOptions then
            ns.OpenOptions()
        end
    end

    function ns.ShowPostSetupSettingsPrompt()
        if settingsPromptFrame then
            settingsPromptFrame:Show()
            return
        end

        settingsPromptFrame = CreateFrame("Frame", "NomToolsPostSetupPrompt", UIParent, "BasicFrameTemplateWithInset")
        settingsPromptFrame:SetSize(456, 148)
        settingsPromptFrame:SetPoint("CENTER")
        settingsPromptFrame:SetFrameStrata("DIALOG")
        settingsPromptFrame:SetFrameLevel(510)
        settingsPromptFrame:SetToplevel(true)
        settingsPromptFrame:SetClampedToScreen(true)
        AttachFrameDragHandle(settingsPromptFrame)
        if settingsPromptFrame.TitleText then
            settingsPromptFrame.TitleText:SetText("NomTools")
        end
        EnsureSpecialFrameRegistered(settingsPromptFrame:GetName())
        settingsPromptFrame:EnableKeyboard(true)
        settingsPromptFrame:SetScript("OnKeyDown", function(self, key)
            if key == "ESCAPE" then
                self:SetPropagateKeyboardInput(false)
                HideSettingsPrompt(true)
            else
                self:SetPropagateKeyboardInput(true)
            end
        end)
        settingsPromptFrame:HookScript("OnHide", function()
            local wasExplicit = settingsPromptExplicitHidePending == true
            settingsPromptExplicitHidePending = false
            if not wasExplicit and settingsPromptPendingThisSession then
                ScheduleSettingsPromptReopen()
            end
        end)

        if settingsPromptFrame.CloseButton then
            settingsPromptFrame.CloseButton:SetScript("OnClick", function()
                HideSettingsPrompt(true)
            end)
        end

        local contentHost = CreateFrame("Frame", nil, settingsPromptFrame, "InsetFrameTemplate3")
        contentHost:SetPoint("TOPLEFT", settingsPromptFrame, "TOPLEFT", 8, -28)
        contentHost:SetPoint("BOTTOMRIGHT", settingsPromptFrame, "BOTTOMRIGHT", -8, 8)
        if ns.ApplyDefaultPanelChrome then
            ns.ApplyDefaultPanelChrome(contentHost)
        end

        local description = contentHost:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        description:SetPoint("TOP", contentHost, "TOP", 0, -18)
        description:SetWidth(396)
        description:SetJustifyH("CENTER")
        description:SetText("Would you like to configure NomTools now or later?\nYou can open the settings at any time by typing /nomtools")
        description:SetTextColor(0.82, 0.82, 0.82)

        local openSettingsButton = CreateFrame("Button", nil, contentHost, "UIPanelButtonTemplate")
        openSettingsButton:SetSize(126, 26)
        openSettingsButton:SetPoint("BOTTOMRIGHT", contentHost, "BOTTOM", -6, 14)
        openSettingsButton:SetText("Open Settings")
        openSettingsButton:SetScript("OnClick", OpenSettingsFromPrompt)

        local laterButton = CreateFrame("Button", nil, contentHost, "UIPanelButtonTemplate")
        laterButton:SetSize(126, 26)
        laterButton:SetPoint("BOTTOMLEFT", contentHost, "BOTTOM", 6, 14)
        laterButton:SetText("Later")
        laterButton:SetScript("OnClick", function()
            HideSettingsPrompt(true)
        end)

        settingsPromptFrame:Show()
    end

    function ns.ResetSetupWizardWindowPositions()
        ResetStandaloneFramePosition(wizardFrame)
        ResetStandaloneFramePosition(settingsPromptFrame)
    end

    function ns.ShowSetupWizard()
        if wizardFrame then
            wizardFrame.restoreSetupOnHide = true
            wizardFrame:Show()
            return
        end

        wizardFrame = CreateFrame("Frame", "NomToolsSetupWizard", UIParent, "BasicFrameTemplateWithInset")
        wizardFrame:SetSize(WIZARD_WIDTH + (WIZARD_FRAME_HORIZONTAL_PADDING * 2), WIZARD_FIXED_HEIGHT)
        wizardFrame:SetPoint("CENTER")
        wizardFrame:SetFrameStrata("DIALOG")
        wizardFrame:SetFrameLevel(500)
        wizardFrame:SetToplevel(true)
        wizardFrame:SetClampedToScreen(true)
        AttachFrameDragHandle(wizardFrame)
        if wizardFrame.Inset then
            wizardFrame.Inset:Hide()
        end
        if wizardFrame.Bg then
            wizardFrame.Bg:SetAlpha(0.92)
        end
        if wizardFrame.InsetBg then
            wizardFrame.InsetBg:SetAlpha(0)
        end
        if wizardFrame.TitleText then
            wizardFrame.TitleText:SetText("NomTools Setup")
        end
        EnsureSpecialFrameRegistered(wizardFrame:GetName())
        wizardFrame:SetScript("OnKeyDown", function(self, key)
            if key == "ESCAPE" then
                self:SetPropagateKeyboardInput(false)
                HideWizard(true, true)
            else
                self:SetPropagateKeyboardInput(true)
            end
        end)
        wizardFrame:EnableKeyboard(true)
        wizardFrame:HookScript("OnHide", function(self)
            local wasExplicit = wizardExplicitHidePending == true
            wizardExplicitHidePending = false
            if wasExplicit and self.restoreSetupOnHide then
                RestorePreviousSetupState()
                ResetWizardSelections()
            end
            self.restoreSetupOnHide = nil
            if not wasExplicit and not (ns.IsSetupComplete and ns.IsSetupComplete()) then
                ScheduleWizardReopen()
            end
        end)

        if wizardFrame.CloseButton then
            wizardFrame.CloseButton:SetScript("OnClick", function()
                HideWizard(true, true)
            end)
        end

        local contentHost = CreateFrame("Frame", nil, wizardFrame, "InsetFrameTemplate3")
        contentHost:SetPoint("TOPLEFT", wizardFrame, "TOPLEFT", WIZARD_FRAME_HORIZONTAL_PADDING, -28)
        contentHost:SetPoint("BOTTOMRIGHT", wizardFrame, "BOTTOMRIGHT", -WIZARD_FRAME_HORIZONTAL_PADDING, 8)
        if ns.ApplyDefaultPanelChrome then
            ns.ApplyDefaultPanelChrome(contentHost)
        end
        wizardFrame.contentHost = contentHost

        local pageArea = CreateFrame("Frame", nil, contentHost)
        pageArea:SetPoint("TOPLEFT", contentHost, "TOPLEFT", 4, -4)
        pageArea:SetPoint("BOTTOMRIGHT", contentHost, "BOTTOMRIGHT", -4, 52)
        wizardFrame.pageArea = pageArea

        local progressText = pageArea:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        progressText:SetPoint("TOPRIGHT", pageArea, "TOPRIGHT", -8, -4)
        progressText:SetJustifyH("RIGHT")
        progressText:SetWidth(160)
        progressText:SetTextColor(0.55, 0.55, 0.55)
        wizardFrame.progressText = progressText

        local navAnchor = CreateFrame("Frame", nil, contentHost)
        navAnchor:SetPoint("BOTTOMLEFT", contentHost, "BOTTOMLEFT", 8, WIZARD_NAV_BOTTOM)
        navAnchor:SetPoint("BOTTOMRIGHT", contentHost, "BOTTOMRIGHT", -8, WIZARD_NAV_BOTTOM)
        navAnchor:SetHeight(28)

        local backBtn = CreateFrame("Button", nil, navAnchor, "UIPanelButtonTemplate")
        backBtn:SetSize(120, 28)
        backBtn:SetPoint("BOTTOMLEFT", navAnchor, "BOTTOMLEFT", 0, 0)
        backBtn:SetText("Back")

        local nextBtn = CreateFrame("Button", nil, navAnchor, "UIPanelButtonTemplate")
        nextBtn:SetSize(140, 28)
        nextBtn:SetPoint("BOTTOMRIGHT", navAnchor, "BOTTOMRIGHT", 0, 0)

        local cancelBtn = CreateFrame("Button", nil, navAnchor, "UIPanelButtonTemplate")
        cancelBtn:SetSize(100, 28)
        cancelBtn:SetPoint("BOTTOM", navAnchor, "BOTTOM", 0, 0)
        cancelBtn:SetText("Cancel")
        cancelBtn:SetScript("OnClick", function()
            HideWizard(true, true)
        end)

        wizardFrame.selection = CreateDefaultSelectionState()
        wizardFrame.controlRefs = {}

        local function BindCheckboxToState(checkbox, stateKey)
            checkbox.nomtoolsStateKey = stateKey
            wizardFrame.controlRefs[#wizardFrame.controlRefs + 1] = checkbox
            checkbox:SetChecked(false)
            checkbox:SetScript("OnClick", function(self)
                wizardFrame.selection[stateKey] = self:GetChecked() and true or false
            end)
        end

        local function SyncAllCheckboxesFromSelection()
            for _, cb in ipairs(wizardFrame.controlRefs) do
                local key = cb.nomtoolsStateKey
                if key then
                    cb:SetChecked(wizardFrame.selection[key] and true or false)
                end
            end
        end

        --- Release tag sits immediately after the title text (title must not use a wide SetWidth or the tag will float far right).
        local function RegisterReleaseTagAfterTitle(content, titleFontString, mod)
            local tagInfo = GetReleaseTagInfo(mod.releaseTag)
            if not tagInfo then
                return
            end

            local tagButton = CreateFrame("Frame", nil, content)
            local tagText = tagButton:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            tagText:SetPoint("LEFT", tagButton, "LEFT", 0, 0)
            tagText:SetText(tagInfo.text)
            tagText:SetTextColor(tagInfo.color[1], tagInfo.color[2], tagInfo.color[3])

            local function LayoutReleaseTag()
                local titleWidth = titleFontString:GetStringWidth() or 0
                tagButton:ClearAllPoints()
                tagButton:SetPoint("LEFT", titleFontString, "LEFT", titleWidth + 6, 0)
                local tagWidth = math.ceil(tagText:GetStringWidth() or 0)
                local tagHeight = math.ceil(tagText:GetStringHeight() or 0)
                tagButton:SetSize(math.max(tagWidth, 1), math.max(tagHeight, 14))
            end

            LayoutReleaseTag()
            -- FontStrings cannot host OnTextChanged; re-layout when the page is shown so metrics are valid.
            if content and content.HookScript then
                content:HookScript("OnShow", LayoutReleaseTag)
            end

            tagButton:EnableMouse(true)
            tagButton:SetScript("OnEnter", function(self)
                ShowReleaseTagTooltip(self, tagInfo)
            end)
            tagButton:SetScript("OnLeave", HideReleaseTagTooltip)
        end

        local pages = {}
        local totalSteps = 1 + #WIZARD_MODULES + 1

        do
            local welcome = CreateFrame("Frame", nil, pageArea)
            welcome:SetAllPoints(pageArea)
            welcome:Hide()
            pages[0] = welcome

            local title = welcome:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
            title:SetPoint("TOP", welcome, "TOP", 0, -12)
            title:SetWidth(WIZARD_WIDTH - 40)
            title:SetJustifyH("CENTER")
            title:SetText("Welcome to NomTools")
            title:SetTextColor(1, 0.88, 0.74)

            local body = welcome:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
            body:SetPoint("TOP", title, "BOTTOM", 0, -16)
            body:SetWidth(WIZARD_WIDTH - 48)
            body:SetJustifyH("CENTER")
            body:SetText(
                "NomTools adds optional UI improvements and tools.\n\n"
                    .. "This guided setup lets you choose exactly which areas to enable. Nothing is turned on until you choose it.\n\n"
                    .. "You can change these later with |cffffcc00/nomtools|r."
            )
            body:SetTextColor(0.82, 0.82, 0.82)

            local startBtn = CreateFrame("Button", nil, welcome, "UIPanelButtonTemplate")
            startBtn:SetSize(220, 30)
            startBtn:SetPoint("TOP", body, "BOTTOM", 0, -28)
            startBtn:SetText("Start Guided Setup")
            startBtn:SetScript("OnClick", function()
                PlayWizardForwardSound()
                wizardFrame.SetStep(1)
            end)

            local quickBtn = CreateFrame("Button", nil, welcome, "UIPanelButtonTemplate")
            quickBtn:SetSize(220, 26)
            quickBtn:SetPoint("TOP", startBtn, "BOTTOM", 0, -10)
            quickBtn:SetText("Quick Setup: Enable Everything")
            quickBtn:SetScript("OnClick", function()
                PlayWizardForwardSound()
                wizardFrame.restoreSetupOnHide = false
                ApplySelectionsAndReload(BuildQuickSetupSelectionState())
            end)
        end

        for modIndex, mod in ipairs(WIZARD_MODULES) do
            local page = CreateFrame("Frame", nil, pageArea)
            page:SetAllPoints(pageArea)
            page:Hide()
            pages[modIndex] = page

            local title = page:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
            title:SetPoint("TOPLEFT", page, "TOPLEFT", 12, -12)
            title:SetWordWrap(false)
            title:SetJustifyH("LEFT")
            title:SetText(mod.name)
            title:SetTextColor(1, 0.88, 0.74)
            RegisterReleaseTagAfterTitle(page, title, mod)

            if mod.key == "reminders" then
                local intro = page:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                intro:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -10)
                intro:SetWidth(WIZARD_WIDTH - 48)
                intro:SetJustifyH("LEFT")
                intro:SetText(mod.desc)
                intro:SetTextColor(0.75, 0.75, 0.75)

                local rowAnchor = intro
                for _, feat in ipairs(REMINDER_FEATURES) do
                    local cb = CreateFrame("CheckButton", nil, page, "UICheckButtonTemplate")
                    cb:SetPoint("TOP", rowAnchor, "BOTTOM", 0, -14)
                    cb:SetPoint("LEFT", page, "LEFT", 20, 0)
                    cb:SetSize(26, 26)
                    BindCheckboxToState(cb, feat.stateKey)

                    local label = cb:CreateFontString(nil, "OVERLAY", "GameFontNormal")
                    label:SetPoint("LEFT", cb, "RIGHT", 6, 2)
                    label:SetText(feat.name)
                    label:SetTextColor(1, 0.88, 0.74)

                    local desc = page:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
                    desc:SetPoint("TOPLEFT", label, "BOTTOMLEFT", 0, -2)
                    desc:SetWidth(WIZARD_WIDTH - 72)
                    desc:SetJustifyH("LEFT")
                    desc:SetText(feat.desc)
                    desc:SetTextColor(0.65, 0.65, 0.65)

                    rowAnchor = desc
                end
            elseif mod.key == "miscellaneous" then
                local intro = page:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                intro:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -10)
                intro:SetWidth(WIZARD_WIDTH - 48)
                intro:SetJustifyH("LEFT")
                intro:SetText(
                    "Miscellaneous features."
                )
                intro:SetTextColor(0.75, 0.75, 0.75)

                local rowAnchor = intro
                for _, feat in ipairs(MISC_FEATURES) do
                    local cb = CreateFrame("CheckButton", nil, page, "UICheckButtonTemplate")
                    cb:SetPoint("TOP", rowAnchor, "BOTTOM", 0, -14)
                    cb:SetPoint("LEFT", page, "LEFT", 20, 0)
                    cb:SetSize(26, 26)
                    BindCheckboxToState(cb, feat.stateKey)

                    local label = cb:CreateFontString(nil, "OVERLAY", "GameFontNormal")
                    label:SetPoint("LEFT", cb, "RIGHT", 6, 2)
                    label:SetText(feat.name)
                    label:SetTextColor(1, 0.88, 0.74)

                    local desc = page:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
                    desc:SetPoint("TOPLEFT", label, "BOTTOMLEFT", 0, -2)
                    desc:SetWidth(WIZARD_WIDTH - 72)
                    desc:SetJustifyH("LEFT")
                    desc:SetText(feat.desc)
                    desc:SetTextColor(0.65, 0.65, 0.65)

                    rowAnchor = desc
                end
            elseif mod.key == "classesMonk" then
                local intro = page:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
                intro:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -10)
                intro:SetWidth(WIZARD_WIDTH - 48)
                intro:SetJustifyH("LEFT")
                intro:SetText(mod.desc)
                intro:SetTextColor(0.75, 0.75, 0.75)

                local rowAnchor = intro
                for _, feat in ipairs(CLASS_MODULES) do
                    local cb = CreateFrame("CheckButton", nil, page, "UICheckButtonTemplate")
                    cb:SetPoint("TOP", rowAnchor, "BOTTOM", 0, -14)
                    cb:SetPoint("LEFT", page, "LEFT", 20, 0)
                    cb:SetSize(26, 26)
                    BindCheckboxToState(cb, feat.stateKey)

                    local label = cb:CreateFontString(nil, "OVERLAY", "GameFontNormal")
                    label:SetPoint("LEFT", cb, "RIGHT", 6, 2)
                    label:SetText(feat.name)
                    label:SetTextColor(1, 0.88, 0.74)

                    local desc = page:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
                    desc:SetPoint("TOPLEFT", label, "BOTTOMLEFT", 0, -2)
                    desc:SetWidth(WIZARD_WIDTH - 72)
                    desc:SetJustifyH("LEFT")
                    desc:SetText(feat.desc)
                    desc:SetTextColor(0.65, 0.65, 0.65)

                    rowAnchor = desc
                end
            else
                local cb = CreateFrame("CheckButton", nil, page, "UICheckButtonTemplate")
                cb:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -12)
                cb:SetSize(26, 26)
                BindCheckboxToState(cb, mod.key)

                local label = cb:CreateFontString(nil, "OVERLAY", "GameFontNormal")
                label:SetPoint("LEFT", cb, "RIGHT", 6, 2)
                label:SetText("Enable " .. mod.name)
                label:SetTextColor(1, 0.88, 0.74)

                local desc = page:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
                desc:SetPoint("TOPLEFT", label, "BOTTOMLEFT", 0, -2)
                desc:SetWidth(WIZARD_WIDTH - 72)
                desc:SetJustifyH("LEFT")
                desc:SetText(mod.desc)
                desc:SetTextColor(0.65, 0.65, 0.65)
            end
        end

        do
            local reviewIndex = #WIZARD_MODULES + 1
            local review = CreateFrame("Frame", nil, pageArea)
            review:SetAllPoints(pageArea)
            review:Hide()
            pages[reviewIndex] = review

            local title = review:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
            title:SetPoint("TOPLEFT", review, "TOPLEFT", 12, -12)
            title:SetText("Review")
            title:SetTextColor(1, 0.88, 0.74)

            local intro = review:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
            intro:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -10)
            intro:SetWidth(WIZARD_WIDTH - 48)
            intro:SetJustifyH("LEFT")
            intro:SetText("You are about to save your choices and reload the UI.")
            intro:SetTextColor(0.75, 0.75, 0.75)

            local summary = review:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            summary:SetPoint("TOPLEFT", intro, "BOTTOMLEFT", 0, -14)
            summary:SetWidth(WIZARD_WIDTH - 48)
            summary:SetJustifyH("LEFT")
            summary:SetTextColor(0.65, 0.65, 0.65)
            wizardFrame.reviewSummary = summary
        end

        wizardFrame.pages = pages

        local function RefreshReviewSummary()
            local lines = {}
            local s = wizardFrame.selection
            if s.objectiveTracker then
                lines[#lines + 1] = "Objective Tracker"
            end
            if s.consumables then
                lines[#lines + 1] = "Consumables Reminder"
            end
            if s.housing then
                lines[#lines + 1] = "Housing"
            end
            if s.worldQuests then
                lines[#lines + 1] = "World Quests"
            end
            if s.classMonk then
                lines[#lines + 1] = "Classes: Monk"
            end
            if s.dungeonDifficulty then
                lines[#lines + 1] = "Reminder: Dungeon Difficulty"
            end
            if s.greatVault then
                lines[#lines + 1] = "Reminder: Great Vault"
            end
            if s.talentLoadout then
                lines[#lines + 1] = "Reminder: Talent Loadouts"
            end
            if s.menuBar then
                lines[#lines + 1] = "Micro Menu / Queue Eye"
            end
            if s.characterStats then
                lines[#lines + 1] = "Character Stats"
            end
            if s.shoppingList then
                lines[#lines + 1] = "Shopping List"
            end
            if s.cutscenes then
                lines[#lines + 1] = "Skip Seen Cutscenes"
            end

            if #lines == 0 then
                wizardFrame.reviewSummary:SetText("No modules selected. NomTools will stay minimal until you enable features in /nomtools.")
            else
                wizardFrame.reviewSummary:SetText("Enabled:\n- " .. table.concat(lines, "\n- "))
            end
        end

        local function UpdateNavForStep(stepIndex)
            backBtn:SetEnabled(stepIndex > 0)
            if stepIndex == 0 then
                nextBtn:Hide()
            else
                nextBtn:Show()
            end

            if stepIndex == 0 then
                progressText:SetText("")
            else
                progressText:SetText(string.format("Step %d of %d", stepIndex, totalSteps - 1))
            end

            if stepIndex == totalSteps - 1 then
                nextBtn:SetText("Save & Reload")
            else
                nextBtn:SetText("Next")
            end
        end

        function wizardFrame.SetStep(stepIndex)
            wizardFrame.currentStep = stepIndex
            for index = 0, #WIZARD_MODULES + 1 do
                local page = pages[index]
                if page then
                    page:SetShown(index == stepIndex)
                end
            end
            SyncAllCheckboxesFromSelection()
            UpdateNavForStep(stepIndex)
            if stepIndex == totalSteps - 1 then
                RefreshReviewSummary()
            end
        end

        backBtn:SetScript("OnClick", function()
            if not wizardFrame.currentStep or wizardFrame.currentStep <= 0 then
                return
            end
            wizardFrame.SetStep(wizardFrame.currentStep - 1)
        end)

        nextBtn:SetScript("OnClick", function()
            local step = wizardFrame.currentStep or 0
            if step >= totalSteps - 1 then
                PlayWizardForwardSound()
                wizardFrame.restoreSetupOnHide = false
                ApplySelectionsAndReload(wizardFrame.selection)
                return
            end
            PlayWizardForwardSound()
            wizardFrame.SetStep(step + 1)
        end)

        wizardFrame.SetStep(0)

        wizardFrame.restoreSetupOnHide = true
        wizardFrame:Show()
    end

    function ns.IsSetupComplete()
        return ns.db and ns.db.setupComplete == true
    end

    function ns.ShouldShowSetupWizard()
        if setupWizardShownThisSession then
            return false
        end
        return not ns.IsSetupComplete()
    end

    function ns.TryShowSetupWizard()
        if not ns.ShouldShowSetupWizard() then
            return false
        end
        ResetWizardSelections()
        setupWizardShownThisSession = true
        ns.ShowSetupWizard()
        return true
    end

    function ns.TryShowPostSetupSettingsPrompt()
        if settingsPromptShownThisSession or not ns.db or ns.db[POST_SETUP_SETTINGS_PROMPT_PENDING_KEY] ~= true then
            return false
        end

        if not ns.IsSetupComplete() then
            return false
        end

        settingsPromptShownThisSession = true
        settingsPromptPendingThisSession = true
        ns.db[POST_SETUP_SETTINGS_PROMPT_PENDING_KEY] = nil

        if C_Timer and C_Timer.After then
            C_Timer.After(0, ns.ShowPostSetupSettingsPrompt)
        else
            ns.ShowPostSetupSettingsPrompt()
        end

        return true
    end
end
