local API = rawget(_G, "LIDCOUNCIL_API")
local INTERNAL_API = type(API) == "table" and type(API.__internal) == "table" and API.__internal or nil
if type(INTERNAL_API) ~= "table" then
  error("LidCouncil missing internal API registry.")
end

local RequireApi = INTERNAL_API.LIDCOUNCIL_RequireApi_Impl
local RegisterTutorialTexture = INTERNAL_API.LIDCOUNCIL_RegisterTutorialTexture_Impl
local BindMouseWheelScroll = INTERNAL_API.LIDCOUNCIL_BindMouseWheelScroll_Impl
if type(RequireApi) ~= "function"
  or type(RegisterTutorialTexture) ~= "function"
  or type(BindMouseWheelScroll) ~= "function" then
  error("LidCouncil missing shared editor page helpers.")
end

local ApplyFrameSkin = RequireApi("LIDCOUNCIL_ApplyFrameSkin", _G.LIDCOUNCIL_ApplyFrameSkin)
local CARD_W = RequireApi("LIDCOUNCIL_CARD_W", _G.LIDCOUNCIL_CARD_W)
local CARD_H = RequireApi("LIDCOUNCIL_CARD_H", _G.LIDCOUNCIL_CARD_H)
local GetEditorThemePalette = type(INTERNAL_API.LIDCOUNCIL_GetEditorThemePalette_Impl) == "function"
  and INTERNAL_API.LIDCOUNCIL_GetEditorThemePalette_Impl
  or nil

local function GetDeveloperAccentHex()
  local palette = GetEditorThemePalette and GetEditorThemePalette() or nil
  local color = type(palette) == "table" and type(palette.aboutTitle) == "table"
    and palette.aboutTitle
    or { 0.6588, 0.4784, 0.9098, 1.0 }
  local function ToHex(component)
    local value = tonumber(component) or 1
    value = math.max(0, math.min(255, math.floor((value * 255) + 0.5)))
    return string.format("%02x", value)
  end
  return ToHex(color[1]) .. ToHex(color[2]) .. ToHex(color[3])
end

local function ApplyDeveloperAccentMarkup(text)
  return tostring(text or ""):gsub("|cffa87ae8", "|cff" .. GetDeveloperAccentHex())
end

local function RefreshDeveloperAccentMarkup(root)
  local visited = {}

  local function ApplyToFontString(fontString)
    if not fontString or visited[fontString] then
      return
    end
    visited[fontString] = true

    local sourceText = fontString.__developerAccentSourceText
    if type(sourceText) ~= "string" then
      local currentText = fontString.GetText and fontString:GetText() or nil
      if type(currentText) == "string" and currentText:find("|cffa87ae8", 1, true) then
        sourceText = currentText
        fontString.__developerAccentSourceText = currentText
      end
    end
    if type(sourceText) == "string" then
      fontString:SetText(ApplyDeveloperAccentMarkup(sourceText))
    end
  end

  local function WalkFrame(frame)
    if not frame or visited[frame] then
      return
    end
    visited[frame] = true
    if frame.GetObjectType and frame:GetObjectType() == "FontString" then
      ApplyToFontString(frame)
      return
    end
    if type(frame.GetRegions) == "function" then
      for _, region in ipairs({ frame:GetRegions() }) do
        if region and region.GetObjectType and region:GetObjectType() == "FontString" then
          ApplyToFontString(region)
        end
      end
    end
    if type(frame.GetChildren) == "function" then
      for _, child in ipairs({ frame:GetChildren() }) do
        WalkFrame(child)
      end
    end
  end

  WalkFrame(root)
end

local function BuildTutorialPage(editor, tutorialPage)
  if not editor or not tutorialPage then return end

  local tutorialNavBg = CreateFrame("Frame", nil, tutorialPage, "BackdropTemplate")
  tutorialNavBg:SetPoint("TOPLEFT", 0, -44)
  tutorialNavBg:SetPoint("BOTTOMLEFT", 0, 0)
  tutorialNavBg:SetWidth(240)
  ApplyFrameSkin(tutorialNavBg)
  editor.tutorialNavBg = tutorialNavBg

  local tutNavBeginnerBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavBeginnerBtn:SetSize(200, 20)
  tutNavBeginnerBtn:SetPoint("TOPLEFT", 6, -18)
  tutNavBeginnerBtn:SetText("Beginner Guide")
  tutNavBeginnerBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Beginner") end
  end)
  editor.tutorialNavBeginnerBtn = tutNavBeginnerBtn

  local tutNavProfilesBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavProfilesBtn:SetSize(200, 20)
  tutNavProfilesBtn:SetPoint("TOPLEFT", 6, -44)
  tutNavProfilesBtn:SetText("Profiles")
  tutNavProfilesBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Profiles") end
  end)
  editor.tutorialNavProfilesBtn = tutNavProfilesBtn

  local tutNavPanelsBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavPanelsBtn:SetSize(200, 20)
  tutNavPanelsBtn:SetPoint("TOPLEFT", 6, -70)
  tutNavPanelsBtn:SetText("Session")
  tutNavPanelsBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Panels") end
  end)
  editor.tutorialNavPanelsBtn = tutNavPanelsBtn

  local tutNavCustomBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavCustomBtn:SetSize(200, 20)
  tutNavCustomBtn:SetPoint("TOPLEFT", 6, -96)
  tutNavCustomBtn:SetText("Live Board")
  tutNavCustomBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Custom") end
  end)
  editor.tutorialNavCustomBtn = tutNavCustomBtn

  local tutNavSimulationBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavSimulationBtn:SetSize(200, 20)
  tutNavSimulationBtn:SetPoint("TOPLEFT", 6, -122)
  tutNavSimulationBtn:SetText("Run Simulation")
  tutNavSimulationBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("RunSimulation") end
  end)
  editor.tutorialNavSimulationBtn = tutNavSimulationBtn

  local tutNavAddFolderBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavAddFolderBtn:SetSize(200, 20)
  tutNavAddFolderBtn:SetPoint("TOPLEFT", 6, -148)
  tutNavAddFolderBtn:SetText("Loot List")
  tutNavAddFolderBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("AddFolder") end
  end)
  editor.tutorialNavAddFolderBtn = tutNavAddFolderBtn

  editor.tutorialNavGroupsBtn = nil

  local tutNavWorkflowBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavWorkflowBtn:SetSize(200, 20)
  tutNavWorkflowBtn:SetPoint("TOPLEFT", 6, -174)
  tutNavWorkflowBtn:SetText("LC Workflow")
  tutNavWorkflowBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Workflow") end
  end)
  editor.tutorialNavWorkflowBtn = tutNavWorkflowBtn

  local tutNavAutoPassBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavAutoPassBtn:SetSize(200, 20)
  tutNavAutoPassBtn:SetPoint("TOPLEFT", 6, -200)
  tutNavAutoPassBtn:SetText("Auto Pass")
  tutNavAutoPassBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("AutoPass") end
  end)
  editor.tutorialNavAutoPassBtn = tutNavAutoPassBtn

  local tutNavSyncBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavSyncBtn:SetSize(200, 20)
  tutNavSyncBtn:SetPoint("TOPLEFT", 6, -226)
  tutNavSyncBtn:SetText("Sync Workflow")
  tutNavSyncBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow") end
  end)
  editor.tutorialNavSyncBtn = tutNavSyncBtn

  local tutNavRightsBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavRightsBtn:SetSize(200, 20)
  tutNavRightsBtn:SetPoint("TOPLEFT", 6, -278)
  tutNavRightsBtn:SetText("Rights Tutorial")
  tutNavRightsBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Rights") end
  end)
  editor.tutorialNavRightsBtn = tutNavRightsBtn

  local tutNavRolesBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavRolesBtn:SetSize(200, 20)
  tutNavRolesBtn:SetPoint("TOPLEFT", 6, -252)
  tutNavRolesBtn:SetText("Roles")
  tutNavRolesBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Roles") end
  end)
  editor.tutorialNavRolesBtn = tutNavRolesBtn

  local tutorialRightBg = CreateFrame("Frame", nil, tutorialPage, "BackdropTemplate")
  tutorialRightBg:SetPoint("TOPLEFT", tutorialNavBg, "TOPRIGHT", 12, 0)
  tutorialRightBg:SetPoint("BOTTOMRIGHT", 0, 0)
  ApplyFrameSkin(tutorialRightBg)
  tutorialRightBg:SetClipsChildren(true)
  editor.tutorialRightBg = tutorialRightBg
  editor.RefreshTutorialAccentMarkup = function()
    RefreshDeveloperAccentMarkup(tutorialPage)
  end

  local function CreateTutorialSection(parent, headerTex, steps)
    local scroll = CreateFrame("ScrollFrame", nil, parent, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 6, -6)
    scroll:SetPoint("BOTTOMRIGHT", -28, 6)
    BindMouseWheelScroll(scroll)

    local frame = CreateFrame("Frame", nil, scroll)
    frame:SetPoint("TOPLEFT", 0, 0)
    frame:SetSize(1, 1)
    frame:SetClipsChildren(true)
    scroll:SetScrollChild(frame)

    scroll:SetScript("OnSizeChanged", function(self)
      local w = self:GetWidth() - 28
      if w < 1 then w = 1 end
      frame:SetWidth(w)
      if editor and editor.LayoutTutorial then editor:LayoutTutorial() end
      if self.UpdateScrollChildRect then self:UpdateScrollChildRect() end
    end)

    local header = nil
    if headerTex and headerTex ~= "" then
      header = frame:CreateTexture(nil, "ARTWORK")
      header:SetPoint("TOPLEFT", 18, -18)
      RegisterTutorialTexture(editor, header, headerTex)
      header:SetTexCoord(0, 1, 0, 1)
    end

    local cards = {}
    for _, tex in ipairs(steps) do
      local card = CreateFrame("Frame", nil, frame)
      card:SetPoint("TOPLEFT", 0, 0)
      card:SetSize(CARD_W, CARD_H)
      local texObj = card:CreateTexture(nil, "ARTWORK")
      texObj:SetPoint("TOPLEFT", 0, 0)
      texObj:SetPoint("BOTTOMRIGHT", 0, 0)
      RegisterTutorialTexture(editor, texObj, tex)
      texObj:SetTexCoord(0, 1, 0, 1)
      card.texObj = texObj
      cards[#cards + 1] = card
    end

    return scroll, frame, header, cards
  end

  local function SetTutorialSyncCellState(cell, enabled)
    if not cell or not cell.label then
      return
    end

    local ok = enabled and true or false
    cell.label:SetText(ok and "OK" or "X")
    local bg = ok and { 0.08, 0.18, 0.12, 0.98 } or { 0.22, 0.09, 0.10, 0.98 }
    local text = ok and { 0.58, 0.96, 0.70, 1.00 } or { 1.00, 0.58, 0.58, 1.00 }
    if cell.SetBackdropColor then
      cell:SetBackdropColor(bg[1], bg[2], bg[3], bg[4])
    end
    cell.label:SetTextColor(text[1], text[2], text[3], text[4])
  end

  local function CreateTutorialSyncCell(parent, enabled)
    local cell = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    ApplyFrameSkin(cell)
    cell:SetSize(68, 20)
    cell.label = cell:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    cell.label:SetPoint("CENTER", 0, 0)
    SetTutorialSyncCellState(cell, enabled)
    return cell
  end

  local function CreateTutorialSyncTable(parent, titleText, rowDefs, noteText)
    local frame = CreateFrame("Frame", nil, parent)
    frame.__rowDefs = rowDefs or {}
    frame.__rows = {}

    frame.title = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    frame.title:SetJustifyH("LEFT")
    frame.title:SetText(titleText or "")

    frame.header = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    ApplyFrameSkin(frame.header)
    if frame.header.SetBackdropColor then
      frame.header:SetBackdropColor(0.07, 0.07, 0.10, 0.98)
    end
    frame.header.action = frame.header:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    frame.header.action:SetJustifyH("LEFT")
    frame.header.action:SetText("|cffa87ae8Action|r")
    frame.header.live = frame.header:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    frame.header.live:SetJustifyH("CENTER")
    frame.header.live:SetText("|cffa87ae8Live Sync|r")
    frame.header.localOnly = frame.header:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    frame.header.localOnly:SetJustifyH("CENTER")
    frame.header.localOnly:SetText("|cffa87ae8Local|r")

    for _, rowDef in ipairs(frame.__rowDefs) do
      local row = {}
      if rowDef.section then
        row.section = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        row.section:SetJustifyH("LEFT")
        row.section:SetText("|cffa87ae8" .. tostring(rowDef.section) .. "|r")
      else
        row.frame = CreateFrame("Frame", nil, frame, "BackdropTemplate")
        ApplyFrameSkin(row.frame)
        if row.frame.SetBackdropColor then
          row.frame:SetBackdropColor(0.05, 0.05, 0.07, 0.98)
        end
        row.label = row.frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        row.label:SetJustifyH("LEFT")
        row.label:SetText(tostring(rowDef.label or ""))
        row.live = CreateTutorialSyncCell(row.frame, rowDef.live)
        row.localOnly = CreateTutorialSyncCell(row.frame, rowDef.localOnly)
      end
      frame.__rows[#frame.__rows + 1] = row
    end

    if noteText and noteText ~= "" then
      frame.note = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      frame.note:SetJustifyH("LEFT")
      frame.note:SetText(noteText)
    end

    frame.LayoutForWidth = function(self, width)
      width = math.max(360, math.floor(tonumber(width) or 360))
      self:SetWidth(width)

      local y = 0
      self.title:ClearAllPoints()
      self.title:SetPoint("TOPLEFT", 0, y)
      self.title:SetWidth(width)
      y = y - ((self.title.GetStringHeight and self.title:GetStringHeight()) or 16) - 8

      local liveW = 68
      local localW = 68
      local gap = 10
      local labelW = math.max(180, width - liveW - localW - (gap * 3))

      self.header:ClearAllPoints()
      self.header:SetPoint("TOPLEFT", 0, y)
      self.header:SetSize(width, 24)
      self.header.action:ClearAllPoints()
      self.header.action:SetPoint("LEFT", 10, 0)
      self.header.action:SetWidth(labelW - 10)
      self.header.live:ClearAllPoints()
      self.header.live:SetPoint("LEFT", labelW + gap, 0)
      self.header.live:SetWidth(liveW)
      self.header.localOnly:ClearAllPoints()
      self.header.localOnly:SetPoint("LEFT", labelW + liveW + (gap * 2), 0)
      self.header.localOnly:SetWidth(localW)
      y = y - 28

      for index, rowDef in ipairs(self.__rowDefs) do
        local row = self.__rows[index]
        if rowDef.section and row and row.section then
          row.section:ClearAllPoints()
          row.section:SetPoint("TOPLEFT", 0, y)
          row.section:SetWidth(width)
          y = y - ((row.section.GetStringHeight and row.section:GetStringHeight()) or 14) - 4
        elseif row and row.frame then
          row.frame:ClearAllPoints()
          row.frame:SetPoint("TOPLEFT", 0, y)
          row.frame:SetSize(width, 24)
          row.label:ClearAllPoints()
          row.label:SetPoint("LEFT", 10, 0)
          row.label:SetWidth(labelW - 10)
          row.live:ClearAllPoints()
          row.live:SetPoint("LEFT", labelW + gap, 0)
          row.live:SetSize(liveW, 20)
          row.localOnly:ClearAllPoints()
          row.localOnly:SetPoint("LEFT", labelW + liveW + (gap * 2), 0)
          row.localOnly:SetSize(localW, 20)
          y = y - 26
        end
      end

      if self.note then
        y = y - 6
        self.note:ClearAllPoints()
        self.note:SetPoint("TOPLEFT", 0, y)
        self.note:SetWidth(width)
        y = y - ((self.note.GetStringHeight and self.note:GetStringHeight()) or 0) - 4
      end

      self:SetHeight(math.max(1, -y))
    end

    return frame
  end

  local panelsSteps = {}
  local profilesSteps = {}
  local customSteps = {}

  local tBeginnerScroll, tBeginnerFrame, tBeginnerHeader, tBeginnerCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tPanelsScroll, tPanelsFrame, tPanelsHeader, tPanelsCards =
    CreateTutorialSection(tutorialRightBg, nil, panelsSteps)
  local tProfilesScroll, tProfilesFrame, tProfilesHeader, tProfilesCards =
    CreateTutorialSection(tutorialRightBg, nil, profilesSteps)
  local tCustomScroll, tCustomFrame, tCustomHeader, tCustomCards =
    CreateTutorialSection(tutorialRightBg, nil, customSteps)
  local tSimulationScroll, tSimulationFrame, tSimulationHeader, tSimulationCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tAddFolderScroll, tAddFolderFrame, tAddFolderHeader, tAddFolderCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tGroupsScroll, tGroupsFrame, tGroupsHeader, tGroupsCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tWorkflowScroll, tWorkflowFrame, tWorkflowHeader, tWorkflowCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tAutoPassScroll, tAutoPassFrame, tAutoPassHeader, tAutoPassCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tSyncScroll, tSyncFrame, tSyncHeader, tSyncCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tRolesScroll, tRolesFrame, tRolesHeader, tRolesCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tRightsScroll, tRightsFrame, tRightsHeader, tRightsCards =
    CreateTutorialSection(tutorialRightBg, nil, {})

  local tBeginnerIntro = tBeginnerFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tBeginnerIntro:SetJustifyH("LEFT")
  tBeginnerIntro:SetText("Beginner Guide")

  local tBeginnerNote = tBeginnerFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tBeginnerNote:SetJustifyH("LEFT")
  tBeginnerNote:SetText(
    "|cffa87ae81. First raid setup|r\n" ..
    "- Everyone in the raid needs to download and enable LidCouncil.\n" ..
    "- The Guildmaster or Raidlead creates one new profile for the current season, for example |cff4adf6bMidnight Season 1|r.\n" ..
    "- Invite everyone into the WoW raid party first.\n" ..
    "- Open |cff4adf6bSession|r and press |cff4adf6bPrepare Session|r.\n" ..
    "- Everyone with LidCouncil receives a popup and can join the session."
  )

  local tBeginnerAfter = tBeginnerFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tBeginnerAfter:SetJustifyH("LEFT")
  tBeginnerAfter:SetText(
    "|cffa87ae82. Wait for sync|r\n" ..
    "- After players accept, LidCouncil sends them the host profile one by one.\n" ..
    "- This can take a bit of time in a larger raid because the addon avoids sending everything at once.\n" ..
    "- Wait until the joined-member table looks stable and the required members have received the profile.\n" ..
    "- When the raid is ready, press |cff4adf6bStart Session|r."
  )

  local tBeginnerPost = tBeginnerFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tBeginnerPost:SetJustifyH("LEFT")
  tBeginnerPost:SetText(
    "|cffa87ae83. Simple raid workflow|r\n" ..
    "- Raid normally, kill bosses, collect loot, and distribute it through the Live Board.\n" ..
    "- When the raid is done, return to |cff4adf6bSession|r and press |cff4adf6bStop Session|r.\n" ..
    "- Since everyone now has the same profile copy, the next raid is usually simple:\n" ..
    "  1. Prepare Session\n" ..
    "  2. Start Session\n" ..
    "  3. Raid\n" ..
    "  4. Stop Session\n" ..
    "- This is the most simple way to use LidCouncil."
  )

  local tPanelsIntro = tPanelsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tPanelsIntro:SetJustifyH("LEFT")
  tPanelsIntro:SetText("The Session page is the preparation and live-session control room for the active LidCouncil profile.")

  local tPanelsTitle = tPanelsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tPanelsTitle:SetJustifyH("LEFT")
  tPanelsTitle:SetText("Session")

  local tPanelsNote = tPanelsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tPanelsNote:SetJustifyH("LEFT")
  tPanelsNote:SetText(
    "|cffa87ae81. What this page is about|r\n" ..
    "- This page shows whether the selected profile is idle, preparing, or running an active loot session.\n" ..
    "- It is where LidCouncil tracks the host, joined members, invite flow, Auto Pass state, addon version, and sync state.\n" ..
    "- Session work is now split into two stages: |cff4adf6bPrepare Session|r first, then |cff4adf6bStart Session|r when the raid is ready."
  )

  local tPanelsAfter = tPanelsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tPanelsAfter:SetJustifyH("LEFT")
  tPanelsAfter:SetText(
    "|cffa87ae82. What you can do here|r\n" ..
    "- Use |cff4adf6bPrepare Session|r to open the preparation lobby and invite the current party or raid.\n" ..
    "- Review who joined, who is still missing, and whether profile revisions look synced before loot starts.\n" ..
    "- Use |cff4adf6bStart Session|r to make the host profile authoritative and activate the real loot workflow.\n" ..
    "- Send late session invites and use the sync tools when someone joins later or needs a refresh."
  )

  local tPanelsPost = tPanelsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tPanelsPost:SetJustifyH("LEFT")
  tPanelsPost:SetText(
    "|cffa87ae83. Typical use in LidCouncil|r\n" ..
    "- Select and activate the correct profile first.\n" ..
    "- Press |cff4adf6bPrepare Session|r before the boss pull or before loot is handled so members can join and sync can be checked.\n" ..
    "- Wait until the joined-member table and sync state look correct.\n" ..
    "- Press |cff4adf6bStart Session|r only when the host copy should become the source of truth for the active raid session.\n" ..
    "- Return here whenever you need late invites, sync management, Auto Pass overview, or a quick session status check."
  )

  local tProfilesIntro = tProfilesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tProfilesIntro:SetJustifyH("LEFT")
  tProfilesIntro:SetText("Profiles define the full LidCouncil ruleset and data context you want to use for one raid environment.")

  local tAddFolderNote = tAddFolderFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAddFolderNote:SetJustifyH("LEFT")
  tAddFolderNote:SetText(
    "|cffa87ae81. What this page is about|r\n" ..
    "- The Loot List page is the long-term data layer behind your loot decisions.\n" ..
    "- It keeps the raider list, manual point entries, notes, and related profile data in one place.\n" ..
    "- Session awards and profile-specific loot data flow back into this page over time."
  )

  local tAddFolderImage = tAddFolderFrame:CreateTexture(nil, "ARTWORK")
  RegisterTutorialTexture(editor, tAddFolderImage, "Interface\\AddOns\\LidCouncil\\Media\\LidCouncil\\Tutorials\\CBG_6_Folder.tga")

  local tAddFolderAfter = tAddFolderFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAddFolderAfter:SetJustifyH("LEFT")
  tAddFolderAfter:SetText(
    "|cffa87ae82. What you can do here|r\n" ..
    "- Review member-related profile data before and after raid nights.\n" ..
    "- Open details for a raider, maintain notes, and work with manual point entries when your rights allow it.\n" ..
    "- Keep the profile transparent and up to date so Live Board decisions are backed by clean data."
  )

  local tAddFolderPost = tAddFolderFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAddFolderPost:SetJustifyH("LEFT")
  tAddFolderPost:SetText(
    "|cffa87ae83. Typical use in LidCouncil|r\n" ..
    "- Prepare the Loot List between raids so the next session starts from reliable information.\n" ..
    "- Let session results write back into the profile instead of tracking everything manually outside the addon.\n" ..
    "- Use this page as the long-term memory of the profile while the Live Board handles the real-time decisions."
  )

  local tAddFolderPreviewNote = tAddFolderFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAddFolderPreviewNote:SetJustifyH("LEFT")
  tAddFolderPreviewNote:SetText("")

  local tAddFolderIntro = tAddFolderFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAddFolderIntro:SetJustifyH("LEFT")
  tAddFolderIntro:SetText("The Loot List page stores the profile history and planning data that support fair distribution across multiple raids.")

  local tAddFolderStepTitle = tAddFolderFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tAddFolderStepTitle:SetJustifyH("LEFT")
  tAddFolderStepTitle:SetText("Loot List")

  local tAddFolderPreviewImage = tAddFolderFrame:CreateTexture(nil, "ARTWORK")
  RegisterTutorialTexture(editor, tAddFolderPreviewImage, "Interface\\AddOns\\LidCouncil\\Media\\LidCouncil\\Tutorials\\CBG_7_preview.tga")

  local tAddFolderPostImage = tAddFolderFrame:CreateTexture(nil, "ARTWORK")
  RegisterTutorialTexture(editor, tAddFolderPostImage, "Interface\\AddOns\\LidCouncil\\Media\\LidCouncil\\Tutorials\\CBG_8_Select_Background.tga")

  local tGroupsIntro = tGroupsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tGroupsIntro:SetJustifyH("LEFT")
  tGroupsIntro:SetText("Group Tutorial")

  local tGroupsNote = tGroupsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tGroupsNote:SetJustifyH("LEFT")
  tGroupsNote:SetText(
    "|cffa87ae81. Why use groups?|r\n" ..
    "- Groups help you keep long lists clean and organized.\n" ..
    "- You can use them in |cff4adf6bProfiles|r and in |cff4adf6bMy Backgrounds|r.\n" ..
    "- Groups are only for organization. They do not change or delete the content inside them."
  )

  local tGroupsAfter = tGroupsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tGroupsAfter:SetJustifyH("LEFT")
  tGroupsAfter:SetText(
    "|cffa87ae82. How to create and fill a group|r\n" ..
    "- Open |cff4adf6bProfiles|r and click |cff4adf6bCreate Group|r, or open |cff4adf6bMy Backgrounds|r and click |cff4adf6bAdd Group|r.\n" ..
    "- Enter a group name.\n" ..
    "- Left-click and drag a profile or category.\n" ..
    "- Drop it onto the target group row.\n" ..
    "- While dragging, the target group highlights so you can clearly see where it will land."
  )

  local tGroupsPost = tGroupsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tGroupsPost:SetJustifyH("LEFT")
  tGroupsPost:SetText(
    "|cffa87ae83. Managing groups|r\n" ..
    "- Click the |cff4adf6bsmall arrow on the far left|r to collapse or expand a group.\n" ..
    "- Click the group row itself to open its settings on the right side.\n" ..
    "- Use |cff4adf6bRename Group|r to rename it.\n" ..
    "- Use |cff4adf6bDelete Group|r to remove the group only. The profiles or categories inside become |cff4adf6bgroupless|r and stay intact.\n" ..
    "- To remove a single item from a group, drag it and release it back inside the list instead of dropping it on another group."
  )

  local tProfilesGroupsTitle = tProfilesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tProfilesGroupsTitle:SetJustifyH("LEFT")
  tProfilesGroupsTitle:SetText("Profiles")

  local tProfilesGroupsNote = tProfilesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tProfilesGroupsNote:SetJustifyH("LEFT")
  tProfilesGroupsNote:SetText(
    "|cffa87ae81. What this page is about|r\n" ..
    "- A profile bundles your roll settings, point settings, raid setup, council rights, and related loot data.\n" ..
    "- Use different profiles when your guild needs different rulesets, raid formats, or test environments.\n" ..
    "- Only one profile is active at a time, so this page defines the foundation for the rest of LidCouncil."
  )

  local tProfilesGroupsAfter = tProfilesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tProfilesGroupsAfter:SetJustifyH("LEFT")
  tProfilesGroupsAfter:SetText(
    "|cffa87ae82. What you can do here|r\n" ..
    "- Create, copy, rename, activate, import, export, or delete local profile copies.\n" ..
    "- Open the selected profile settings to define roll options, point logic, raid data, and council permissions.\n" ..
    "- Save, load, or delete local backups of profile session and Loot List data when you need a safe restore point.\n" ..
    "- Decide which profile should be the active one before you move into session work."
  )

  local tProfilesGroupsPost = tProfilesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tProfilesGroupsPost:SetJustifyH("LEFT")
  tProfilesGroupsPost:SetText(
    "|cffa87ae83. Typical use in LidCouncil|r\n" ..
    "- Start by selecting the raid profile you want to maintain or use tonight.\n" ..
    "- Review the profile configuration before the first loot drops instead of adjusting rules mid-session.\n" ..
    "- Treat this page as the setup layer; once the profile is ready, the Session page takes over the live workflow."
  )

  local tCustomIntro = tCustomFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tCustomIntro:SetJustifyH("LEFT")
  tCustomIntro:SetText("The Live Board is the real-time decision page once loot starts dropping during a LidCouncil session.")

  local tCustomTitle = tCustomFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tCustomTitle:SetJustifyH("LEFT")
  tCustomTitle:SetText("Live Board")

  local tCustomNote = tCustomFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tCustomNote:SetJustifyH("LEFT")
  tCustomNote:SetText(
    "|cffa87ae81. What this page is about|r\n" ..
    "- The Live Board collects dropped items, player choices, votes, and assignment state in one place.\n" ..
    "- It is the central raid-time page for hosts and council members while loot is actively being processed.\n" ..
    "- Instead of guessing, you can compare the current item context directly inside the board."
  )

  local tCustomAfter = tCustomFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tCustomAfter:SetJustifyH("LEFT")
  tCustomAfter:SetText(
    "|cffa87ae82. What you can do here|r\n" ..
    "- Review each dropped item and see the submitted roll choices from joined members.\n" ..
    "- Inspect the item context, compare candidates, and place or remove council votes.\n" ..
    "- Assign or unassign loot when your current role and session rights allow it."
  )

  local tCustomPost = tCustomFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tCustomPost:SetJustifyH("LEFT")
  tCustomPost:SetText(
    "|cffa87ae83. Typical use in LidCouncil|r\n" ..
    "- Keep this page open during raid once the session is running and loot begins to drop.\n" ..
    "- Use it item by item while raiders send their choices and council members review the outcome.\n" ..
    "- Final assignments from the Live Board flow back into the session state and the Loot List data."
  )

  local tSimulationIntro = tSimulationFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tSimulationIntro:SetJustifyH("LEFT")
  tSimulationIntro:SetText("Run Simulation")

  local tSimulationNote = tSimulationFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSimulationNote:SetJustifyH("LEFT")
  tSimulationNote:SetText(
    "|cffa87ae81. Run Fake Simulation|r\n" ..
    "- This is a local sandbox test for the Live Board.\n" ..
    "- It does not require a prepared or active session.\n" ..
    "- It does not invite, sync, or update other players.\n" ..
    "- It uses local preview members and the Simulation profile to test roll choices, votes, assignments, and board layout.\n" ..
    "- It is useful when you want to check the UI or test profile settings alone."
  )

  local tSimulationAfter = tSimulationFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSimulationAfter:SetJustifyH("LEFT")
  tSimulationAfter:SetText(
    "|cffa87ae82. Run Real Simulation|r\n" ..
    "- This is an active-session dry run.\n" ..
    "- It requires a started session and can only be triggered by the session host.\n" ..
    "- It uses the actual joined session members and the active session profile.\n" ..
    "- LidCouncil sends simulated raid items to the real Live Board and opens the normal LidCouncil roll popup for joined members.\n" ..
    "- Board updates are synced like a real session, but no Loot List points are written."
  )

  local tSimulationPost = tSimulationFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSimulationPost:SetJustifyH("LEFT")
  tSimulationPost:SetText(
    "|cffa87ae83. Which one should I use?|r\n" ..
    "- Use |cff4adf6bRun Fake Simulation|r when you are alone or only want to test the local Live Board behavior.\n" ..
    "- Use |cff4adf6bRun Real Simulation|r when you want to test the real multiplayer flow with joined session members.\n" ..
    "- Real Simulation is the better raid-readiness test because it uses the same host-to-member workflow as real loot handling.\n" ..
    "- Fake Simulation is safer for quick UI checks because nothing leaves your client."
  )

  local tWorkflowIntro = tWorkflowFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tWorkflowIntro:SetJustifyH("LEFT")
  tWorkflowIntro:SetText("LC Workflow")

  local tWorkflowNote = tWorkflowFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tWorkflowNote:SetJustifyH("LEFT")
  tWorkflowNote:SetText(
    "|cffa87ae81. Prepare the session|r\n" ..
    "- Select and activate the loot profile you want to use, then click |cff4adf6bPrepare Session|r.\n" ..
    "- LidCouncil creates a preparation lobby, makes you the host, and sends invites to the current party or raid.\n" ..
    "- Preparation is not live loot processing yet. It exists so members can join and profile sync can be checked before the raid commits to the host copy.\n" ..
    "- If you are not in a party or raid yet, preparation exists only on your client until a group channel exists."
  )

  local tWorkflowAfter = tWorkflowFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tWorkflowAfter:SetJustifyH("LEFT")
  tWorkflowAfter:SetText(
    "|cffa87ae82. How raiders join|r\n" ..
    "- Raiders who are already in your current party or raid and also use LidCouncil receive a join popup.\n" ..
    "- The popup asks whether they want to join the preparation lobby and offers |cff4adf6bYes|r or |cffff4a4aNo|r.\n" ..
    "- If they accept, their client joins the same prepared session and sends a join reply back to the host.\n" ..
    "- The host then updates the joined-member table, syncs member data, and sends the current session snapshot.\n" ..
    "- If the joiner has no local copy of that shared profile yet, the host now sends a smaller Settings + Permissions bootstrap first so roll choices can unlock sooner.\n" ..
    "- The heavier Loot Data and Session Data for that shared profile can keep syncing in the background after the bootstrap.\n" ..
    "- If the joiner already has the same shared profile lineage, LidCouncil compares revisions before replacing anything.\n" ..
    "- They appear in |cff4adf6bCurrent Session|r with sync state, addon version, class/spec, and join time.\n" ..
    "- Players without LidCouncil do not receive the popup or the synced session workflow."
  )

  local tWorkflowLive = tWorkflowFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tWorkflowLive:SetJustifyH("LEFT")
  tWorkflowLive:SetText(
    "|cffa87ae83. Start the live session|r\n" ..
    "- When the joined-member table and profile sync state look correct, the host clicks |cff4adf6bStart Session|r.\n" ..
    "- If some joined members are still syncing, the warning popup now shows how many are already |cff4adf6broll-ready|r versus still waiting on bootstrap before the host decides whether to force start.\n" ..
    "- LidCouncil broadcasts the start signal, switches the prepared session to active, and treats the host profile as authoritative.\n" ..
    "- When loot drops, the host gets the |cff4adf6bLive Board|r.\n" ..
    "- Joined members get the LidCouncil roll popup and can send their choice back to the host.\n" ..
    "- LidCouncil keeps member, choice, and award data updated during the session.\n" ..
    "- |cff4adf6bAuto Pass|r is personal now: each joined player controls only their own setting.\n" ..
    "- If your personal Auto Pass is ON, LidCouncil auto-passes Blizzard loot rolls on your client. If it is OFF, Blizzard's normal loot roll window still appears."
  )

  local tWorkflowPost = tWorkflowFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tWorkflowPost:SetJustifyH("LEFT")
  tWorkflowPost:SetText(
    "|cffa87ae84. Loot List and late joiners|r\n" ..
    "- Session members are added or updated in the |cff4adf6bLoot List|r layer as they join the prepared or active session.\n" ..
    "- When the host assigns an item on the Live Board, the award is written into the Loot List / loot ledger.\n" ..
    "- Raiders invited after preparation or session start do not join automatically.\n" ..
    "- Use |cff4adf6bSend Session Invite|r in |cff4adf6bCurrent Session|r to invite only late raiders who are not already part of the session.\n" ..
    "- Existing session members do not get the popup again.\n" ..
    "- Shared snapshot copies can later be removed locally with |cff4adf6bDelete Profile|r once no prepared or active session is running for that profile.\n" ..
    "- A later host session can repopulate that shared copy again if you accept it.\n" ..
    "- Same visible profile names are safe because shared profiles use an internal immutable |cff4adf6bprofileId|r, not only the display name.\n" ..
    "- Only one active profile and one open LidCouncil session can exist at a time."
  )

  local tAutoPassIntro = tAutoPassFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tAutoPassIntro:SetJustifyH("LEFT")
  tAutoPassIntro:SetText("Auto Pass")

  local tAutoPassBasics = tAutoPassFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAutoPassBasics:SetJustifyH("LEFT")
  tAutoPassBasics:SetText(
    "|cffa87ae81. What Auto Pass means now|r\n" ..
    "- Auto Pass is a |cff4adf6bpersonal setting|r. There is no separate session-wide Auto Pass switch anymore.\n" ..
    "- If your personal Auto Pass is |cff4adf6bON|r, LidCouncil calls Blizzard's pass roll on your client when loot drops during the session.\n" ..
    "- If your personal Auto Pass is |cffff5a5aOFF|r, you keep Blizzard's normal loot roll window on your client."
  )

  local tAutoPassDefaults = tAutoPassFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAutoPassDefaults:SetJustifyH("LEFT")
  tAutoPassDefaults:SetText(
    "|cffa87ae82. Default behavior when a session starts or when you join|r\n" ..
    "- When you |cff4adf6bstart|r a session as the host, your personal Auto Pass defaults to |cffff5a5aOFF|r.\n" ..
    "- This keeps manual Blizzard loot handling available for the host by default.\n" ..
    "- When you |cff4adf6bjoin|r someone else's session, your personal Auto Pass defaults to |cff4adf6bON|r.\n" ..
    "- Joined members therefore start in pass mode automatically unless they choose to opt out.\n" ..
    "- Those defaults are applied each time a session is started or accepted."
  )

  local tAutoPassControl = tAutoPassFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAutoPassControl:SetJustifyH("LEFT")
  tAutoPassControl:SetText(
    "|cffa87ae83. Who can change it|r\n" ..
    "- Every joined player can change |cff4adf6btheir own|r Auto Pass setting any time in |cff4adf6bSession -> Auto Pass Setting|r.\n" ..
    "- The host cannot force another joined client to auto-pass through LidCouncil anymore.\n" ..
    "- Current Session shows each joined member's personal Auto Pass state as |cff4adf6bON|r or |cffff5a5aOFF|r."
  )

  local tAutoPassExamples = tAutoPassFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAutoPassExamples:SetJustifyH("LEFT")
  tAutoPassExamples:SetText(
    "|cffa87ae84. Practical examples|r\n" ..
    "- Host OFF, Raider ON: the host keeps Blizzard's roll window, the raider auto-passes.\n" ..
    "- Host OFF, Raider OFF: both keep Blizzard's roll window.\n" ..
    "- Host ON, Raider ON: both auto-pass.\n" ..
    "- This is useful for testing because each client can opt in or out without changing the whole session."
  )

  local tSyncIntro = tSyncFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tSyncIntro:SetJustifyH("LEFT")
  tSyncIntro:SetText("Sync Workflow")

  local tSyncTriggers = tSyncFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSyncTriggers:SetJustifyH("LEFT")
  tSyncTriggers:SetText(
    "|cffa87ae81. Current sync model and overload protection|r\n" ..
    "- |cff4adf6bPrepare Session|r invites party/raid members and builds the joined-member table before loot starts.\n" ..
    "- First join, recovery, or missing-profile cases can still require host-side snapshot repair, but missing-profile joins now try a smaller roll-ready bootstrap before the heavier background sync.\n" ..
    "- When a member rejoins later with the same revision + checksum summary, LidCouncil can now reuse persisted checksum / patch baselines from your earlier session view instead of rebuilding every precise scope from scratch.\n" ..
    "- Full snapshots and profile/member updates are sent through an outbound queue instead of being burst to the raid all at once.\n" ..
    "- Normal raid-time actions prefer smaller messages: one choice, one vote, one note, one Main / Alt update, one manual point entry, one award, or one delete action.\n" ..
    "- Profile settings and permissions prefer smaller per-action deltas where supported. Full snapshots stay as recovery and first-join tools.\n" ..
    "- Color changes are synced after the color picker is confirmed, not while the mouse is dragged.\n" ..
    "- Urgent loot flow messages keep priority over heavy profile sync traffic.\n" ..
    "- Settings, permissions, and loot/session data now carry per-domain checksums in the sync state.\n" ..
    "- Profile deltas are acknowledged by joined clients. If a client detects a missed sequence or checksum mismatch, it requests a full host refresh instead of guessing.\n" ..
    "- High-frequency roll/vote stat updates and Live Board history now coalesce into short delayed host checkpoints. Once a member has the needed leaf patch baseline, those checkpoints prefer smaller patch transfers, including direct vote rows, per-member selection rows, and narrow award/trade field rows, instead of resending the whole affected scope.\n" ..
    "- Preview-based member repair is now fenced more strictly: if the host copy or member state changes, or the preview sits too long, LidCouncil drops that cached preview and asks for a fresh one instead of applying stale nested rows."
  )

  local tSyncPaths = tSyncFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSyncPaths:SetJustifyH("LEFT")
  tSyncPaths:SetText(
    "|cffa87ae82. Who sends what to whom|r\n" ..
    "- Joiners answer only to the host. The host updates joined members and broadcasts member snapshots.\n" ..
    "- Once |cff4adf6bStart Session|r is pressed, the host profile becomes authoritative for that running session.\n" ..
    "- Roll choices go to the host first. The host validates and mirrors the accepted result back to joined boards.\n" ..
    "- Votes, awards, unassigns, and board sync keep the Live Board aligned across host, council, and joined clients.\n" ..
    "- Loot List deltas such as manual points, Main / Alt, notes, and Delete Character go to the host first when the editor is not the host. The host now prefers acknowledged profile deltas for these low-frequency changes and falls back to the older live message path only if needed.\n" ..
    "- |cff4adf6bCheck Differences|r loads detailed leaf checksum scopes for mismatched joined members. LidCouncil also refreshes these in the background when a joined member reports mismatched state. The Sync Management Leaf Scope Picker can then narrow |cff4adf6bPush Host Repair|r, |cff4adf6bPreview Member Repair|r, and |cff4adf6bPull Member Repair|r down to only the selected differing leaves for that member. |cff4adf6bRun Recommended Repair|r stays manual and simply chooses the safest current action for that selected member.\n" ..
    "- After a preview arrives for a patch-capable leaf, the Sync Detail popup can now select individual nested preview rows to keep the host value or take only that member value. Applying that selection updates the host copy and immediately queues direct host repair for the affected leaf scopes. If nothing is selected, the broader member/domain behavior stays unchanged.\n" ..
    "- Sync Management also keeps a short |cff4adf6bRepair History and Undo|r panel for recent host-side repairs so the host can inspect what was applied and revert the last local repair snapshot if needed.\n" ..
    "- |cff4adf6bSend Board Sync|r is the recovery tool for rebuilding a lost Live Board from the host's current board snapshot."
  )

  local tSyncGaps = CreateTutorialSyncTable(tSyncFrame, "|cffa87ae83. Sync List - Profile and Session|r", {
    { section = "Profile" },
    { label = "Create Profile", live = false, localOnly = true },
    { label = "Activate / Deactivate Profile", live = false, localOnly = true },
    { label = "Copy Profile", live = false, localOnly = true },
    { label = "Delete Local Profile Copy", live = false, localOnly = true },
    { label = "Save / Load / Delete Backup", live = false, localOnly = true },
    { section = "Council Settings" },
    { label = "Promote / Degrade", live = true, localOnly = false },
    { label = "Change permission policy", live = true, localOnly = false },
    { section = "Raid Settings" },
    { label = "Change thumbnail", live = true, localOnly = false },
    { section = "Roll Settings" },
    { label = "Template / add / edit / delete", live = true, localOnly = false },
    { section = "Point Settings" },
    { label = "LidTable / custom rules", live = true, localOnly = false },
    { section = "Session" },
    { label = "Prepare Session / Accept Join", live = true, localOnly = false },
    { label = "Start / Stop Session", live = true, localOnly = false },
    { label = "Send Invite / Remove Member", live = true, localOnly = false },
    { label = "Push Profile / Request Member / Board", live = true, localOnly = false },
    { label = "Auto Pass Setting", live = true, localOnly = true },
  }, nil)

  local tSyncNotes = CreateTutorialSyncTable(tSyncFrame, "|cffa87ae84. Sync List - Live Board, Loot List, and local-only areas|r", {
    { section = "Live Board" },
    { label = "Loot item opens roll popup", live = true, localOnly = false },
    { label = "Player roll choice", live = true, localOnly = false },
    { label = "Council vote", live = true, localOnly = false },
    { label = "Assign / Unassign item", live = true, localOnly = false },
    { label = "Pop Out position / collapse state", live = false, localOnly = true },
    { label = "Run Fake Simulation", live = false, localOnly = true },
    { label = "Run Real Simulation", live = true, localOnly = false },
    { section = "Loot List" },
    { label = "Manual points add / delete", live = true, localOnly = false },
    { label = "Main / Alt change", live = true, localOnly = false },
    { label = "Character note", live = true, localOnly = false },
    { label = "Delete Character", live = true, localOnly = false },
    { label = "Search, sorting, view mode", live = false, localOnly = true },
    { section = "Local client areas" },
    { label = "UI Settings: editor size, collapse, minimap", live = false, localOnly = true },
    { label = "Tutorial / About / Developer Mode toggles", live = false, localOnly = true },
  },
    "- |cff4adf6bLive Sync|r means the action is sent during a prepared or active matching session, usually host-routed.\n" ..
    "- |cff4adf6bLocal|r means it changes only your client/SavedVariables and may become part of a future shared profile if the host later shares that profile.\n" ..
    "- Auto Pass is both: your personal setting is local control, but the current ON/OFF state is shared to the session table.\n" ..
    "- Current profile settings now prefer smaller per-action deltas and queued large snapshots for safer raid-scale sync."
  )

  local tRolesIntro = tRolesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tRolesIntro:SetJustifyH("LEFT")
  tRolesIntro:SetText("Roles")

  local tRolesBasics = tRolesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRolesBasics:SetJustifyH("LEFT")
  tRolesBasics:SetText(
    "|cffa87ae81. Bare minimum role purpose|r\n" ..
    "- |cff4adf6bProfile Master|r is the owner of the profile and has full access.\n" ..
    "- |cff4adf6bSession Host|r is responsible for assigning and unassigning loot during the active session.\n" ..
    "- |cff4adf6bCouncil Member|r is responsible for voting and contributing to fair loot sharing decisions.\n" ..
    "- |cff4adf6bRaider|r has transparent access to the Loot List and Loot List details, but is limited in manager options."
  )

  local tRolesMinimum = tRolesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRolesMinimum:SetJustifyH("LEFT")
  tRolesMinimum:SetText(
    "|cffa87ae82. Non-revokable minimum rights|r\n" ..
    "- Manage Rights defaults to |cff4adf6bProfile Master|r only. The Master can grant it to Session Host or Council when trusted helpers should manage rights too.\n" ..
    "- Session Host must keep |cff4adf6bManage / Stop Session|r, |cff4adf6bOpen Live Board|r, |cff4adf6bUse Voting|r, and |cff4adf6bAssign Loot|r.\n" ..
    "- Council Member must keep |cff4adf6bOpen Live Board|r and |cff4adf6bUse Voting|r.\n" ..
    "- These rights are not editable, even by the Profile Master, because removing them would eliminate the purpose of the role.\n" ..
    "- If you try to revoke one of these rights, LidCouncil denies the change instead of creating a broken role."
  )

  local tRolesDefaults = tRolesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRolesDefaults:SetJustifyH("LEFT")
  tRolesDefaults:SetText(
    "|cffa87ae83. Default LidCouncil rights|r\n" ..
    "- The explanations above describe only the bare minimum function of each role.\n" ..
    "- LidCouncil's default right settings grant more access to Session Host and Council than the minimum.\n" ..
    "- Those defaults are intentional: LidCouncil has carefully studied the practical right settings needed to manage many field situations properly.\n" ..
    "- In real raids, trusted helpers may need profile, session, or Loot List tools even when those tools are above the strict role minimum.\n" ..
    "- The Profile Master can still revoke non-critical access when a stricter setup is needed."
  )

  local tRolesPractical = tRolesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRolesPractical:SetJustifyH("LEFT")
  tRolesPractical:SetText(
    "|cffa87ae84. Practical model|r\n" ..
    "- Use |cff4adf6bProfile Master|r for the person who owns and maintains the profile.\n" ..
    "- Use |cff4adf6bSession Host|r for the person currently running loot assignment on the Live Board.\n" ..
    "- Use |cff4adf6bCouncil Member|r for trusted voters who help decide loot distribution.\n" ..
    "- Use |cff4adf6bRaider|r for regular participants who should see transparent loot data without management access."
  )

  local tRightsIntro = tRightsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tRightsIntro:SetJustifyH("LEFT")
  tRightsIntro:SetText("Rights Tutorial")

  local tRightsProfile = tRightsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRightsProfile:SetJustifyH("LEFT")
  tRightsProfile:SetText(
    "|cffa87ae81. Profile rights|r\n" ..
    "- |cff4adf6bView Council Settings|r allows opening and reviewing the council/member role area.\n" ..
    "- |cff4adf6bManage Rights|r allows granting/revoking permission policy rights and changing member roles. It defaults to Master only, but the Master can grant it to Session Host or Council.\n" ..
    "- |cff4adf6bEdit Raid Settings|r allows changing the selected raid thumbnail and raid association for the profile.\n" ..
    "- |cff4adf6bEdit Roll Settings|r allows adding, removing, reordering, importing, and editing roll options.\n" ..
    "- |cff4adf6bEdit Point Settings|r allows changing the point mode and point rules.\n" ..
    "- |cff4adf6bDelete Local Profile Copy|r allows deleting only your local profile copy. This right is fixed on for every role, never deletes a profile for everyone, and is blocked while any LidCouncil session is active."
  )

  local tRightsSession = tRightsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRightsSession:SetJustifyH("LEFT")
  tRightsSession:SetText(
    "|cffa87ae82. Session rights|r\n" ..
    "- |cff4adf6bStart Session|r allows starting a new LidCouncil session for the active profile when no session is running.\n" ..
    "- |cff4adf6bManage / Stop Session|r is the running-session administration right. It enables Stop Session, Send Session Invite, Check Differences, Push Host Repair, Preview Member Repair, Pull Member Repair, and Send Board Sync while you are the active session host.\n" ..
    "- |cff4adf6bOpen Live Board|r allows opening and reviewing the Live Board section. This does not by itself grant item assignment.\n" ..
    "- |cff4adf6bUse Voting|r allows casting and removing your vote on Live Board member rows.\n" ..
    "- |cff4adf6bAssign Loot|r allows the active session host to assign and unassign items on the Live Board. The client must still be the actual session host; granting this right alone does not let a non-host assign loot."
  )

  local tRightsLootList = tRightsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRightsLootList:SetJustifyH("LEFT")
  tRightsLootList:SetText(
    "|cffa87ae83. Loot List rights|r\n" ..
    "- |cff4adf6bEdit Loot List|r allows direct Loot List edits such as changing loot-related member data where the UI exposes edit controls.\n" ..
    "- |cff4adf6bEdit Manual Points|r allows adding or deleting manual point entries.\n" ..
    "- |cff4adf6bEdit Main / Alt|r allows changing main/alt relationships in Loot List details.\n" ..
    "- |cff4adf6bEdit Notes|r allows editing character notes.\n" ..
    "- |cff4adf6bDelete Loot List Character|r allows deleting a character and all their points from Loot List details. It defaults to Master, Session Host, and Council, but the Profile Master can revoke it for Session Host, Council, or Raider.\n" ..
    "- Viewing the Loot List and Loot List details is intentionally available to Raiders by default, but edit controls are disabled unless the relevant edit right is granted."
  )

  local tRightsSync = tRightsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRightsSync:SetJustifyH("LEFT")
  tRightsSync:SetText(
    "|cffa87ae84. When changed rights reach other players|r\n" ..
    "- Changing rights outside a raid updates only your local authoritative profile copy immediately.\n" ..
    "- Other players receive the changed rights when they accept a shared profile snapshot/update from a later session or from a manual |cff4adf6bPush Host Repair|r during an active session.\n" ..
    "- If another member has a newer or diverged shared copy, LidCouncil shows the sync/review flow instead of blindly overwriting."
  )

  editor.tutorialBeginnerScroll = tBeginnerScroll
  editor.tutorialBeginnerFrame = tBeginnerFrame
  editor.tutorialBeginnerHeader = tBeginnerHeader
  editor.tutorialBeginnerCards = tBeginnerCards
  editor.tutorialBeginnerIntro = tBeginnerIntro
  editor.tutorialBeginnerNote = tBeginnerNote
  editor.tutorialBeginnerAfter = tBeginnerAfter
  editor.tutorialBeginnerPost = tBeginnerPost
  editor.tutorialPanelsScroll = tPanelsScroll
  editor.tutorialPanelsFrame = tPanelsFrame
  editor.tutorialPanelsHeader = tPanelsHeader
  editor.tutorialPanelsCards = tPanelsCards
  editor.tutorialPanelsIntro = tPanelsIntro
  editor.tutorialPanelsTitle = tPanelsTitle
  editor.tutorialPanelsNote = tPanelsNote
  editor.tutorialPanelsAfter = tPanelsAfter
  editor.tutorialPanelsPost = tPanelsPost
  editor.tutorialProfilesScroll = tProfilesScroll
  editor.tutorialProfilesFrame = tProfilesFrame
  editor.tutorialProfilesHeader = tProfilesHeader
  editor.tutorialProfilesCards = tProfilesCards
  editor.tutorialProfilesIntro = tProfilesIntro
  editor.tutorialProfilesGroupsTitle = tProfilesGroupsTitle
  editor.tutorialProfilesGroupsNote = tProfilesGroupsNote
  editor.tutorialProfilesGroupsAfter = tProfilesGroupsAfter
  editor.tutorialProfilesGroupsPost = tProfilesGroupsPost

  tBeginnerScroll:Hide()
  tProfilesScroll:Hide()
  tCustomScroll:Hide()
  tSimulationScroll:Hide()
  tAddFolderScroll:Hide()
  tGroupsScroll:Hide()
  tWorkflowScroll:Hide()
  tAutoPassScroll:Hide()
  tSyncScroll:Hide()
  tRolesScroll:Hide()
  tRightsScroll:Hide()

  editor.tutorialCustomScroll = tCustomScroll
  editor.tutorialCustomFrame = tCustomFrame
  editor.tutorialCustomHeader = tCustomHeader
  editor.tutorialCustomCards = tCustomCards
  editor.tutorialCustomIntro = tCustomIntro
  editor.tutorialCustomTitle = tCustomTitle
  editor.tutorialCustomNote = tCustomNote
  editor.tutorialCustomAfter = tCustomAfter
  editor.tutorialCustomPost = tCustomPost
  editor.tutorialSimulationScroll = tSimulationScroll
  editor.tutorialSimulationFrame = tSimulationFrame
  editor.tutorialSimulationHeader = tSimulationHeader
  editor.tutorialSimulationCards = tSimulationCards
  editor.tutorialSimulationIntro = tSimulationIntro
  editor.tutorialSimulationNote = tSimulationNote
  editor.tutorialSimulationAfter = tSimulationAfter
  editor.tutorialSimulationPost = tSimulationPost
  editor.tutorialAddFolderScroll = tAddFolderScroll
  editor.tutorialAddFolderFrame = tAddFolderFrame
  editor.tutorialAddFolderHeader = tAddFolderHeader
  editor.tutorialAddFolderCards = tAddFolderCards
  editor.tutorialAddFolderIntro = tAddFolderIntro
  editor.tutorialAddFolderStepTitle = tAddFolderStepTitle
  editor.tutorialAddFolderNote = tAddFolderNote
  editor.tutorialAddFolderImage = tAddFolderImage
  editor.tutorialAddFolderAfter = tAddFolderAfter
  editor.tutorialAddFolderPost = tAddFolderPost
  editor.tutorialAddFolderPostImage = tAddFolderPostImage
  editor.tutorialAddFolderPreviewNote = tAddFolderPreviewNote
  editor.tutorialAddFolderPreviewImage = tAddFolderPreviewImage
  editor.tutorialGroupsScroll = tGroupsScroll
  editor.tutorialGroupsFrame = tGroupsFrame
  editor.tutorialGroupsHeader = tGroupsHeader
  editor.tutorialGroupsCards = tGroupsCards
  editor.tutorialGroupsIntro = tGroupsIntro
  editor.tutorialGroupsNote = tGroupsNote
  editor.tutorialGroupsAfter = tGroupsAfter
  editor.tutorialGroupsPost = tGroupsPost
  editor.tutorialWorkflowScroll = tWorkflowScroll
  editor.tutorialWorkflowFrame = tWorkflowFrame
  editor.tutorialWorkflowHeader = tWorkflowHeader
  editor.tutorialWorkflowCards = tWorkflowCards
  editor.tutorialWorkflowIntro = tWorkflowIntro
  editor.tutorialWorkflowNote = tWorkflowNote
  editor.tutorialWorkflowAfter = tWorkflowAfter
  editor.tutorialWorkflowLive = tWorkflowLive
  editor.tutorialWorkflowPost = tWorkflowPost
  editor.tutorialAutoPassScroll = tAutoPassScroll
  editor.tutorialAutoPassFrame = tAutoPassFrame
  editor.tutorialAutoPassHeader = tAutoPassHeader
  editor.tutorialAutoPassCards = tAutoPassCards
  editor.tutorialAutoPassIntro = tAutoPassIntro
  editor.tutorialAutoPassBasics = tAutoPassBasics
  editor.tutorialAutoPassDefaults = tAutoPassDefaults
  editor.tutorialAutoPassControl = tAutoPassControl
  editor.tutorialAutoPassExamples = tAutoPassExamples
  editor.tutorialSyncScroll = tSyncScroll
  editor.tutorialSyncFrame = tSyncFrame
  editor.tutorialSyncHeader = tSyncHeader
  editor.tutorialSyncCards = tSyncCards
  editor.tutorialSyncIntro = tSyncIntro
  editor.tutorialSyncTriggers = tSyncTriggers
  editor.tutorialSyncPaths = tSyncPaths
  editor.tutorialSyncGaps = tSyncGaps
  editor.tutorialSyncNotes = tSyncNotes
  editor.tutorialRolesScroll = tRolesScroll
  editor.tutorialRolesFrame = tRolesFrame
  editor.tutorialRolesHeader = tRolesHeader
  editor.tutorialRolesCards = tRolesCards
  editor.tutorialRolesIntro = tRolesIntro
  editor.tutorialRolesBasics = tRolesBasics
  editor.tutorialRolesMinimum = tRolesMinimum
  editor.tutorialRolesDefaults = tRolesDefaults
  editor.tutorialRolesPractical = tRolesPractical
  editor.tutorialRightsScroll = tRightsScroll
  editor.tutorialRightsFrame = tRightsFrame
  editor.tutorialRightsHeader = tRightsHeader
  editor.tutorialRightsCards = tRightsCards
  editor.tutorialRightsIntro = tRightsIntro
  editor.tutorialRightsProfile = tRightsProfile
  editor.tutorialRightsSession = tRightsSession
  editor.tutorialRightsLootList = tRightsLootList
  editor.tutorialRightsSync = tRightsSync

  local tutorialBanner = CreateFrame("Frame", nil, tutorialRightBg)
  tutorialBanner:SetPoint("TOPLEFT", 6, -6)
  tutorialBanner:SetPoint("BOTTOMRIGHT", -6, 6)
  local bannerTex = tutorialBanner:CreateTexture(nil, "ARTWORK")
  bannerTex:SetPoint("TOPLEFT", 18, -18)
  RegisterTutorialTexture(editor, bannerTex, "Interface\\AddOns\\LidCouncil\\Media\\LidCouncil\\Tutorials\\CBG_1_Custom_Banner.tga")
  bannerTex:SetTexCoord(0, 1, 0, 1)
  local BANNER_W, BANNER_H = 3173, 1117
  local function LayoutBanner()
    local avail = (tProfilesFrame and tProfilesFrame:GetWidth()) or (tutorialRightBg:GetWidth() - 62)
    if not avail or avail < 1 then avail = 1 end
    local leftPad = 18
    local cardGap = 20
    local rowW = avail - leftPad
    local cardW = math.max(1, math.floor((rowW - cardGap) / 2))
    local headerW = cardW * 2 + cardGap
    local scale = headerW / BANNER_W
    if scale > 1 then scale = 1 end
    local headerH = math.floor((BANNER_H * scale) + 0.5)
    bannerTex:SetSize(headerW, headerH)
  end
  tutorialBanner:SetScript("OnShow", LayoutBanner)
  tutorialRightBg:HookScript("OnSizeChanged", LayoutBanner)
  editor.tutorialBanner = tutorialBanner

  function editor:ShowTutorialSection(which)
    if self.RefreshTutorialAccentMarkup then
      self:RefreshTutorialAccentMarkup()
    end
    self.tutorialSection = which == "Groups" and "Profiles" or (which or "Beginner")
    local function ResetScrollTop(scroll)
      if scroll and scroll.SetVerticalScroll then
        scroll:SetVerticalScroll(0)
      end
    end
    local function HideTutorialScrolls()
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialBeginnerScroll then self.tutorialBeginnerScroll:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialSimulationScroll then self.tutorialSimulationScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRolesScroll then self.tutorialRolesScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
    end
    HideTutorialScrolls()
    if self.tutorialSection == "Beginner" then
      if self.tutorialBeginnerScroll then self.tutorialBeginnerScroll:Show() end
      ResetScrollTop(self.tutorialBeginnerScroll)
    elseif self.tutorialSection == "Profiles" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Show() end
      ResetScrollTop(self.tutorialProfilesScroll)
    elseif self.tutorialSection == "Custom" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Show() end
      ResetScrollTop(self.tutorialCustomScroll)
    elseif self.tutorialSection == "RunSimulation" then
      if self.tutorialSimulationScroll then self.tutorialSimulationScroll:Show() end
      ResetScrollTop(self.tutorialSimulationScroll)
    elseif self.tutorialSection == "AddFolder" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Show() end
      ResetScrollTop(self.tutorialAddFolderScroll)
    elseif self.tutorialSection == "Groups" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Show() end
      ResetScrollTop(self.tutorialGroupsScroll)
    elseif self.tutorialSection == "Workflow" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Show() end
      ResetScrollTop(self.tutorialWorkflowScroll)
    elseif self.tutorialSection == "AutoPass" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Show() end
      ResetScrollTop(self.tutorialAutoPassScroll)
    elseif self.tutorialSection == "SyncWorkflow" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Show() end
      ResetScrollTop(self.tutorialSyncScroll)
    elseif self.tutorialSection == "Roles" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialRolesScroll then self.tutorialRolesScroll:Show() end
      ResetScrollTop(self.tutorialRolesScroll)
    elseif self.tutorialSection == "Rights" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Show() end
      ResetScrollTop(self.tutorialRightsScroll)
    elseif self.tutorialSection == "Panels" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Show() end
      ResetScrollTop(self.tutorialPanelsScroll)
    else
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialBanner then self.tutorialBanner:Show() end
    end
    if self.LayoutTutorial then self:LayoutTutorial() end
  end

  function editor:LayoutTutorial()
    if self.RefreshTutorialAccentMarkup then
      self:RefreshTutorialAccentMarkup()
    end
    local function LayoutSection(frame, header, cards, scroll, note, noteImage, noteAfter, postText, postImage, previewNote, previewImage, noteAfterCards, introText, stepTitle)
      if not frame or not cards then return 400 end
      local avail = frame:GetWidth()
      if not avail or avail < 1 then return 400 end

      local leftPad = 18
      local cardGap = 20
      local rowW = avail - leftPad
      local cardW = math.max(1, math.floor((rowW - cardGap) / 2))
      local cardH = math.floor(cardW * 0.75)
      local headerW = cardW * 2 + cardGap
      local headerH = 0

      if header then
        headerH = math.floor(cardH * 0.6)
        header:SetSize(headerW, headerH)
        header:ClearAllPoints()
        header:SetPoint("TOPLEFT", leftPad, -18)
      end
      local y = -18
      if header then
        y = y - headerH - 20
      end

      local function LayoutNoteBlocks()
        if note then
          note:ClearAllPoints()
          note:SetPoint("TOPLEFT", leftPad, y)
          note:SetWidth(headerW)
          local noteH = note.GetStringHeight and note:GetStringHeight() or 0
          y = y - noteH - 14
        end
        if noteImage then
          local imgW = math.min(headerW, 520)
          local imgH = math.floor(imgW * 0.55)
          noteImage:ClearAllPoints()
          noteImage:SetPoint("TOPLEFT", leftPad, y)
          noteImage:SetSize(imgW, imgH)
          y = y - imgH - 20
        end
        if noteAfter then
          noteAfter:ClearAllPoints()
          noteAfter:SetPoint("TOPLEFT", leftPad, y)
          noteAfter:SetWidth(headerW)
          local afterH = noteAfter.GetStringHeight and noteAfter:GetStringHeight() or 0
          y = y - afterH - 20
        end
      end

      local function LayoutCards()
        local idx = 1
        local total = (cards and #cards) or 0
        local rows = math.ceil(total / 2)
        for _ = 1, rows do
          local placed = 0
          for col = 1, 2 do
            local card = cards[idx]
            if not card then break end
            card:SetSize(cardW, cardH)
            local x = leftPad + (col - 1) * (cardW + cardGap)
            card:ClearAllPoints()
            card:SetPoint("TOPLEFT", x, y)
            idx = idx + 1
            placed = placed + 1
          end
          if placed > 0 then
            y = y - cardH - 20
          end
        end
      end

      if introText then
        introText:ClearAllPoints()
        introText:SetPoint("TOPLEFT", leftPad, y)
        introText:SetWidth(headerW)
        local introH = introText.GetStringHeight and introText:GetStringHeight() or 0
        y = y - introH - 14
      end

      if noteAfterCards then
        LayoutCards()
        if stepTitle then
          stepTitle:ClearAllPoints()
          stepTitle:SetPoint("TOPLEFT", leftPad, y)
          stepTitle:SetWidth(headerW)
          local stepTitleH = stepTitle.GetStringHeight and stepTitle:GetStringHeight() or 0
          y = y - stepTitleH - 18
        end
        LayoutNoteBlocks()
      else
        LayoutNoteBlocks()
        LayoutCards()
      end

      if previewNote then
        previewNote:ClearAllPoints()
        previewNote:SetPoint("TOPLEFT", leftPad, y)
        if type(previewNote.LayoutForWidth) == "function" then
          previewNote:LayoutForWidth(headerW)
        elseif previewNote.SetWidth then
          previewNote:SetWidth(headerW)
        end
        local previewH = previewNote.GetStringHeight and previewNote:GetStringHeight()
          or (previewNote.GetHeight and previewNote:GetHeight())
          or 0
        y = y - previewH - 14
      end
      if previewImage then
        local imgW = math.min(headerW, 520)
        local imgH = math.floor(imgW * 0.55)
        previewImage:ClearAllPoints()
        previewImage:SetPoint("TOPLEFT", leftPad, y)
        previewImage:SetSize(imgW, imgH)
        y = y - imgH - 20
      end
      if postText then
        postText:ClearAllPoints()
        postText:SetPoint("TOPLEFT", leftPad, y)
        if type(postText.LayoutForWidth) == "function" then
          postText:LayoutForWidth(headerW)
        elseif postText.SetWidth then
          postText:SetWidth(headerW)
        end
        local postH = postText.GetStringHeight and postText:GetStringHeight()
          or (postText.GetHeight and postText:GetHeight())
          or 0
        y = y - postH - 14
      end
      if postImage then
        local imgW = math.min(headerW, 520)
        local imgH = math.floor(imgW * 0.55)
        postImage:ClearAllPoints()
        postImage:SetPoint("TOPLEFT", leftPad, y)
        postImage:SetSize(imgW, imgH)
        y = y - imgH - 20
      end

      local contentHeight = math.max(400, (-y) + 120)
      frame:SetHeight(contentHeight)
      if scroll and scroll.UpdateScrollChildRect then scroll:UpdateScrollChildRect() end
      return contentHeight
    end

    local which = self.tutorialSection or "Beginner"
    if which == "Beginner" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialBeginnerFrame,
        self.tutorialBeginnerHeader,
        self.tutorialBeginnerCards,
        self.tutorialBeginnerScroll,
        self.tutorialBeginnerNote,
        nil,
        self.tutorialBeginnerAfter,
        self.tutorialBeginnerPost,
        nil,
        nil,
        nil,
        false,
        self.tutorialBeginnerIntro,
        nil
      )
    elseif which == "Profiles" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialProfilesFrame,
        self.tutorialProfilesHeader,
        self.tutorialProfilesCards,
        self.tutorialProfilesScroll,
        self.tutorialProfilesGroupsNote,
        nil,
        self.tutorialProfilesGroupsAfter,
        self.tutorialProfilesGroupsPost,
        nil,
        nil,
        nil,
        true,
        self.tutorialProfilesIntro,
        self.tutorialProfilesGroupsTitle
      )
    elseif which == "Custom" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialCustomFrame,
        self.tutorialCustomHeader,
        self.tutorialCustomCards,
        self.tutorialCustomScroll,
        self.tutorialCustomNote,
        nil,
        self.tutorialCustomAfter,
        self.tutorialCustomPost,
        nil,
        nil,
        nil,
        true,
        self.tutorialCustomIntro,
        self.tutorialCustomTitle
      )
    elseif which == "RunSimulation" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialSimulationFrame,
        self.tutorialSimulationHeader,
        self.tutorialSimulationCards,
        self.tutorialSimulationScroll,
        self.tutorialSimulationNote,
        nil,
        self.tutorialSimulationAfter,
        self.tutorialSimulationPost,
        nil,
        nil,
        nil,
        false,
        self.tutorialSimulationIntro,
        nil
      )
    elseif which == "AddFolder" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialAddFolderFrame,
        self.tutorialAddFolderHeader,
        self.tutorialAddFolderCards,
        self.tutorialAddFolderScroll,
        self.tutorialAddFolderNote,
        nil,
        self.tutorialAddFolderAfter,
        self.tutorialAddFolderPost,
        nil,
        nil,
        nil,
        true,
        self.tutorialAddFolderIntro,
        self.tutorialAddFolderStepTitle
      )
    elseif which == "Groups" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialGroupsFrame,
        self.tutorialGroupsHeader,
        self.tutorialGroupsCards,
        self.tutorialGroupsScroll,
        self.tutorialGroupsNote,
        nil,
        self.tutorialGroupsAfter,
        self.tutorialGroupsPost,
        nil,
        nil,
        nil,
        false,
        self.tutorialGroupsIntro,
        nil
      )
    elseif which == "Workflow" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialWorkflowFrame,
        self.tutorialWorkflowHeader,
        self.tutorialWorkflowCards,
        self.tutorialWorkflowScroll,
        self.tutorialWorkflowNote,
        nil,
        self.tutorialWorkflowAfter,
        self.tutorialWorkflowPost,
        nil,
        self.tutorialWorkflowLive,
        nil,
        false,
        self.tutorialWorkflowIntro,
        nil
      )
    elseif which == "AutoPass" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialAutoPassFrame,
        self.tutorialAutoPassHeader,
        self.tutorialAutoPassCards,
        self.tutorialAutoPassScroll,
        self.tutorialAutoPassBasics,
        nil,
        self.tutorialAutoPassDefaults,
        self.tutorialAutoPassExamples,
        nil,
        self.tutorialAutoPassControl,
        nil,
        false,
        self.tutorialAutoPassIntro,
        nil
      )
    elseif which == "SyncWorkflow" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialSyncFrame,
        self.tutorialSyncHeader,
        self.tutorialSyncCards,
        self.tutorialSyncScroll,
        self.tutorialSyncTriggers,
        nil,
        self.tutorialSyncPaths,
        self.tutorialSyncNotes,
        nil,
        self.tutorialSyncGaps,
        nil,
        false,
        self.tutorialSyncIntro,
        nil
      )
    elseif which == "Roles" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialRolesFrame,
        self.tutorialRolesHeader,
        self.tutorialRolesCards,
        self.tutorialRolesScroll,
        self.tutorialRolesBasics,
        nil,
        self.tutorialRolesMinimum,
        self.tutorialRolesPractical,
        nil,
        self.tutorialRolesDefaults,
        nil,
        false,
        self.tutorialRolesIntro,
        nil
      )
    elseif which == "Rights" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialRightsFrame,
        self.tutorialRightsHeader,
        self.tutorialRightsCards,
        self.tutorialRightsScroll,
        self.tutorialRightsProfile,
        nil,
        self.tutorialRightsSession,
        self.tutorialRightsSync,
        nil,
        self.tutorialRightsLootList,
        nil,
        false,
        self.tutorialRightsIntro,
        nil
      )
    elseif which == "Panels" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialPanelsFrame,
        self.tutorialPanelsHeader,
        self.tutorialPanelsCards,
        self.tutorialPanelsScroll,
        self.tutorialPanelsNote,
        nil,
        self.tutorialPanelsAfter,
        self.tutorialPanelsPost,
        nil,
        nil,
        nil,
        true,
        self.tutorialPanelsIntro,
        self.tutorialPanelsTitle
      )
    else
      tutorialPage.__contentHeight = (self.tutorialBanner and self.tutorialBanner:GetHeight()) or 600
    end
  end

  editor:ShowTutorialSection("Beginner")
  tutorialPage.__contentHeight = 1200
end

INTERNAL_API.LIDCOUNCIL_BuildTutorialPage_Impl = BuildTutorialPage
