-- MSUF_EM2_HUD.lua — Edit Mode HUD (two-row, polished)
local addonName, ns = ...
local EM2 = _G.MSUF_EM2
if not EM2 then return end

local HUD = {}; EM2.HUD = HUD

local L     = (ns and ns.L) or _G.MSUF_L or setmetatable({}, { __index = function(_, k) return k end })
local FONT  = STANDARD_TEXT_FONT or "Fonts/FRIZQT__.TTF"
local W8    = "Interface/Buttons/WHITE8X8"
local MEDIA = "Interface\\AddOns\\" .. tostring(addonName or "MidnightSimpleUnitFrames") .. "\\Media\\"
local floor, max, min = math.floor, math.max, math.min

local hudFrame, row2Frame
local previewBtn, auraBtn, snapToggle, cdmBtn, anchorBtn
local previewAddonSlot
local undoBtn, redoBtn, cancelAllBtn, exitBtn
local alphaFS, stepFS
local helpBtn, tutorialPanel, tourState
local bgWidget, gridWidget
local HelpText

local R1_H    = 42
local R2_H    = 34
local BTN_H   = 32
local BTN_H2  = 26
local BTN_GAP = 5
local SEP_W   = 16

local TH = {
    r1Bg   = { 0.045, 0.05, 0.07, 0.95 },
    r2Bg   = { 0.035, 0.04, 0.06, 0.90 },
    edge   = { 0.20, 0.22, 0.28, 0.45 },
    titleR=0.50, titleG=0.53, titleB=0.60,
    textR=0.72, textG=0.74, textB=0.80,
    mutedR=0.52, mutedG=0.54, mutedB=0.60,
    onR=0.38, onG=0.65, onB=1.00,
    offR=0.40, offG=0.42, offB=0.50,
    exitR=0.90, exitG=0.32, exitB=0.32,
}

local function MakeFS(p, sz, r, g, b, a)
    local fs = p:CreateFontString(nil, "OVERLAY")
    fs:SetFont(FONT, sz or 12, ""); fs:SetShadowOffset(1, -1)
    fs:SetTextColor(r or 1, g or 1, b or 1, a or 1); return fs
end

local function SetActive(btn, on)
    if not btn or not btn._label then return end
    if on then
        btn._label:SetTextColor(TH.onR, TH.onG, TH.onB, 1)
        if btn._dot then btn._dot:Show() end
    else
        btn._label:SetTextColor(TH.offR, TH.offG, TH.offB, 0.85)
        if btn._dot then btn._dot:Hide() end
    end
end

local function SetTip(widget, text)
    if not widget or not text then return end
    widget:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_BOTTOM", 0, -6)
        GameTooltip:SetText(HelpText(text), 1, 1, 1, 1, true)
        GameTooltip:Show()
    end)
    widget:SetScript("OnLeave", function() GameTooltip:Hide() end)
end

local function MakeBtn(parent, text, w, h, fontSize, onClick)
    local btn = CreateFrame("Button", nil, parent)
    w = w or (#text * 8 + 18); h = h or BTN_H
    btn:SetSize(w, h)
    local hl = btn:CreateTexture(nil, "HIGHLIGHT")
    hl:SetAllPoints(); hl:SetColorTexture(1, 1, 1, 0.05)
    local label = MakeFS(btn, fontSize or 12, TH.textR, TH.textG, TH.textB, 0.92)
    label:SetPoint("CENTER"); label:SetText(HelpText(text))
    btn._label = label
    local dot = btn:CreateTexture(nil, "OVERLAY")
    dot:SetSize(w - 8, 2); dot:SetPoint("BOTTOM", btn, "BOTTOM", 0, 2)
    dot:SetColorTexture(TH.onR, TH.onG, TH.onB, 0.90); dot:Hide()
    btn._dot = dot
    if onClick then btn:SetScript("OnClick", onClick) end
    return btn
end

local function AttachHistoryIcon(btn, texturePath)
    if not btn then return end
    if btn._label then btn._label:Hide() end
    if btn._dot then btn._dot:Hide() end
    local icon = btn:CreateTexture(nil, "ARTWORK", nil, 5)
    icon:SetTexture(texturePath)
    icon:SetSize(17, 17)
    icon:SetPoint("CENTER", btn, "CENTER", 0, 0)
    btn._historyIcon = icon
    return icon
end

local function MakeSep(parent, h)
    local s = parent:CreateTexture(nil, "OVERLAY")
    s:SetSize(1, (h or BTN_H) - 8); s:SetColorTexture(0.35, 0.38, 0.45, 0.28)
    return s
end

local function LayoutCenter(anchor, items, gap, sepW)
    local totalW = 0
    for i, b in ipairs(items) do
        totalW = totalW + (b._isSep and sepW or b:GetWidth())
        if i < #items then totalW = totalW + gap end
    end
    local x = -totalW / 2
    for _, b in ipairs(items) do
        local w = b._isSep and sepW or b:GetWidth()
        b:SetPoint("LEFT", anchor, "CENTER", b._isSep and (x + w/2) or x, 0)
        x = x + w + gap
    end
end

local HELP_DEFAULTS = {
    EM_HELP_DRAG    = "Hold the left mouse button on a mover overlay and drag it. The live frame follows the mover, and the X/Y readout below the overlay shows the current screen offset. Release the button to commit the position.",
    EM_HELP_NUDGE   = "Arrow keys move the active target. Priority is: selected preview handle, open castbar popup, open aura popup, then the currently selected unit frame. Step size: 1 px, Shift = 5 px, Ctrl = 10 px, Alt = current grid step.",
    EM_HELP_POPUP   = "Left-click a mover without dragging to open its edit popup. Unit popups edit frame size, position, text anchors, detached power settings, and copy-to-unit options. Castbar and aura movers open their own focused popups.",
    EM_HELP_SNAP    = "The grid is visual guidance. Left-click |cff60a5ffGrid ##px|r to toggle grid lines on or off; scroll it to change spacing. |cff60a5ffSnap|r is separate: when enabled, dragged movers snap to the screen center or to edges/centers of other visible movers, with orange guide lines.",
    EM_HELP_OPACITY = "Scroll the |cff60a5ffBG ##%|r control to change only the Edit Mode background overlay opacity. It does not change unitframe alpha or saved frame visibility.",
    EM_HELP_PREVIEW = "|cff60a5ffPreview|r toggles placeholder unit data for missing target, focus, target-of-target, pet, boss, and castbar previews while Edit Mode is active. |cff60a5ffAuras|r toggles aura preview icons and aura mover boxes.",
    EM_HELP_UNDO    = "|cff60a5ffUndo|r and |cff60a5ffRedo|r restore recent Edit Mode changes captured before drags, nudges, and popup edits. |cff60a5ffCancel All|r asks for confirmation, restores the snapshot from before Edit Mode was entered, then exits.",
    EM_HELP_CDM     = "|cff60a5ffCDM|r toggles the global Essential Cooldown Manager anchor. |cff60a5ffAnchor|r opens the anchor picker; hover a named frame, hold Ctrl, and left-click to use it as the global anchor. Picking a custom anchor turns CDM off.",
    EM_HELP_COPYTO  = "In unit and castbar popups, |cff60a5ffCopy To|r copies the current popup's settings to another target. Unit frame copy keeps the target's position and anchor settings; castbar copy keeps target X/Y offsets.",
    EM_HELP_EXIT    = "|cff60a5ffExit|r leaves Edit Mode and keeps the current changes. Entering combat also exits Edit Mode. Popup Escape closes/cancels that popup; the HUD Exit button is the normal way to leave.",
    EM_HELP_TITLE   = "Edit Mode - Quick Reference",
    EM_TOUR_START   = "Start Guided Tour",
    EM_TOUR_NEXT    = "Next",
    EM_TOUR_BACK    = "Back",
    EM_TOUR_SKIP    = "Skip",
    EM_TOUR_DONE    = "Done",
    EM_TOUR_STEP    = "Step %d of %d",
    EM_HELP_BTN     = "? Help",
    EM_HELP_BTN_TIP = "Quick reference and guided tour\nfor Edit Mode controls.",
    ["Drag & Move"]        = "Drag & Move",
    ["Arrow Key Nudge"]    = "Arrow Key Nudge",
    ["Click Popup"]        = "Click Popup",
    ["Grid & Snap"]        = "Grid & Snap",
    ["Background Opacity"] = "Background Opacity",
    ["Preview & Auras"]    = "Preview & Auras",
    ["Undo / Cancel All"]  = "Undo / Cancel All",
    ["CDM & Anchor"]       = "CDM & Anchor",
    ["Copy Settings"]      = "Copy Settings",
    ["Exit Edit Mode"]     = "Exit Edit Mode",
}

function HelpText(key)
    if type(key) ~= "string" then return key end
    local value = type(L) == "table" and rawget(L, key) or nil
    if type(value) == "string" and value ~= "" and value ~= key then
        return value
    end
    return HELP_DEFAULTS[key] or key
end

-- Seed the current locale table for old callers, but all Help/Tour rendering
-- uses HelpText() so ns.SetLocale() rebuilds cannot expose raw keys again.
do
    for key, text in pairs(HELP_DEFAULTS) do
        local current = rawget(L, key)
        if current == nil or current == key then L[key] = text end
    end
end

-- Tutorial / Help Reference Panel (lazy init)
local HELP_SECTIONS = {
    { title = "Drag & Move",        body = "EM_HELP_DRAG" },
    { title = "Click Popup",        body = "EM_HELP_POPUP" },
    { title = "Arrow Key Nudge",    body = "EM_HELP_NUDGE" },
    { title = "Grid & Snap",        body = "EM_HELP_SNAP" },
    { title = "Background Opacity", body = "EM_HELP_OPACITY" },
    { title = "Preview & Auras",    body = "EM_HELP_PREVIEW" },
    { title = "CDM & Anchor",       body = "EM_HELP_CDM" },
    { title = "Copy Settings",      body = "EM_HELP_COPYTO" },
    { title = "Undo / Cancel All",  body = "EM_HELP_UNDO" },
    { title = "Exit Edit Mode",     body = "EM_HELP_EXIT" },
}

local PANEL_W     = 340
local PANEL_PAD   = 16
local SEC_GAP     = 6
local TITLE_SZ    = 12
local BODY_SZ     = 11
local BODY_W      = PANEL_W - PANEL_PAD * 2
local HEADER_H    = 36
local CLOSE_SZ    = 20

local function EnsureTutorialPanel()
    if tutorialPanel then return tutorialPanel end

    local p = CreateFrame("Frame", "MSUF_EM2_TutorialPanel", UIParent, "BackdropTemplate")
    p:SetFrameStrata("TOOLTIP"); p:SetFrameLevel(950)
    p:SetWidth(PANEL_W)
    p:SetPoint("CENTER", UIParent, "CENTER", 0, 40)
    p:SetBackdrop({ bgFile = W8, edgeFile = W8, edgeSize = 1,
                    insets = { left = 1, right = 1, top = 1, bottom = 1 } })
    p:SetBackdropColor(0.03, 0.05, 0.12, 0.97)
    p:SetBackdropBorderColor(0.10, 0.20, 0.45, 0.90)
    p:EnableMouse(true); p:Hide()

    p:EnableKeyboard(true)
    p:SetScript("OnKeyDown", function(self, k)
        if k == "ESCAPE" then
            self:SetPropagateKeyboardInput(false); self:Hide()
        else
            self:SetPropagateKeyboardInput(true)
        end
    end)

    local hdr = MakeFS(p, 13, 0.75, 0.88, 1.00, 1)
    hdr:SetPoint("TOPLEFT", p, "TOPLEFT", PANEL_PAD, -12)
    hdr:SetText(HelpText("EM_HELP_TITLE"))

    local closeBtn = CreateFrame("Button", nil, p)
    closeBtn:SetSize(CLOSE_SZ, CLOSE_SZ)
    closeBtn:SetPoint("TOPRIGHT", p, "TOPRIGHT", -8, -8)
    local closeFS = MakeFS(closeBtn, 14, 0.55, 0.62, 0.78, 0.70)
    closeFS:SetPoint("CENTER"); closeFS:SetText("x")
    closeBtn:SetScript("OnEnter", function() closeFS:SetTextColor(1, 1, 1, 1) end)
    closeBtn:SetScript("OnLeave", function() closeFS:SetTextColor(0.55, 0.62, 0.78, 0.70) end)
    closeBtn:SetScript("OnClick", function() p:Hide() end)

    local y = -(HEADER_H)

    for i, sec in ipairs(HELP_SECTIONS) do
        if i > 1 then
            local div = p:CreateTexture(nil, "ARTWORK")
            div:SetSize(BODY_W, 1)
            div:SetPoint("TOPLEFT", p, "TOPLEFT", PANEL_PAD, y - SEC_GAP * 0.5)
            div:SetColorTexture(0.10, 0.20, 0.45, 0.25)
            y = y - SEC_GAP
        end

        local tFS = MakeFS(p, TITLE_SZ, 1.00, 0.82, 0.00, 1.00)
        tFS:SetPoint("TOPLEFT", p, "TOPLEFT", PANEL_PAD, y)
        tFS:SetText(HelpText(sec.title))

        y = y - (TITLE_SZ + 4)

        local bFS = MakeFS(p, BODY_SZ, 0.78, 0.82, 0.90, 0.90)
        bFS:SetPoint("TOPLEFT", p, "TOPLEFT", PANEL_PAD, y)
        bFS:SetWidth(BODY_W); bFS:SetWordWrap(true); bFS:SetJustifyH("LEFT")
        bFS:SetText(HelpText(sec.body))

        local bH = bFS:GetStringHeight() or 14
        y = y - bH - SEC_GAP
    end

    y = y - 4
    local tourBtn = CreateFrame("Button", nil, p, "BackdropTemplate")
    tourBtn:SetSize(BODY_W, 28)
    tourBtn:SetPoint("TOPLEFT", p, "TOPLEFT", PANEL_PAD, y)
    tourBtn:SetBackdrop({ bgFile = W8, edgeFile = W8, edgeSize = 1,
                          insets = { left = 1, right = 1, top = 1, bottom = 1 } })
    tourBtn:SetBackdropColor(TH.onR * 0.25, TH.onG * 0.25, TH.onB * 0.25, 0.90)
    tourBtn:SetBackdropBorderColor(TH.onR, TH.onG, TH.onB, 0.50)
    local tbHL = tourBtn:CreateTexture(nil, "HIGHLIGHT")
    tbHL:SetAllPoints(); tbHL:SetColorTexture(TH.onR, TH.onG, TH.onB, 0.10)
    local tbFS = MakeFS(tourBtn, 12, TH.onR, TH.onG, TH.onB, 1)
    tbFS:SetPoint("CENTER"); tbFS:SetText(HelpText("EM_TOUR_START"))
    tourBtn:SetScript("OnClick", function()
        p:Hide()
        HUD.StartTour()
    end)

    y = y - 28 - 4
    p:SetHeight(-y + PANEL_PAD * 0.5)

    tutorialPanel = p
    return p
end

-- Phase 2: Guided Tour — spotlight mask + step cards
local MASK_ALPHA = 0.65
local CARD_W     = 300
local CARD_PAD   = 14
local SPOT_PAD   = 6

local function GetTourSteps()
    return {
        {
            title  = HelpText("Drag & Move"),
            body   = HelpText("EM_HELP_DRAG"),
            anchor = "CENTER",
        },
        {
            target = function() return previewBtn end,
            title  = HelpText("Preview & Auras"),
            body   = HelpText("EM_HELP_PREVIEW"),
            anchor = "BOTTOM",
        },
        {
            title  = HelpText("Click Popup"),
            body   = HelpText("EM_HELP_POPUP"),
            anchor = "CENTER",
        },
        {
            title  = HelpText("Arrow Key Nudge"),
            body   = HelpText("EM_HELP_NUDGE"),
            anchor = "CENTER",
        },
        {
            target = function() return snapToggle end,
            title  = HelpText("Grid & Snap"),
            body   = HelpText("EM_HELP_SNAP"),
            anchor = "BOTTOM",
        },
        {
            target = function() return bgWidget end,
            title  = HelpText("Background Opacity"),
            body   = HelpText("EM_HELP_OPACITY"),
            anchor = "BOTTOM",
        },
        {
            target = function() return cdmBtn end,
            title  = HelpText("CDM & Anchor"),
            body   = HelpText("EM_HELP_CDM"),
            anchor = "BOTTOM",
        },
        {
            title  = HelpText("Copy Settings"),
            body   = HelpText("EM_HELP_COPYTO"),
            anchor = "CENTER",
        },
        {
            target = function() return undoBtn end,
            title  = HelpText("Undo / Cancel All"),
            body   = HelpText("EM_HELP_UNDO"),
            anchor = "BOTTOM",
        },
        {
            target = function() return exitBtn end,
            title  = HelpText("Exit Edit Mode"),
            body   = HelpText("EM_HELP_EXIT"),
            anchor = "BOTTOM",
        },
    }
end

local function EnsureTourFrames()
    if tourState then return tourState end

    local ts = {}
    tourState = ts
    ts.step = 0

    ts.masks = {}
    for i = 1, 4 do
        local m = CreateFrame("Frame", nil, UIParent)
        m:SetFrameStrata("TOOLTIP"); m:SetFrameLevel(940)
        local tex = m:CreateTexture(nil, "BACKGROUND")
        tex:SetAllPoints(); tex:SetColorTexture(0, 0, 0, MASK_ALPHA)
        m:EnableMouse(true); m:Hide()
        ts.masks[i] = m
    end

    ts.ring = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
    ts.ring:SetFrameStrata("TOOLTIP"); ts.ring:SetFrameLevel(941)
    ts.ring:SetBackdrop({ edgeFile = W8, edgeSize = 2 })
    ts.ring:SetBackdropBorderColor(1.00, 0.82, 0.00, 0.85)
    ts.ring:Hide()

    ts.card = CreateFrame("Frame", "MSUF_EM2_TourCard", UIParent, "BackdropTemplate")
    ts.card:SetFrameStrata("TOOLTIP"); ts.card:SetFrameLevel(945)
    ts.card:SetWidth(CARD_W)
    ts.card:SetBackdrop({ bgFile = W8, edgeFile = W8, edgeSize = 1,
                          insets = { left = 1, right = 1, top = 1, bottom = 1 } })
    ts.card:SetBackdropColor(0.03, 0.05, 0.12, 0.97)
    ts.card:SetBackdropBorderColor(1.00, 0.82, 0.00, 0.70)
    ts.card:EnableMouse(true); ts.card:Hide()

    ts.card:EnableKeyboard(true)
    ts.card:SetScript("OnKeyDown", function(self, k)
        if k == "ESCAPE" then
            self:SetPropagateKeyboardInput(false); HUD.StopTour()
        else
            self:SetPropagateKeyboardInput(true)
        end
    end)

    ts.stepFS = MakeFS(ts.card, 10, TH.mutedR, TH.mutedG, TH.mutedB, 0.70)
    ts.stepFS:SetPoint("TOPRIGHT", ts.card, "TOPRIGHT", -CARD_PAD, -10)

    ts.titleFS = MakeFS(ts.card, 13, 1.00, 0.82, 0.00, 1.00)
    ts.titleFS:SetPoint("TOPLEFT", ts.card, "TOPLEFT", CARD_PAD, -10)

    ts.bodyFS = MakeFS(ts.card, 11, 0.78, 0.82, 0.90, 0.90)
    ts.bodyFS:SetPoint("TOPLEFT", ts.card, "TOPLEFT", CARD_PAD, -28)
    ts.bodyFS:SetWidth(CARD_W - CARD_PAD * 2)
    ts.bodyFS:SetWordWrap(true); ts.bodyFS:SetJustifyH("LEFT")

    local NAV_H = 26
    local NAV_W = 68

    local function NavBtn(text)
        local b = CreateFrame("Button", nil, ts.card, "BackdropTemplate")
        b:SetSize(NAV_W, NAV_H)
        b:SetBackdrop({ bgFile = W8, edgeFile = W8, edgeSize = 1,
                        insets = { left = 1, right = 1, top = 1, bottom = 1 } })
        b:SetBackdropColor(0.09, 0.10, 0.14, 0.90)
        b:SetBackdropBorderColor(0.10, 0.20, 0.42, 0.65)
        local hl = b:CreateTexture(nil, "HIGHLIGHT"); hl:SetAllPoints(); hl:SetColorTexture(1, 1, 1, 0.06)
        b._fs = MakeFS(b, 11, TH.textR, TH.textG, TH.textB, 1)
        b._fs:SetPoint("CENTER"); b._fs:SetText(text)
        return b
    end

    ts.skipBtn = NavBtn(HelpText("EM_TOUR_SKIP"))
    ts.skipBtn:SetScript("OnClick", function() HUD.StopTour() end)

    ts.backBtn = NavBtn(HelpText("EM_TOUR_BACK"))
    ts.backBtn:SetScript("OnClick", function() HUD.TourStep(ts.step - 1) end)

    ts.nextBtn = NavBtn(HelpText("EM_TOUR_NEXT"))
    ts.nextBtn:SetScript("OnClick", function()
        local steps = GetTourSteps()
        if ts.step >= #steps then
            HUD.StopTour()
        else
            HUD.TourStep(ts.step + 1)
        end
    end)

    return ts
end

local function PositionMask(ts, tgt)
    local uiW, uiH = UIParent:GetWidth(), UIParent:GetHeight()
    local m1, m2, m3, m4 = ts.masks[1], ts.masks[2], ts.masks[3], ts.masks[4]

    if not tgt then
        m1:ClearAllPoints(); m1:SetAllPoints(UIParent); m1:Show()
        m2:Hide(); m3:Hide(); m4:Hide()
        ts.ring:Hide()
        return
    end

    local sl = tgt:GetLeft() or 0
    local sr = tgt:GetRight() or 0
    local st = tgt:GetTop() or 0
    local sb = tgt:GetBottom() or 0
    local ratio = tgt:GetEffectiveScale() / UIParent:GetEffectiveScale()
    sl = sl * ratio - SPOT_PAD
    sr = sr * ratio + SPOT_PAD
    st = st * ratio + SPOT_PAD
    sb = sb * ratio - SPOT_PAD

    m1:ClearAllPoints()
    m1:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 0, 0)
    m1:SetPoint("BOTTOMRIGHT", UIParent, "TOPLEFT", uiW, -(uiH - st))
    m1:Show()

    m2:ClearAllPoints()
    m2:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 0, -(uiH - sb))
    m2:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", 0, 0)
    m2:Show()

    m3:ClearAllPoints()
    m3:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 0, -(uiH - st))
    m3:SetPoint("BOTTOMRIGHT", UIParent, "TOPLEFT", sl, -(uiH - sb))
    m3:Show()

    m4:ClearAllPoints()
    m4:SetPoint("TOPLEFT", UIParent, "TOPLEFT", sr, -(uiH - st))
    m4:SetPoint("BOTTOMRIGHT", UIParent, "TOPLEFT", uiW, -(uiH - sb))
    m4:Show()

    ts.ring:ClearAllPoints()
    ts.ring:SetPoint("TOPLEFT", UIParent, "TOPLEFT", sl, -(uiH - st))
    ts.ring:SetPoint("BOTTOMRIGHT", UIParent, "TOPLEFT", sr, -(uiH - sb))
    ts.ring:Show()
end

local function PositionCard(ts, tgt, anchorHint)
    local card = ts.card
    card:ClearAllPoints()

    if not tgt or anchorHint == "CENTER" then
        card:SetPoint("CENTER", UIParent, "CENTER", 0, -20)
        return
    end

    local ratio = tgt:GetEffectiveScale() / UIParent:GetEffectiveScale()
    local uiH = UIParent:GetHeight()
    local t = (tgt:GetTop() or 0) * ratio
    local b = (tgt:GetBottom() or 0) * ratio
    local cx = ((tgt:GetLeft() or 0) + (tgt:GetRight() or 0)) * 0.5 * ratio

    if (uiH - t) < uiH * 0.3 then
        card:SetPoint("TOP", UIParent, "TOPLEFT", cx, -(uiH - b) - 14)
    else
        card:SetPoint("BOTTOM", UIParent, "TOPLEFT", cx, (uiH - t) + 14)
    end
end

function HUD.TourStep(idx)
    local ts = EnsureTourFrames()
    local steps = GetTourSteps()
    if idx < 1 then idx = 1 end
    if idx > #steps then idx = #steps end
    ts.step = idx

    local s = steps[idx]
    local tgt = s.target and s.target() or nil

    PositionMask(ts, tgt)
    PositionCard(ts, tgt, s.anchor)

    ts.titleFS:SetText(s.title)
    ts.bodyFS:SetText(s.body)
    ts.stepFS:SetText(HelpText("EM_TOUR_STEP"):format(idx, #steps))

    local bH = ts.bodyFS:GetStringHeight() or 14
    ts.card:SetHeight(28 + bH + 12 + 26 + 12)

    ts.skipBtn:ClearAllPoints()
    ts.skipBtn:SetPoint("BOTTOMLEFT", ts.card, "BOTTOMLEFT", CARD_PAD, 10)

    ts.backBtn:ClearAllPoints()
    ts.nextBtn:ClearAllPoints()
    ts.nextBtn:SetPoint("BOTTOMRIGHT", ts.card, "BOTTOMRIGHT", -CARD_PAD, 10)
    ts.backBtn:SetPoint("RIGHT", ts.nextBtn, "LEFT", -6, 0)

    if idx <= 1 then ts.backBtn:Hide() else ts.backBtn:Show() end

    local isLast = idx >= #steps
    ts.nextBtn._fs:SetText(isLast and HelpText("EM_TOUR_DONE") or HelpText("EM_TOUR_NEXT"))
    if isLast then
        ts.nextBtn._fs:SetTextColor(TH.onR, TH.onG, TH.onB, 1)
    else
        ts.nextBtn._fs:SetTextColor(TH.textR, TH.textG, TH.textB, 1)
    end

    ts.card:Show()
end

function HUD.StartTour()
    local ts = EnsureTourFrames()
    for i = 1, 4 do ts.masks[i]:Show() end
    HUD.TourStep(1)
end

function HUD.StopTour()
    if not tourState then return end
    for i = 1, 4 do tourState.masks[i]:Hide() end
    tourState.ring:Hide()
    tourState.card:Hide()
    tourState.step = 0
end

local function EnsureHUD()
    if hudFrame then return end

    -- ── ROW 1 ──
    hudFrame = CreateFrame("Frame", "MSUF_EM2_HUD", UIParent, "BackdropTemplate")
    hudFrame:SetFrameStrata("FULLSCREEN"); hudFrame:SetFrameLevel(100)
    hudFrame:SetHeight(R1_H)
    hudFrame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 0, 0)
    hudFrame:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", 0, 0)
    hudFrame:SetBackdrop({ bgFile=W8, edgeFile=W8, edgeSize=1, insets={left=0,right=0,top=0,bottom=0} })
    hudFrame:SetBackdropColor(unpack(TH.r1Bg))
    hudFrame:SetBackdropBorderColor(unpack(TH.edge))
    hudFrame:EnableMouse(true); hudFrame:Hide()

    local title = MakeFS(hudFrame, 11, TH.titleR, TH.titleG, TH.titleB, 0.50)
    title:SetPoint("LEFT", hudFrame, "LEFT", 14, 0)
    title:SetText(HelpText("EDIT MODE"))

    -- ── Prominent HELP button ──
    helpBtn = CreateFrame("Button", nil, hudFrame, "BackdropTemplate")
    helpBtn:SetSize(72, 26)
    helpBtn:SetPoint("LEFT", title, "RIGHT", 10, 0)
    helpBtn:SetBackdrop({ bgFile = W8, edgeFile = W8, edgeSize = 1,
                          insets = { left = 1, right = 1, top = 1, bottom = 1 } })
    helpBtn:SetBackdropColor(TH.onR * 0.20, TH.onG * 0.20, TH.onB * 0.20, 0.85)
    helpBtn:SetBackdropBorderColor(TH.onR, TH.onG, TH.onB, 0.60)
    do
        local glow = helpBtn:CreateTexture(nil, "BACKGROUND", nil, -1)
        glow:SetPoint("TOPLEFT", -3, 3); glow:SetPoint("BOTTOMRIGHT", 3, -3)
        glow:SetColorTexture(TH.onR, TH.onG, TH.onB, 0.08)
        helpBtn._glow = glow

        local hl = helpBtn:CreateTexture(nil, "HIGHLIGHT")
        hl:SetAllPoints(); hl:SetColorTexture(TH.onR, TH.onG, TH.onB, 0.12)

        local lbl = MakeFS(helpBtn, 12, TH.onR, TH.onG, TH.onB, 1)
        lbl:SetPoint("CENTER", 0, 0); lbl:SetText(HelpText("EM_HELP_BTN"))
        helpBtn._label = lbl

        local pulse = helpBtn:CreateAnimationGroup()
        local fadeOut = pulse:CreateAnimation("Alpha")
        fadeOut:SetFromAlpha(1); fadeOut:SetToAlpha(0.45)
        fadeOut:SetDuration(0.8); fadeOut:SetOrder(1); fadeOut:SetSmoothing("IN_OUT")
        local fadeIn = pulse:CreateAnimation("Alpha")
        fadeIn:SetFromAlpha(0.45); fadeIn:SetToAlpha(1)
        fadeIn:SetDuration(0.8); fadeIn:SetOrder(2); fadeIn:SetSmoothing("IN_OUT")
        pulse:SetLooping("REPEAT")
        helpBtn._pulse = pulse
    end
    helpBtn:SetScript("OnClick", function()
        local panel = EnsureTutorialPanel()
        if panel:IsShown() then panel:Hide() else panel:Show() end
    end)
    helpBtn:SetScript("OnEnter", function(self)
        if self._pulse then self._pulse:Stop() end
        self:SetAlpha(1)
        GameTooltip:SetOwner(self, "ANCHOR_BOTTOM", 0, -6)
        GameTooltip:SetText(HelpText("EM_HELP_BTN_TIP"), 1, 1, 1, 1, true)
        GameTooltip:Show()
    end)
    helpBtn:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)

    -- Right-side: Cancel All | Exit
    exitBtn = MakeBtn(hudFrame, "Exit", 48, BTN_H, 12, function()
        if EM2.State then EM2.State.Exit("hud_exit") end
    end)
    exitBtn:SetPoint("RIGHT", hudFrame, "RIGHT", -12, 0)
    exitBtn._label:SetTextColor(TH.exitR, TH.exitG, TH.exitB, 1)
    exitBtn._dot:Hide()
    SetTip(exitBtn, "Lock positions and exit Edit Mode.")

    local rSep = MakeSep(hudFrame, BTN_H)
    rSep:SetPoint("RIGHT", exitBtn, "LEFT", -BTN_GAP, 0)

    cancelAllBtn = MakeBtn(hudFrame, "Cancel All", 78, BTN_H, 12, function()
        if not EM2.State or not EM2.State.CancelAll then return end
        local cf = _G["MSUF_EM2_CancelConfirm"]
        if cf then cf:Show(); return end
        cf = CreateFrame("Frame", "MSUF_EM2_CancelConfirm", UIParent, "BackdropTemplate")
        cf:SetSize(280, 100)
        cf:SetPoint("CENTER", UIParent, "CENTER", 0, 80)
        cf:SetFrameStrata("TOOLTIP"); cf:SetFrameLevel(999)
        cf:SetBackdrop({ bgFile=W8, edgeFile=W8, edgeSize=1, insets={left=1,right=1,top=1,bottom=1} })
        cf:SetBackdropColor(0.03, 0.05, 0.12, 0.97)
        cf:SetBackdropBorderColor(0.90, 0.70, 0.30, 0.80)
        cf:EnableMouse(true)
        local msg = MakeFS(cf, 13, TH.textR, TH.textG, TH.textB, 1)
        msg:SetPoint("TOP", cf, "TOP", 0, -18)
        msg:SetText(HelpText("Discard all changes and exit?"))
        local function ConfBtn(text, xOff, onClick)
            local b = CreateFrame("Button", nil, cf)
            b:SetSize(90, 28)
            b:SetPoint("BOTTOM", cf, "BOTTOM", xOff, 14)
            local bg = b:CreateTexture(nil, "BACKGROUND"); bg:SetAllPoints(); bg:SetColorTexture(0.09, 0.10, 0.14, 0.90)
            local brd = CreateFrame("Frame", nil, b, "BackdropTemplate"); brd:SetAllPoints()
            brd:SetFrameLevel(max(0, b:GetFrameLevel()-1))
            brd:SetBackdrop({edgeFile=W8, edgeSize=1}); brd:SetBackdropBorderColor(0.10, 0.20, 0.42, 0.65)
            local hl = b:CreateTexture(nil, "HIGHLIGHT"); hl:SetAllPoints(); hl:SetColorTexture(1,1,1,0.06)
            local fs = MakeFS(b, 12, TH.textR, TH.textG, TH.textB, 1); fs:SetPoint("CENTER"); fs:SetText(HelpText(text))
            b:SetScript("OnClick", onClick); return b
        end
        ConfBtn("Yes, discard", -54, function() cf:Hide(); EM2.State.CancelAll() end)
        ConfBtn("No, keep", 54, function() cf:Hide() end)
        cf:EnableKeyboard(true)
        cf:SetScript("OnKeyDown", function(s, k)
            if k == "ESCAPE" then s:SetPropagateKeyboardInput(false); cf:Hide()
            else s:SetPropagateKeyboardInput(true) end
        end)
        cf:Show()
    end)
    cancelAllBtn:SetPoint("RIGHT", rSep, "LEFT", -BTN_GAP, 0)
    cancelAllBtn._label:SetTextColor(0.90, 0.70, 0.30, 0.90)
    cancelAllBtn._dot:Hide()
    SetTip(cancelAllBtn, "Discard ALL changes made in Edit Mode\nand restore settings to the state\nbefore Edit Mode was opened.")

    -- Center toggles
    local c1 = CreateFrame("Frame", nil, hudFrame)
    c1:SetSize(1, BTN_H); c1:SetPoint("CENTER", hudFrame, "CENTER", 0, 0)
    local r1 = {}

    previewBtn = MakeBtn(c1, "Preview", 64, BTN_H, 12, function()
        _G.MSUF_UnitPreviewActive = not (_G.MSUF_UnitPreviewActive and true or false)
        if _G.MSUF_SyncAllUnitPreviews then _G.MSUF_SyncAllUnitPreviews() end
        SetActive(previewBtn, _G.MSUF_UnitPreviewActive)
    end)
    SetTip(previewBtn, "Show placeholder data on unitframes\nwithout real units (target, focus, etc.)")
    r1[#r1+1] = previewBtn

    previewAddonSlot = CreateFrame("Frame", "MSUF_EM2_HUD_PreviewAddonSlot", c1)
    previewAddonSlot:SetSize(38, BTN_H)
    r1[#r1+1] = previewAddonSlot

    auraBtn = MakeBtn(c1, "Auras", 52, BTN_H, 12, function()
        local db = _G.MSUF_DB; if not db then return end
        local a2 = db.auras2; if not a2 then return end
        local sh = a2.shared; if not sh then return end
        sh.showInEditMode = not (sh.showInEditMode and true or false)
        SetActive(auraBtn, sh.showInEditMode)
        if sh.showInEditMode then
            if _G.MSUF_A2_ShowAllEditMovers then _G.MSUF_A2_ShowAllEditMovers() end
        else
            if _G.MSUF_A2_HideAllEditMovers then _G.MSUF_A2_HideAllEditMovers() end
        end
        if _G.MSUF_Auras2_RefreshAll then _G.MSUF_Auras2_RefreshAll() end
    end)
    SetTip(auraBtn, "Toggle aura preview icons\nand aura mover boxes.")
    r1[#r1+1] = auraBtn

    snapToggle = MakeBtn(c1, "Snap", 48, BTN_H, 12, function()
        if EM2.Snap then
            local on = not EM2.Snap.IsEnabled()
            EM2.Snap.SetEnabled(on); SetActive(snapToggle, on)
        end
    end)
    SetTip(snapToggle, "Snap frames to edges of\nother frames while dragging.")
    r1[#r1+1] = snapToggle

    do local s = MakeSep(c1, BTN_H); s._isSep = true; r1[#r1+1] = s end

    cdmBtn = MakeBtn(c1, "CDM", 46, BTN_H, 12, function()
        local db = _G.MSUF_DB; if not db then return end
        db.general = db.general or {}
        db.general.anchorToCooldown = not (db.general.anchorToCooldown and true or false)
        SetActive(cdmBtn, db.general.anchorToCooldown)
        if type(ApplyAllSettings) == "function" then ApplyAllSettings() end
        C_Timer.After(0.1, function()
            if EM2.Movers and EM2.Movers.SyncAll then EM2.Movers.SyncAll() end
            if _G.MSUF_EM2_ReforcePreviewFrames then _G.MSUF_EM2_ReforcePreviewFrames() end
        end)
    end)
    SetTip(cdmBtn, "Anchor all unitframes to the\nEssential Cooldown Manager.")
    r1[#r1+1] = cdmBtn

    anchorBtn = MakeBtn(c1, "Anchor", 58, BTN_H, 12, function()
        local ov = type(_G.MSUF_EnsureAnchorPicker) == "function" and _G.MSUF_EnsureAnchorPicker()
        if not ov then return end
        ov._onPick = function(frameName)
            local db = _G.MSUF_DB; if not db then return end
            db.general = db.general or {}
            db.general.anchorName = frameName
            db.general.anchorToCooldown = false
            SetActive(cdmBtn, false)
            if type(ApplyAllSettings) == "function" then ApplyAllSettings() end
            C_Timer.After(0.1, function()
                if EM2.Movers and EM2.Movers.SyncAll then EM2.Movers.SyncAll() end
            end)
        end
        ov:Show()
    end)
    SetTip(anchorBtn, "Pick any frame as global anchor\nfor all unitframes.\nOverrides CDM anchor.")
    r1[#r1+1] = anchorBtn

    LayoutCenter(c1, r1, BTN_GAP, SEP_W)

    -- ── ROW 2 ──
    row2Frame = CreateFrame("Frame", "MSUF_EM2_HUD_Row2", hudFrame, "BackdropTemplate")
    row2Frame:SetHeight(R2_H)
    row2Frame:SetPoint("TOPLEFT", hudFrame, "BOTTOMLEFT", 0, 0)
    row2Frame:SetPoint("TOPRIGHT", hudFrame, "BOTTOMRIGHT", 0, 0)
    row2Frame:SetBackdrop({ bgFile=W8, edgeFile=W8, edgeSize=1, insets={left=0,right=0,top=0,bottom=0} })
    row2Frame:SetBackdropColor(unpack(TH.r2Bg))
    row2Frame:SetBackdropBorderColor(unpack(TH.edge))
    row2Frame:EnableMouse(true)

    local c2 = CreateFrame("Frame", nil, row2Frame)
    c2:SetSize(1, BTN_H2); c2:SetPoint("CENTER", row2Frame, "CENTER", 0, 0)
    local r2 = {}

    undoBtn = MakeBtn(c2, "", 42, BTN_H2, 11, function()
        if _G.MSUF_EM_UndoUndo then _G.MSUF_EM_UndoUndo() end
        HUD.RefreshControls()
    end)
    _G.MSUF_EditModeUndoBtn = undoBtn
    AttachHistoryIcon(undoBtn, MEDIA .. "msuf_history_undo_red.png")
    SetTip(undoBtn, "Undo last position change.")
    r2[#r2+1] = undoBtn

    redoBtn = MakeBtn(c2, "", 42, BTN_H2, 11, function()
        if _G.MSUF_EM_UndoRedo then _G.MSUF_EM_UndoRedo() end
        HUD.RefreshControls()
    end)
    _G.MSUF_EditModeRedoBtn = redoBtn
    AttachHistoryIcon(redoBtn, MEDIA .. "msuf_history_redo_green.png")
    SetTip(redoBtn, "Redo last undone change.")
    r2[#r2+1] = redoBtn

    do local s = MakeSep(c2, BTN_H2); s._isSep = true; r2[#r2+1] = s end

    do
        local f = CreateFrame("Frame", nil, c2)
        f:SetSize(80, BTN_H2); f:EnableMouse(true); f:EnableMouseWheel(true)
        gridWidget = f
        local stateBg = f:CreateTexture(nil, "BACKGROUND")
        stateBg:SetAllPoints()
        stateBg:SetColorTexture(0, 0, 0, 0)
        f._stateBg = stateBg
        stepFS = MakeFS(f, 11, TH.mutedR, TH.mutedG, TH.mutedB, 0.80)
        stepFS:SetPoint("CENTER")
        local hl = f:CreateTexture(nil, "HIGHLIGHT"); hl:SetAllPoints(); hl:SetColorTexture(1,1,1,0.04)
        f:SetScript("OnMouseUp", function(_, button)
            if button ~= "LeftButton" or not EM2.Grid or not EM2.Grid.ToggleEnabled then return end
            EM2.Grid.ToggleEnabled()
            HUD.RefreshControls()
        end)
        f:SetScript("OnMouseWheel", function(_, d)
            if not EM2.Grid then return end
            EM2.Grid.SetGridStep(max(4, min(80, EM2.Grid.GetGridStep() + d * 4)))
            HUD.RefreshControls()
        end)
        SetTip(f, "Left-click to toggle grid lines.\nScroll to adjust spacing.")
        r2[#r2+1] = f
    end

    do
        local f = CreateFrame("Frame", nil, c2)
        f:SetSize(74, BTN_H2); f:EnableMouseWheel(true)
        bgWidget = f
        alphaFS = MakeFS(f, 11, TH.mutedR, TH.mutedG, TH.mutedB, 0.80)
        alphaFS:SetPoint("CENTER")
        local hl = f:CreateTexture(nil, "HIGHLIGHT"); hl:SetAllPoints(); hl:SetColorTexture(1,1,1,0.04)
        f:SetScript("OnMouseWheel", function(_, d)
            if not EM2.Grid then return end
            EM2.Grid.SetBgAlpha(max(0, min(1, EM2.Grid.GetBgAlpha() + d * 0.05)))
            HUD.RefreshControls()
        end)
        SetTip(f, "Background overlay opacity.\nScroll to adjust.")
        r2[#r2+1] = f
    end

    LayoutCenter(c2, r2, BTN_GAP, SEP_W)
end

function HUD.RefreshUnitSelector() end

function HUD.RefreshControls()
    if alphaFS and EM2.Grid then alphaFS:SetText(HelpText("BG") .. " " .. floor(EM2.Grid.GetBgAlpha() * 100 + 0.5) .. "%") end
    if stepFS and EM2.Grid then
        local enabled = not EM2.Grid.GetEnabled or EM2.Grid.GetEnabled()
        stepFS:SetText(HelpText("Grid") .. " " .. floor(EM2.Grid.GetGridStep()) .. "px")
        if enabled then
            stepFS:SetTextColor(0.45, 0.95, 0.55, 0.95)
            if gridWidget and gridWidget._stateBg then gridWidget._stateBg:SetColorTexture(0.05, 0.28, 0.10, 0.18) end
        else
            stepFS:SetTextColor(0.95, 0.38, 0.38, 0.95)
            if gridWidget and gridWidget._stateBg then gridWidget._stateBg:SetColorTexture(0.34, 0.06, 0.06, 0.20) end
        end
    end
    if snapToggle and EM2.Snap then SetActive(snapToggle, EM2.Snap.IsEnabled()) end
    if previewBtn then SetActive(previewBtn, _G.MSUF_UnitPreviewActive and true or false) end
    if cdmBtn then
        local db = _G.MSUF_DB
        SetActive(cdmBtn, db and db.general and db.general.anchorToCooldown and true or false)
    end
    if auraBtn then
        local db = _G.MSUF_DB; local a2 = db and db.auras2; local sh = a2 and a2.shared
        SetActive(auraBtn, sh and sh.showInEditMode and true or false)
    end
    local canUndo = EM2.Undo and EM2.Undo.CanUndo() or false
    local canRedo = EM2.Undo and EM2.Undo.CanRedo() or false
    if undoBtn and undoBtn._historyIcon then
        undoBtn._historyIcon:SetAlpha(canUndo and 1 or 0.35)
    end
    if redoBtn and redoBtn._historyIcon then
        redoBtn._historyIcon:SetAlpha(canRedo and 1 or 0.35)
    end
end

function HUD.Show()
    EnsureHUD(); HUD.RefreshControls()
    hudFrame:Show(); if row2Frame then row2Frame:Show() end
    if helpBtn and helpBtn._pulse then helpBtn._pulse:Play() end

    local db = _G.MSUF_DB
    if db then
        db.general = db.general or {}
        if not db.general.emTutorialSeen then
            db.general.emTutorialSeen = true
            C_Timer.After(0.3, function()
                if HUD.IsShown() then
                    local panel = EnsureTutorialPanel()
                    if panel and not panel:IsShown() then panel:Show() end
                end
            end)
        end
    end
end

function HUD.Hide()
    HUD.StopTour()
    if tutorialPanel then tutorialPanel:Hide() end
    local cf = _G["MSUF_EM2_CancelConfirm"]; if cf then cf:Hide() end
    if helpBtn and helpBtn._pulse then helpBtn._pulse:Stop() end
    if row2Frame then row2Frame:Hide() end; if hudFrame then hudFrame:Hide() end
end

function HUD.IsShown() return hudFrame and hudFrame:IsShown() or false end
