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

INTERNAL_API.LIDCOUNCIL_CreateLootSessionSyncHelpers_Impl = function(deps)
  deps = type(deps) == "table" and deps or {}
  local GetEditorThemePalette = type(INTERNAL_API.LIDCOUNCIL_GetEditorThemePalette_Impl) == "function"
    and INTERNAL_API.LIDCOUNCIL_GetEditorThemePalette_Impl
    or nil

  local ApplyFrameSkin = deps.ApplyFrameSkin
  local RootDB = deps.RootDB
  local TrimText = deps.TrimText
  local GetSessionClassColor = deps.GetSessionClassColor
  local ResolveOptionalApiFunction = deps.ResolveOptionalApiFunction
  local FlowLayoutControls = deps.FlowLayoutControls
  local ClampLayoutValue = deps.ClampLayoutValue
  local FormatDateTime = deps.FormatDateTime
  local ApplyTrafficLight = deps.ApplyTrafficLight

  local function IsTutorialModeEnabled()
    local root = type(RootDB) == "function" and RootDB() or nil
    local ui = type(root) == "table" and type(root.ui) == "table" and root.ui or nil
    return type(ui) == "table" and ui.tutorialMode == true
  end

  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 BuildProfileSyncCandidateLabel(candidate)
    if type(candidate) ~= "table" then
      return "(select joined member)"
    end
    local memberLabel = tostring(candidate.memberKey or "Unknown")
    local comparison = TrimText(candidate.comparison)
    local bootstrapState = string.lower(TrimText(candidate.bootstrapState))
    local readinessSuffix = bootstrapState == "ready" and " | roll-ready" or ""
    local preciseSuffix = candidate.preciseDetailLoaded and " | precise scope ready" or ""
    if candidate.patchPushReady then
      preciseSuffix = preciseSuffix .. " | patch ready"
    end
    if comparison == "remote_newer" then
      return memberLabel .. " | member newer: " .. tostring(candidate.remoteDomainText or "shared data") .. readinessSuffix .. preciseSuffix
    elseif comparison == "diverged" then
      return memberLabel .. " | conflict: " .. tostring(candidate.remoteDomainText or "shared data") .. readinessSuffix .. preciseSuffix
    elseif comparison == "different_lineage" then
      return memberLabel .. " | different profile lineage"
    elseif comparison == "local_newer" then
      return memberLabel .. " | host newer: " .. tostring(candidate.localDomainText or "shared data") .. readinessSuffix .. preciseSuffix
    elseif comparison == "same" then
      return memberLabel .. " | same version" .. readinessSuffix
    end
    return memberLabel .. " | status unknown"
  end

  local function GetProfileSyncComparisonLabel(comparison)
    local resolved = TrimText(comparison)
    if resolved == "remote_newer" then
      return "Member newer"
    elseif resolved == "diverged" then
      return "Conflict"
    elseif resolved == "local_newer" then
      return "Your copy newer"
    elseif resolved == "same" then
      return "Aligned"
    elseif resolved == "different_lineage" then
      return "Different lineage"
    end
    return "Unknown"
  end

  local function GetProfileSyncComparisonColor(comparison)
    local resolved = TrimText(comparison)
    if resolved == "remote_newer" then
      return 1.00, 0.82, 0.32, 1.00
    elseif resolved == "diverged" then
      return 0.95, 0.32, 0.26, 1.00
    elseif resolved == "local_newer" then
      return 0.30, 0.90, 0.36, 1.00
    elseif resolved == "same" then
      return 0.82, 0.82, 0.86, 1.00
    elseif resolved == "different_lineage" then
      return 0.94, 0.62, 0.28, 1.00
    end
    return 0.82, 0.82, 0.86, 1.00
  end

  local function HasProfileSyncDomain(domains, domain)
    local needle = TrimText(domain)
    if needle == "" then
      return false
    end
    for _, value in ipairs(type(domains) == "table" and domains or {}) do
      if TrimText(value) == needle then
        return true
      end
    end
    return false
  end

  local function GetProfileSyncDomainState(candidate, domain)
    candidate = type(candidate) == "table" and candidate or {}
    if TrimText(candidate.comparison) == "different_lineage" then
      return "lineage"
    end
    local memberNewer = HasProfileSyncDomain(candidate.remoteNewerDomains, domain)
    local hostNewer = HasProfileSyncDomain(candidate.localNewerDomains, domain)
    if memberNewer and hostNewer then
      return "conflict"
    elseif memberNewer then
      return "member_newer"
    elseif hostNewer then
      return "host_newer"
    end
    return "same"
  end

  local function GetProfileSyncDomainStateLabel(state)
    local resolved = TrimText(state)
    if resolved == "same" then
      return "Identical"
    elseif resolved == "member_newer" then
      return "Member newer"
    elseif resolved == "host_newer" then
      return "Host newer"
    elseif resolved == "conflict" then
      return "Both changed"
    elseif resolved == "lineage" then
      return "Lineage"
    end
    return "Unknown"
  end

  local function GetProfileSyncDomainStateColor(state)
    local resolved = TrimText(state)
    if resolved == "same" then
      return 0.30, 0.90, 0.36, 1.00
    elseif resolved == "member_newer" then
      return 1.00, 0.82, 0.32, 1.00
    elseif resolved == "host_newer" then
      return 0.38, 0.72, 1.00, 1.00
    elseif resolved == "conflict" then
      return 0.95, 0.32, 0.26, 1.00
    elseif resolved == "lineage" then
      return 0.94, 0.62, 0.28, 1.00
    end
    return 0.82, 0.82, 0.86, 1.00
  end

  local function GetProfileSyncDomainLabel(text)
    local resolved = TrimText(text)
    return resolved ~= "" and resolved or "-"
  end

  local function GetProfileSyncBootstrapLabel(state)
    local resolved = string.lower(TrimText(state))
    if resolved == "ready" then
      return "Roll-Ready"
    elseif resolved == "pending" then
      return "Bootstrap Pending"
    elseif resolved == "review" then
      return "Bootstrap Needs Review"
    elseif resolved == "lineage" then
      return "Different Lineage"
    end
    return "Bootstrap Unknown"
  end

  local function CanPullProfileSyncCandidate(candidate)
    candidate = type(candidate) == "table" and candidate or {}
    local comparison = TrimText(candidate.comparison)
    if comparison == "remote_newer" then
      return true
    end
    if comparison ~= "diverged" then
      return false
    end
    for _, domain in ipairs(type(candidate.remoteNewerDomains) == "table" and candidate.remoteNewerDomains or {}) do
      if not HasProfileSyncDomain(candidate.localNewerDomains, domain) then
        return true
      end
    end
    return false
  end

  local function GetProfileSyncRequestButtonText(candidate)
    if type(candidate) ~= "table" then
      return "Select Member"
    end
    local comparison = TrimText(candidate.comparison)
    if comparison == "remote_newer" then
      return candidate.precisePullReady and "Pull Precise Repair" or "Pull Member Repair"
    elseif comparison == "diverged" then
      return CanPullProfileSyncCandidate(candidate) and "Pull Selected Repair" or "Preview First"
    elseif comparison == "same" then
      return "Already synced - no new data"
    elseif comparison == "local_newer" then
      return "Host copy newer - no pull"
    elseif comparison == "different_lineage" then
      return "Different lineage"
    end
    return "No newer data"
  end

  local function BuildSelectedProfileSyncHint(candidate, isHost)
    if type(candidate) ~= "table" then
      return "Select one joined member to inspect the recommended repair path."
    end
    local comparison = TrimText(candidate.comparison)
    local bootstrapLine = "Bootstrap state: " .. GetProfileSyncBootstrapLabel(candidate.bootstrapState) .. ". "
    if comparison == "same" then
      return bootstrapLine .. "No repair is needed for this joined member."
    elseif comparison == "local_newer" then
      if candidate.patchPushReady then
        return bootstrapLine .. "Host newer. Push Host Repair can send nested patch transfers for these loaded scopes: " .. tostring(candidate.patchPushText)
      end
      if candidate.precisePushReady then
        return bootstrapLine .. "Host newer. Push Host Repair can send only these loaded scopes: " .. tostring(candidate.precisePushText)
      end
      if TrimText(candidate.patchPushText) ~= "" then
        return bootstrapLine .. "Host newer. Patch-capable leaf scopes were found, but nested patch manifests are still loading. Push Host Repair will fall back to precise snapshots until they arrive."
      end
      return bootstrapLine .. "Host newer. Push Host Repair will use domain-level repair. Use Check Differences first to load smaller leaf repair scopes."
    elseif comparison == "remote_newer" then
      if TrimText(candidate.patchPullText) ~= "" then
        return bootstrapLine .. "Member newer. Pull Member Repair can use nested patch transfers for patch-capable scopes and precise snapshots for the rest: " .. tostring(candidate.patchPullText)
      end
      if candidate.precisePullReady then
        return bootstrapLine .. "Member newer. Pull Member Repair can request only these loaded scopes: " .. tostring(candidate.precisePullText)
      end
      return bootstrapLine .. "Member newer. Use Check Differences first if you want a smaller pull scope before applying their repair."
    elseif comparison == "diverged" then
      if TrimText(candidate.patchPreviewText) ~= "" then
        return bootstrapLine .. "Conflict. Preview Member Repair is recommended first. Patch-capable preview scopes: " .. tostring(candidate.patchPreviewText)
      end
      if candidate.precisePreviewReady then
        return bootstrapLine .. "Conflict. Preview Member Repair is recommended first. Loaded differing scopes: " .. tostring(candidate.precisePreviewText)
      end
      return bootstrapLine .. "Conflict. Preview Member Repair is recommended first. Use Check Differences to load the smaller differing scopes."
    elseif comparison == "different_lineage" then
      return bootstrapLine .. "Only a full snapshot fallback is possible; preview first if you need to inspect it."
    end
    return bootstrapLine .. (isHost and "Check Differences can request detailed leaf checksum scopes without replacing profile data." or "Waiting for the host to decide the repair path.")
  end

  local function FindProfileSyncCandidateByKey(candidates, memberKey)
    local resolvedKey = TrimText(memberKey)
    if resolvedKey == "" then
      return nil
    end
    for _, candidate in ipairs(candidates or {}) do
      if TrimText(candidate.memberKey) == resolvedKey then
        return candidate
      end
    end
    return nil
  end

  local function GetProfileSyncStatusColor(severity)
    local resolvedSeverity = string.lower(TrimText(severity))
    if resolvedSeverity == "success" then
      return 0.30, 0.90, 0.36, 1.00
    elseif resolvedSeverity == "warn" then
      return 1.00, 0.82, 0.32, 1.00
    elseif resolvedSeverity == "error" then
      return 0.95, 0.32, 0.26, 1.00
    end
    return 0.82, 0.82, 0.86, 1.00
  end

  local OpenProfileSyncDetailPopup
  local CreateSyncDashboardHelpers = INTERNAL_API.LIDCOUNCIL_CreateLootSessionSyncDashboardHelpers_Impl
  if type(CreateSyncDashboardHelpers) ~= "function" then
    error("LidCouncil missing loot session sync dashboard helpers.")
  end
  local CreateSyncScopeHelpers = INTERNAL_API.LIDCOUNCIL_CreateLootSessionSyncScopeHelpers_Impl
  if type(CreateSyncScopeHelpers) ~= "function" then
    error("LidCouncil missing loot session sync scope helpers.")
  end
  local CreateSyncConflictResolverHelpers = INTERNAL_API.LIDCOUNCIL_CreateLootSessionSyncConflictResolverHelpers_Impl
  if type(CreateSyncConflictResolverHelpers) ~= "function" then
    error("LidCouncil missing loot session sync conflict resolver helpers.")
  end
  local CreateSyncHistoryHelpers = INTERNAL_API.LIDCOUNCIL_CreateLootSessionSyncHistoryHelpers_Impl
  if type(CreateSyncHistoryHelpers) ~= "function" then
    error("LidCouncil missing loot session sync history helpers.")
  end
  local syncDashboardHelpers = CreateSyncDashboardHelpers({
    ApplyFrameSkin = ApplyFrameSkin,
    TrimText = TrimText,
    FormatDateTime = FormatDateTime,
    GetSessionClassColor = GetSessionClassColor,
    OpenProfileSyncDetail = function(editor, candidate)
      if type(OpenProfileSyncDetailPopup) == "function" then
        OpenProfileSyncDetailPopup(editor, candidate)
      end
    end,
  })
  local RefreshSyncDashboard = syncDashboardHelpers.RefreshSyncDashboard
  local LayoutSyncDashboard = syncDashboardHelpers.LayoutSyncDashboard
  local syncScopeHelpers = CreateSyncScopeHelpers({
    ApplyFrameSkin = ApplyFrameSkin,
    TrimText = TrimText,
    GetSubdomainLabel = function(subdomain)
      local fn = ResolveOptionalApiFunction("LIDCOUNCIL_GetLootProfileSubdomainLabel")
      return type(fn) == "function" and fn(subdomain) or subdomain
    end,
    GetSubdomainParentDomain = function(subdomain)
      local fn = ResolveOptionalApiFunction("LIDCOUNCIL_GetLootProfileSubdomainParentDomain")
      return type(fn) == "function" and fn(subdomain) or ""
    end,
  })
  local RefreshSyncScopePicker = syncScopeHelpers.RefreshSyncScopePicker
  local LayoutSyncScopePicker = syncScopeHelpers.LayoutSyncScopePicker
  local syncConflictResolverHelpers = CreateSyncConflictResolverHelpers({
    ApplyFrameSkin = ApplyFrameSkin,
    TrimText = TrimText,
  })
  local RefreshSyncConflictResolver = syncConflictResolverHelpers.RefreshSyncConflictResolver
  local BuildSelectedConflictResolution = syncConflictResolverHelpers.BuildSelectedConflictResolution
  local syncHistoryHelpers = CreateSyncHistoryHelpers({
    ApplyFrameSkin = ApplyFrameSkin,
    TrimText = TrimText,
    FormatDateTime = FormatDateTime,
    ResolveOptionalApiFunction = ResolveOptionalApiFunction,
  })
  local RefreshSyncHistoryPanel = syncHistoryHelpers.RefreshSyncHistoryPanel
  local LayoutSyncHistoryPanel = syncHistoryHelpers.LayoutSyncHistoryPanel

  local function AppendDomainDiffLines(lines, diff, key)
    local domain = type(diff) == "table" and type(diff[key]) == "table" and diff[key] or nil
    if not domain then
      return
    end
    lines[#lines + 1] = tostring(domain.label or key) .. ": " .. tostring(tonumber(domain.count) or 0) .. " difference(s)"
    for _, text in ipairs(domain.lines or {}) do
      lines[#lines + 1] = "  - " .. tostring(text)
    end
  end

  local function ColorMarkup(text, hexColor)
    local resolvedText = tostring(text or "")
    local resolvedColor = TrimText(hexColor)
    if resolvedColor == "" then
      return resolvedText
    end
    return "|cff" .. resolvedColor .. resolvedText .. "|r"
  end

  local function FormatComparedChecksum(checksum, otherChecksum)
    local resolved = TrimText(checksum)
    local other = TrimText(otherChecksum)
    local label = GetProfileSyncDomainLabel(resolved)
    if resolved == "" or other == "" then
      return ColorMarkup(label, "b8b8c2")
    end
    if resolved == other then
      return ColorMarkup(label, "4de66b")
    end
    return ColorMarkup(label, "ff5145")
  end

  local function BuildProfileSyncDetailText(candidate, syncStatus)
    candidate = type(candidate) == "table" and candidate or {}
    syncStatus = type(syncStatus) == "table" and syncStatus or {}
    local memberKey = TrimText(candidate.memberKey)
    local getRevisionSummary = ResolveOptionalApiFunction("LIDCOUNCIL_GetLootProfileRevisionSummary")
    local localSummary = type(getRevisionSummary) == "function" and getRevisionSummary(syncStatus.profileName) or nil
    local function GetSummaryRevision(summary, key)
      return math.max(0, tonumber(type(summary) == "table" and summary[key]) or 0)
    end
    local function GetSummaryChecksum(summary, key)
      return GetProfileSyncDomainLabel(type(summary) == "table" and summary[key] or "")
    end
    local function FirstDomainText(primary, fallback)
      local resolved = TrimText(primary)
      if resolved ~= "" then
        return resolved
      end
      return TrimText(fallback)
    end
    local localSettingsChecksum = TrimText(type(localSummary) == "table" and localSummary.settingsChecksum or "")
    local localPermissionsChecksum = TrimText(type(localSummary) == "table" and localSummary.permissionsChecksum or "")
    local localLootChecksum = TrimText(type(localSummary) == "table" and localSummary.lootChecksum or "")
    local localSessionChecksum = TrimText(type(localSummary) == "table" and localSummary.sessionChecksum or "")
    local memberSettingsChecksum = TrimText(candidate.settingsChecksum)
    local memberPermissionsChecksum = TrimText(candidate.permissionsChecksum)
    local memberLootChecksum = TrimText(FirstDomainText(candidate.lootChecksum, candidate.operationalChecksum))
    local memberSessionChecksum = TrimText(FirstDomainText(candidate.sessionChecksum, candidate.operationalChecksum))
    local lines = {
      "Member: " .. (memberKey ~= "" and memberKey or "Unknown"),
      "State: " .. GetProfileSyncComparisonLabel(candidate.comparison),
      "Bootstrap: " .. GetProfileSyncBootstrapLabel(candidate.bootstrapState),
      "Member newer: " .. GetProfileSyncDomainLabel(candidate.remoteDomainText),
      "Host newer: " .. GetProfileSyncDomainLabel(candidate.localDomainText),
      "Last profile timestamp: " .. ((tonumber(candidate.updatedAt) or 0) > 0 and FormatDateTime(candidate.updatedAt) or "-"),
      "",
      "Your local copy:",
      "Settings: r" .. tostring(GetSummaryRevision(localSummary, "settings")) .. " | " .. FormatComparedChecksum(localSettingsChecksum, memberSettingsChecksum),
      "Permissions: r" .. tostring(GetSummaryRevision(localSummary, "permissions")) .. " | " .. FormatComparedChecksum(localPermissionsChecksum, memberPermissionsChecksum),
      "Loot Data: r" .. tostring(GetSummaryRevision(localSummary, "loot")) .. " | " .. FormatComparedChecksum(localLootChecksum, memberLootChecksum),
      "Session Data: r" .. tostring(GetSummaryRevision(localSummary, "session")) .. " | " .. FormatComparedChecksum(localSessionChecksum, memberSessionChecksum),
      "",
      "Selected member report:",
      "Settings: r" .. tostring(candidate.settings or 0) .. " | " .. FormatComparedChecksum(memberSettingsChecksum, localSettingsChecksum),
      "Permissions: r" .. tostring(candidate.permissions or 0) .. " | " .. FormatComparedChecksum(memberPermissionsChecksum, localPermissionsChecksum),
      "Loot Data: r" .. tostring(candidate.loot or candidate.operational or 0) .. " | " .. FormatComparedChecksum(memberLootChecksum, localLootChecksum),
      "Session Data: r" .. tostring(candidate.session or candidate.operational or 0) .. " | " .. FormatComparedChecksum(memberSessionChecksum, localSessionChecksum),
    }

    if candidate.preciseDetailLoaded then
      lines[#lines + 1] = ""
      lines[#lines + 1] = "Loaded precise leaf scopes:"
      if TrimText(candidate.precisePushText) ~= "" then
        lines[#lines + 1] = "Host repair scope: " .. tostring(candidate.precisePushText)
      end
      if TrimText(candidate.precisePullText) ~= "" then
        lines[#lines + 1] = "Member repair scope: " .. tostring(candidate.precisePullText)
      end
      if TrimText(candidate.precisePreviewText) ~= "" and TrimText(candidate.precisePreviewText) ~= TrimText(candidate.precisePullText)
          and TrimText(candidate.precisePreviewText) ~= TrimText(candidate.precisePushText) then
        lines[#lines + 1] = "Preview scope: " .. tostring(candidate.precisePreviewText)
      end
    end

    local detailDiff = type(syncStatus.detailDiff) == "table" and syncStatus.detailDiff or nil
    if type(detailDiff) == "table" and TrimText(detailDiff.memberKey) == memberKey then
      lines[#lines + 1] = ""
      lines[#lines + 1] = "Detailed snapshot diff:"
      if detailDiff.hasChanges == false then
        lines[#lines + 1] = "No exact differences were found in the compared sync domains."
      else
        local shownDomain = false
        for _, domain in ipairs(type(detailDiff.scopeDomains) == "table" and detailDiff.scopeDomains or { "settings", "permissions", "loot", "session" }) do
          AppendDomainDiffLines(lines, detailDiff, domain)
          shownDomain = true
        end
        if not shownDomain then
          AppendDomainDiffLines(lines, detailDiff, "settings")
          AppendDomainDiffLines(lines, detailDiff, "permissions")
          AppendDomainDiffLines(lines, detailDiff, "loot")
          AppendDomainDiffLines(lines, detailDiff, "session")
        end
      end
    else
      lines[#lines + 1] = ""
      lines[#lines + 1] = "Detailed snapshot diff is not loaded yet."
      lines[#lines + 1] = "Use Preview Member Repair to request this member's differing shared-profile data for inspection without applying it."
    end

    local detailConflict = type(syncStatus.detailConflict) == "table" and syncStatus.detailConflict or nil
    if type(detailConflict) == "table" and TrimText(detailConflict.memberKey) == memberKey and tonumber(detailConflict.totalRows) and tonumber(detailConflict.totalRows) > 0 then
      lines[#lines + 1] = ""
      lines[#lines + 1] = "Nested preview rows:"
      lines[#lines + 1] = tostring(detailConflict.totalRows) .. " member-selectable nested differences are loaded below."
      lines[#lines + 1] = "Check only the rows you want to take from the member before applying them to the host copy."
    end

    return table.concat(lines, "\n")
  end

  local function RefreshProfileSyncDetailPopup(editor, frame, candidate, syncStatus)
    frame = frame or (editor and editor.lootSessionSyncDetailPopup)
    candidate = type(candidate) == "table" and candidate or type(frame) == "table" and frame.__candidate or nil
    syncStatus = type(syncStatus) == "table" and syncStatus or {}
    if not frame or type(candidate) ~= "table" then
      return
    end

    local detailConflict = type(syncStatus.detailConflict) == "table"
      and TrimText(syncStatus.detailConflict.memberKey) == TrimText(candidate.memberKey)
      and syncStatus.detailConflict
      or nil
    local hasResolver = type(detailConflict) == "table" and tonumber(detailConflict.totalRows) and tonumber(detailConflict.totalRows) > 0

    frame:SetSize(hasResolver and 780 or 560, hasResolver and 560 or 430)
    if frame.body then
      frame.body:ClearAllPoints()
      frame.body:SetPoint("TOPLEFT", 18, -50)
      frame.body:SetPoint("BOTTOMRIGHT", -18, hasResolver and 308 or 50)
      frame.body:SetText(BuildProfileSyncDetailText(candidate, syncStatus))
    end
    if frame.requestBtn then
      local selectedActions = type(editor.lootSessionSelectedScopeActions) == "table" and editor.lootSessionSelectedScopeActions.preview or nil
      if type(selectedActions) == "table"
        and selectedActions.selectionActive
        and selectedActions.compatibleCount > 0
        and string.lower(TrimText(selectedActions.targetMemberKey)) == string.lower(TrimText(candidate.memberKey)) then
        frame.requestBtn:SetText("Preview Selected Repair (" .. tostring(selectedActions.compatibleCount) .. ")")
      else
        frame.requestBtn:SetText(candidate.precisePreviewReady and "Preview Precise Repair" or "Preview Member Repair")
      end
    end
    if frame.applyBtn then
      if hasResolver then
        local resolution = BuildSelectedConflictResolution(editor, candidate, detailConflict)
        frame.applyBtn:Show()
        frame.applyBtn:SetText(
          resolution.selectionActive
            and ("Apply + Sync Selected (" .. tostring(resolution.selectedCount) .. ")")
            or "Apply + Sync Selected"
        )
        if resolution.selectionActive then
          frame.applyBtn:Enable()
        else
          frame.applyBtn:Disable()
        end
      else
        frame.applyBtn:Hide()
        frame.applyBtn:Disable()
      end
    end
    if type(RefreshSyncConflictResolver) == "function" then
      RefreshSyncConflictResolver(editor, frame, candidate, detailConflict)
    end
  end

  local function EnsureProfileSyncDetailPopup(editor)
    if not editor then
      return nil
    end
    if editor.lootSessionSyncDetailPopup then
      return editor.lootSessionSyncDetailPopup
    end

    local frame = CreateFrame("Frame", "LIDCOUNCIL_ProfileSyncDetailPopup", UIParent, "BackdropTemplate")
    frame:SetSize(560, 430)
    frame:SetPoint("CENTER")
    frame:SetFrameStrata("DIALOG")
    frame:SetFrameLevel(500)
    frame:EnableMouse(true)
    frame:SetMovable(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetScript("OnDragStart", frame.StartMoving)
    frame:SetScript("OnDragStop", frame.StopMovingOrSizing)
    ApplyFrameSkin(frame)
    frame:Hide()

    local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOPLEFT", 18, -16)
    title:SetText("Sync Detail")
    frame.title = title

    local closeBtn = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
    closeBtn:SetPoint("TOPRIGHT", -6, -6)
    frame.closeBtn = closeBtn

    local body = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    body:SetPoint("TOPLEFT", 18, -50)
    body:SetPoint("BOTTOMRIGHT", -18, 50)
    body:SetJustifyH("LEFT")
    body:SetJustifyV("TOP")
    body:SetSpacing(2)
    frame.body = body

    local requestBtn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    requestBtn:SetSize(170, 24)
    requestBtn:SetPoint("BOTTOMLEFT", 18, 16)
    requestBtn:SetText("Preview Member Repair")
    requestBtn:SetScript("OnClick", function()
      local candidate = frame.__candidate
      local memberKey = TrimText(type(candidate) == "table" and candidate.memberKey or "")
      if memberKey == "" then
        return
      end
      editor.lootSessionSelectedSyncMember = memberKey
      local requestProfileUpdate = ResolveOptionalApiFunction("LIDCOUNCIL_RequestActiveProfileSnapshotFlow")
      if type(requestProfileUpdate) == "function" then
        local previewOptions = { mode = "PREVIEW" }
        local selectedActions = type(editor.lootSessionSelectedScopeActions) == "table" and editor.lootSessionSelectedScopeActions.preview or nil
        if type(selectedActions) == "table"
          and selectedActions.selectionActive
          and selectedActions.compatibleCount > 0
          and string.lower(TrimText(selectedActions.targetMemberKey)) == string.lower(memberKey) then
          previewOptions.domains = selectedActions.domains
          previewOptions.subdomains = selectedActions.subdomains
        end
        requestProfileUpdate(memberKey, previewOptions)
      end
    end)
    frame.requestBtn = requestBtn

    local applyBtn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    applyBtn:SetSize(190, 24)
    applyBtn:SetPoint("BOTTOMRIGHT", -18, 16)
    applyBtn:SetText("Apply + Sync Selected")
    applyBtn:Hide()
    applyBtn:SetScript("OnClick", function()
      local candidate = frame.__candidate
      local memberKey = TrimText(type(candidate) == "table" and candidate.memberKey or "")
      if memberKey == "" then
        return
      end
      local getProfileSyncStatus = ResolveOptionalApiFunction("LIDCOUNCIL_GetActiveProfileSyncStatus")
      local syncStatus = type(getProfileSyncStatus) == "function" and getProfileSyncStatus(editor and editor.lootSessionAllSyncCandidates or nil) or {}
      local detailConflict = type(syncStatus.detailConflict) == "table"
        and TrimText(syncStatus.detailConflict.memberKey) == memberKey
        and syncStatus.detailConflict
        or nil
      local resolution = type(BuildSelectedConflictResolution) == "function"
        and BuildSelectedConflictResolution(editor, candidate, detailConflict)
        or nil
      if type(resolution) ~= "table" or resolution.selectionActive ~= true then
        return
      end
      local applyResolution = ResolveOptionalApiFunction("LIDCOUNCIL_ApplyActiveProfilePreviewConflictResolution")
      if type(applyResolution) == "function" then
        applyResolution(memberKey, resolution)
      end
      local refreshedStatus = type(getProfileSyncStatus) == "function" and getProfileSyncStatus(editor and editor.lootSessionAllSyncCandidates or nil) or {}
      RefreshProfileSyncDetailPopup(editor, frame, candidate, refreshedStatus)
    end)
    frame.applyBtn = applyBtn

    editor.lootSessionSyncDetailPopup = frame
    return frame
  end

  OpenProfileSyncDetailPopup = function(editor, candidate)
    local frame = EnsureProfileSyncDetailPopup(editor)
    if not frame or type(candidate) ~= "table" then
      return
    end
    local getProfileSyncStatus = ResolveOptionalApiFunction("LIDCOUNCIL_GetActiveProfileSyncStatus")
    local syncStatus = type(getProfileSyncStatus) == "function" and getProfileSyncStatus(editor and editor.lootSessionAllSyncCandidates or nil) or {}
    frame.__candidate = candidate
    if frame.title then
      frame.title:SetText("Sync Detail - " .. tostring(candidate.memberKey or "Unknown"))
    end
    RefreshProfileSyncDetailPopup(editor, frame, candidate, syncStatus)
    frame:Show()
    if frame.Raise then
      frame:Raise()
    end
  end

  local function EnsureProfileSyncReviewRows(editor, count)
    if not editor or not editor.lootSessionSyncReviewRowsFrame then
      return
    end
    editor.lootSessionSyncReviewRows = editor.lootSessionSyncReviewRows or {}
    while #(editor.lootSessionSyncReviewRows or {}) < count do
      local row = CreateFrame("Frame", nil, editor.lootSessionSyncReviewRowsFrame, "BackdropTemplate")
      ApplyFrameSkin(row)
      row:SetHeight(24)
      row:EnableMouse(true)
      row:SetScript("OnMouseUp", function(self)
        if type(OpenProfileSyncDetailPopup) == "function" then
          OpenProfileSyncDetailPopup(editor, self.__candidate)
        end
      end)

      local memberText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      memberText:SetJustifyH("LEFT")
      row.memberText = memberText

      local stateText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      stateText:SetJustifyH("LEFT")
      row.stateText = stateText

      local remoteText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      remoteText:SetJustifyH("LEFT")
      row.remoteText = remoteText

      local localText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      localText:SetJustifyH("LEFT")
      row.localText = localText

      local sessionText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      sessionText:SetJustifyH("LEFT")
      row.sessionText = sessionText

      local updatedText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      updatedText:SetJustifyH("LEFT")
      row.updatedText = updatedText

      editor.lootSessionSyncReviewRows[#editor.lootSessionSyncReviewRows + 1] = row
    end
  end

  local function RefreshProfileSyncReviewRows(editor, candidates)
    if not editor then
      return
    end
    candidates = type(candidates) == "table" and candidates or {}
    EnsureProfileSyncReviewRows(editor, #candidates)
    for index, row in ipairs(editor.lootSessionSyncReviewRows or {}) do
      local candidate = candidates[index]
      if candidate then
        row.__candidate = candidate
        local classR, classG, classB = GetSessionClassColor(candidate.classFile)
        row.memberText:SetText(tostring(candidate.memberKey or "-"))
        row.memberText:SetTextColor(classR, classG, classB, 1.00)
        local settingsState = GetProfileSyncDomainState(candidate, "settings")
        local permissionsState = GetProfileSyncDomainState(candidate, "permissions")
        local lootState = GetProfileSyncDomainState(candidate, "loot")
        local sessionState = GetProfileSyncDomainState(candidate, "session")
        local settingsR, settingsG, settingsB = GetProfileSyncDomainStateColor(settingsState)
        local permissionsR, permissionsG, permissionsB = GetProfileSyncDomainStateColor(permissionsState)
        local lootR, lootG, lootB = GetProfileSyncDomainStateColor(lootState)
        local sessionR, sessionG, sessionB = GetProfileSyncDomainStateColor(sessionState)
        row.stateText:SetText(GetProfileSyncDomainStateLabel(settingsState))
        row.stateText:SetTextColor(settingsR, settingsG, settingsB, 1.00)
        row.remoteText:SetText(GetProfileSyncDomainStateLabel(permissionsState))
        row.remoteText:SetTextColor(permissionsR, permissionsG, permissionsB, 1.00)
        row.localText:SetText(GetProfileSyncDomainStateLabel(lootState))
        row.localText:SetTextColor(lootR, lootG, lootB, 1.00)
        if row.sessionText then
          row.sessionText:SetText(GetProfileSyncDomainStateLabel(sessionState))
          row.sessionText:SetTextColor(sessionR, sessionG, sessionB, 1.00)
        end
        if row.updatedText then
          row.updatedText:SetText((tonumber(candidate.updatedAt) or 0) > 0 and FormatDateTime(candidate.updatedAt) or "-")
        end
        row:Show()
      else
        row.__candidate = nil
        row:Hide()
      end
    end
    editor.lootSessionSyncReviewRowsHeight = math.max(0, #candidates * 28)
  end

  local function BuildProfileSyncStatusText(syncStatus)
    syncStatus = type(syncStatus) == "table" and syncStatus or {}
    local tutorialMode = IsTutorialModeEnabled()
    if syncStatus.activeSession ~= true then
      return tutorialMode and "Shared Profile Sync: idle until a session starts." or ""
    end

    local lines = {}
    local message = TrimText(syncStatus.message)
    local counts = type(syncStatus.candidateCounts) == "table" and syncStatus.candidateCounts or {}
    local bootstrapCounts = type(syncStatus.bootstrapCounts) == "table" and syncStatus.bootstrapCounts or {}
    local sender = TrimText(syncStatus.sender)
    local mode = string.lower(TrimText(syncStatus.mode))
    local severity = string.lower(TrimText(syncStatus.severity))

    if message ~= "" then
      if tutorialMode or mode ~= "session_started" or severity ~= "info" then
        lines[#lines + 1] = "Shared Profile Sync: " .. message
      end
    elseif syncStatus.isHost then
      local newerCount = tonumber(counts.remote_newer) or 0
      local conflictCount = tonumber(counts.diverged) or 0
      local sameCount = tonumber(counts.same) or 0
      local olderCount = tonumber(counts.local_newer) or 0
      local readyCount = tonumber(bootstrapCounts.ready) or 0
      local pendingBootstrapCount = tonumber(bootstrapCounts.pending) or 0
      local reviewBootstrapCount = tonumber(bootstrapCounts.review) or 0
      if newerCount > 0 or conflictCount > 0 then
        lines[#lines + 1] = string.format(
          "Shared Profile Sync: %d roll-ready, %d waiting bootstrap, %d need bootstrap review, %d joined member copy/copies are newer and %d conflict(s) need review.",
          readyCount,
          pendingBootstrapCount,
          reviewBootstrapCount,
          newerCount,
          conflictCount
        )
      elseif pendingBootstrapCount > 0 or reviewBootstrapCount > 0 then
        lines[#lines + 1] = string.format(
          "Shared Profile Sync: %d roll-ready, %d still waiting bootstrap, and %d need bootstrap review while the remaining profile history keeps syncing.",
          readyCount,
          pendingBootstrapCount,
          reviewBootstrapCount
        )
      elseif olderCount > 0 then
        lines[#lines + 1] = string.format(
          "Shared Profile Sync: %d roll-ready, while %d joined member copy/copies are still catching up on non-bootstrap loot or session history.",
          readyCount,
          olderCount
        )
      elseif tutorialMode then
        lines[#lines + 1] = string.format(
          "Shared Profile Sync: %d roll-ready, %d joined copy/copies match and %d are older than the host copy.",
          readyCount,
          sameCount,
          olderCount
        )
      end
    elseif tutorialMode then
      lines[#lines + 1] = "Shared Profile Sync: host snapshots are compared against your local shared copy before anything is replaced."
    elseif severity == "warn" or severity == "error" or (tonumber(counts.local_newer) or 0) > 0 or (tonumber(counts.remote_newer) or 0) > 0 then
      lines[#lines + 1] = string.format(
        "Shared Profile Sync: %d remote scope(s) are newer, %d scope(s) are newer on your local copy, and %d conflict(s) are waiting on host review.",
        tonumber(counts.remote_newer) or 0,
        tonumber(counts.local_newer) or 0,
        tonumber(counts.diverged) or 0
      )
    end

    if #lines > 0 and syncStatus.isHost and (tutorialMode or severity == "warn" or severity == "error") then
      lines[#lines + 1] = "Use Check Differences to load precise scopes, Push Host Repair to send host repairs or nested patch transfers when ready, Preview Member Repair to inspect details, or Pull Member Repair to apply one joined member copy."
    elseif #lines > 0 and not syncStatus.isHost and ((tonumber(counts.local_newer) or 0) > 0 or (tonumber(counts.remote_newer) or 0) > 0 or (tonumber(counts.diverged) or 0) > 0) then
      lines[#lines + 1] = "Wait for the session host to push repairs, preview conflicts, or pull your newer scopes. Joined-member profile sync can continue in the background while the raid is running."
    elseif #lines > 0 and sender ~= "" then
      lines[#lines + 1] = "Last shared-profile event involved " .. sender .. "."
    end

    if #lines > 0 and tonumber(syncStatus.updatedAt) and tonumber(syncStatus.updatedAt) > 0 then
      lines[#lines + 1] = "Last event: " .. FormatDateTime(syncStatus.updatedAt)
    end

    return table.concat(lines, "\n")
  end

  local function EnsureSyncProgressPanel(editor)
    if not editor then
      return nil
    end
    if editor.lootSessionSyncProgressFrame then
      return editor.lootSessionSyncProgressFrame
    end

    local parent = editor.lootSessionRightContent or editor.lootSessionRightBg or UIParent
    local frame = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    ApplyFrameSkin(frame)
    if frame.SetBackdropColor then
      frame:SetBackdropColor(0.05, 0.06, 0.08, 0.96)
    end

    local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    title:SetJustifyH("LEFT")
    title:SetText("Background Sync Monitor")
    frame.title = title

    local stateText = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    stateText:SetJustifyH("RIGHT")
    frame.stateText = stateText

    frame.rows = {}
    frame.rowsByKey = {}
    local rowDefs = {
      { key = "queue", label = "Queue" },
      { key = "pending", label = "Pending Mix" },
      { key = "throughput", label = "Throughput" },
      { key = "results", label = "Results" },
      { key = "targets", label = "Profile Targets" },
      { key = "transfer1", label = "Transfer 1" },
      { key = "transfer2", label = "Transfer 2" },
      { key = "transfer3", label = "Transfer 3" },
      { key = "notes", label = "Notes" },
    }
    for _, rowDef in ipairs(rowDefs) do
      local label = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      label:SetJustifyH("LEFT")
      label:SetText(tostring(rowDef.label or ""))

      local value = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      value:SetJustifyH("LEFT")
      value:SetJustifyV("TOP")

      local row = {
        key = rowDef.key,
        label = label,
        value = value,
        visible = true,
      }
      frame.rows[#frame.rows + 1] = row
      frame.rowsByKey[rowDef.key] = row
    end

    editor.lootSessionSyncProgressFrame = frame
    return frame
  end

  local function GetSyncProgressStateColor(queueStatus)
    queueStatus = type(queueStatus) == "table" and queueStatus or {}
    local pending = math.max(0, tonumber(queueStatus.pending) or 0)
    local throttledTotal = math.max(0, tonumber(queueStatus.throttledTotal) or 0)
    local failedTotal = math.max(0, tonumber(queueStatus.failedTotal) or 0)
    local lastFailureReason = TrimText(queueStatus.lastFailureReason)
    local busy = queueStatus.busy == true or queueStatus.running == true or pending > 0

    if busy and failedTotal > 0 and lastFailureReason ~= "" then
      return "Retrying", 1.00, 0.62, 0.28, 1.00
    elseif busy and throttledTotal > 0 then
      return "Throttled", 1.00, 0.82, 0.32, 1.00
    elseif busy then
      return "Running", 0.38, 0.72, 1.00, 1.00
    end
    return "Idle", 0.30, 0.90, 0.36, 1.00
  end

  local function SetSyncProgressRow(frame, key, valueText, color, visible)
    local row = type(frame) == "table" and type(frame.rowsByKey) == "table" and frame.rowsByKey[key] or nil
    if not row then
      return
    end

    row.visible = visible ~= false
    row.value:SetText(tostring(valueText or "-"))
    row.label:SetTextColor(0.72, 0.78, 0.88, 1.00)
    local resolvedColor = type(color) == "table" and color or { 0.86, 0.88, 0.94, 1.00 }
    row.value:SetTextColor(
      tonumber(resolvedColor[1]) or 0.86,
      tonumber(resolvedColor[2]) or 0.88,
      tonumber(resolvedColor[3]) or 0.94,
      tonumber(resolvedColor[4]) or 1.00
    )
  end

  local function EnsurePushScopeHelp(editor)
    if not editor then
      return nil
    end
    if editor.lootSessionSyncPushHelp then
      return editor.lootSessionSyncPushHelp
    end
    local parent = editor.lootSessionRightContent or editor.lootSessionRightBg or UIParent
    local text = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    text:SetJustifyH("LEFT")
    text:SetJustifyV("TOP")
    text:SetSpacing(2)
    editor.lootSessionSyncPushHelp = text
    return text
  end

  local function RefreshSyncProgressPanel(editor, queueStatus)
    local frame = EnsureSyncProgressPanel(editor)
    if not frame then
      return
    end

    queueStatus = type(queueStatus) == "table" and queueStatus or {}
    local pending = math.max(0, tonumber(queueStatus.pending) or 0)
    local interval = tonumber(queueStatus.intervalSeconds) or 0.12
    local estimatedSeconds = math.ceil(pending * interval)
    local stateLabel, stateR, stateG, stateB, stateA = GetSyncProgressStateColor(queueStatus)
    frame.stateText:SetText(stateLabel)
    frame.stateText:SetTextColor(stateR, stateG, stateB, stateA)
    if frame.SetBackdropBorderColor then
      frame:SetBackdropBorderColor(stateR * 0.8, stateG * 0.8, stateB * 0.8, 0.90)
    end

    local byKind = type(queueStatus.byKind) == "table" and queueStatus.byKind or {}
    local profileChunks = math.max(0, tonumber(byKind.PSYNC_CHUNK) or 0)
    local profileControl = math.max(0, (tonumber(byKind.PSYNC_START) or 0) + (tonumber(byKind.PSYNC_END) or 0))
    local detailCompare = math.max(0, (tonumber(byKind.PSUB_REQ) or 0) + (tonumber(byKind.PSUB_STATE) or 0))
    local sessionControl = math.max(0,
      (tonumber(byKind.START) or 0)
      + (tonumber(byKind.BEGIN) or 0)
      + (tonumber(byKind.JOIN) or 0)
      + (tonumber(byKind.MEMBER) or 0)
      + (tonumber(byKind.STOP) or 0)
      + (tonumber(byKind.PSTATE) or 0)
      + (tonumber(byKind.PSYNC_ACK) or 0)
    )

    local sendRate = tonumber(queueStatus.sendRatePerSecond) or 0
    local byteRate = tonumber(queueStatus.byteRatePerSecond) or 0
    local sentTotal = math.max(0, tonumber(queueStatus.sentTotal) or 0)
    local attemptedTotal = math.max(0, tonumber(queueStatus.attemptedTotal) or 0)
    local throttledTotal = math.max(0, tonumber(queueStatus.throttledTotal) or 0)
    local failedTotal = math.max(0, tonumber(queueStatus.failedTotal) or 0)
    local lastFailureReason = TrimText(queueStatus.lastFailureReason)

    local queueValue
    local queueColor
    if pending > 0 or queueStatus.busy == true or queueStatus.running == true then
      queueValue = tostring(pending) .. " pending message" .. (pending == 1 and "" or "s")
        .. (pending > 0 and (" (~" .. tostring(estimatedSeconds) .. "s minimum)") or "")
      queueColor = { stateR, stateG, stateB, stateA }
    else
      queueValue = "Idle"
      queueColor = { 0.30, 0.90, 0.36, 1.00 }
    end
    SetSyncProgressRow(frame, "queue", queueValue, queueColor)

    SetSyncProgressRow(
      frame,
      "pending",
      "Profile chunks " .. tostring(profileChunks)
        .. " | Profile control " .. tostring(profileControl)
        .. " | Detail compare " .. tostring(detailCompare)
        .. " | Session control " .. tostring(sessionControl),
      { 0.82, 0.84, 0.92, 1.00 }
    )

    SetSyncProgressRow(
      frame,
      "throughput",
      string.format("%.2f msg/s | %.0f bytes/s (10s window)", sendRate, byteRate),
      { 0.72, 0.86, 0.92, 1.00 }
    )

    SetSyncProgressRow(
      frame,
      "results",
      tostring(sentTotal) .. " sent / " .. tostring(attemptedTotal) .. " attempted"
        .. " | Throttled " .. tostring(throttledTotal)
        .. " | Failed " .. tostring(failedTotal),
      failedTotal > 0 and { 1.00, 0.78, 0.62, 1.00 } or { 0.82, 0.84, 0.92, 1.00 }
    )

    local snapshotBatch = type(queueStatus.profileSnapshotBatch) == "table" and queueStatus.profileSnapshotBatch or {}
    local pendingTargets = math.max(0, tonumber(snapshotBatch.pendingTargets) or 0)
    local holdRemaining = math.max(0, tonumber(snapshotBatch.holdRemaining) or 0)
    local targetsValue = "None queued"
    if pendingTargets > 0 then
      local nextTarget = TrimText(snapshotBatch.nextTarget)
      if nextTarget == "" then
        nextTarget = "next member"
      end
      targetsValue = tostring(pendingTargets) .. " waiting | Next " .. nextTarget
      if holdRemaining > 0 then
        targetsValue = targetsValue .. " | Join grace " .. tostring(math.ceil(holdRemaining)) .. "s"
      end
    elseif holdRemaining > 0 then
      targetsValue = "Join grace active for ~" .. tostring(math.ceil(holdRemaining)) .. "s"
    end
    SetSyncProgressRow(
      frame,
      "targets",
      targetsValue,
      pendingTargets > 0 and { 1.00, 0.82, 0.32, 1.00 } or { 0.82, 0.84, 0.92, 1.00 }
    )

    local transfers = type(queueStatus.profileTransfers) == "table" and queueStatus.profileTransfers or {}
    local activeTransfers = {}
    for _, transfer in ipairs(transfers) do
      local state = TrimText(transfer.state)
      if state ~= "" and state ~= "acked" then
        activeTransfers[#activeTransfers + 1] = transfer
      end
      if #activeTransfers >= 3 then
        break
      end
    end
    for index = 1, 3 do
      local transfer = activeTransfers[index]
      if type(transfer) == "table" then
        local totalChunks = math.max(0, tonumber(transfer.totalChunks) or 0)
        local sentChunks = math.min(totalChunks, math.max(0, tonumber(transfer.sentChunks) or 0))
        local target = TrimText(transfer.target)
        if target == "" then
          target = "unknown target"
        end
        local transferText = target .. " - " .. TrimText(transfer.state)
          .. " (" .. tostring(sentChunks) .. "/" .. tostring(totalChunks) .. " chunks)"
        SetSyncProgressRow(frame, "transfer" .. tostring(index), transferText, { 0.86, 0.88, 0.94, 1.00 }, true)
      else
        SetSyncProgressRow(frame, "transfer" .. tostring(index), "", nil, false)
      end
    end

    local notesText = ""
    local notesColor = { 0.82, 0.84, 0.92, 1.00 }
    if throttledTotal > 0 and (pending > 0 or queueStatus.busy == true or queueStatus.running == true) then
      notesText = "Throttle detected. LidCouncil requeues throttled messages and retries after at least 1 second."
      notesColor = { 1.00, 0.82, 0.32, 1.00 }
    elseif lastFailureReason ~= "" and failedTotal > 0 then
      notesText = "Last send issue: " .. lastFailureReason
      notesColor = { 1.00, 0.55, 0.44, 1.00 }
    end
    SetSyncProgressRow(frame, "notes", notesText, notesColor, notesText ~= "")
  end

  local function LayoutSyncProgressPanel(editor, topOffset, rightContentWidth)
    local frame = EnsureSyncProgressPanel(editor)
    if not frame then
      return 0
    end

    local width = math.max(320, tonumber(rightContentWidth) or 320)
    frame:ClearAllPoints()
    frame:SetPoint("TOPLEFT", 18, topOffset)
    frame:SetWidth(width)

    frame.title:ClearAllPoints()
    frame.title:SetPoint("TOPLEFT", 14, -12)

    frame.stateText:ClearAllPoints()
    frame.stateText:SetPoint("TOPRIGHT", -14, -12)

    local labelWidth = math.min(118, math.max(88, math.floor(width * 0.18)))
    local valueWidth = math.max(160, width - labelWidth - 34)
    local rowTop = -36
    local rowGap = 6
    local visibleRowsHeight = 0

    for _, row in ipairs(frame.rows or {}) do
      if row.visible == false then
        row.label:Hide()
        row.value:Hide()
      else
        row.label:Show()
        row.value:Show()
        row.label:ClearAllPoints()
        row.label:SetPoint("TOPLEFT", 14, rowTop)
        row.label:SetWidth(labelWidth)

        row.value:ClearAllPoints()
        row.value:SetPoint("TOPLEFT", 14 + labelWidth, rowTop)
        row.value:SetWidth(valueWidth)

        local rowHeight = math.max(
          16,
          math.ceil(row.label:GetStringHeight() or 0),
          math.ceil(row.value:GetStringHeight() or 0)
        )
        rowTop = rowTop - rowHeight - rowGap
        visibleRowsHeight = visibleRowsHeight + rowHeight + rowGap
      end
    end

    local totalHeight = 40 + math.max(0, visibleRowsHeight - rowGap) + 14
    frame:SetHeight(totalHeight)
    return totalHeight
  end

  local function BuildProfileSyncDifferenceSummary(candidates, isHost)
    candidates = type(candidates) == "table" and candidates or {}
    if #candidates == 0 then
      return "No joined member profile comparisons are available yet. Prepare a session and wait for members to join."
    end

    local sameCount = 0
    local issueLines = {}
    for _, candidate in ipairs(candidates) do
      local comparison = TrimText(candidate.comparison)
      if comparison == "same" then
        sameCount = sameCount + 1
      else
        local member = tostring(candidate.memberKey or "Unknown")
        local summary
        if comparison == "remote_newer" then
          summary = (isHost and "Member has newer " or "Compared member is newer in ") .. GetProfileSyncDomainLabel(candidate.remoteDomainText)
        elseif comparison == "local_newer" then
          summary = (isHost and "Host has newer " or "Your local copy is older in ") .. GetProfileSyncDomainLabel(candidate.localDomainText)
        elseif comparison == "diverged" then
          summary = "Conflict. Member newer: " .. GetProfileSyncDomainLabel(candidate.remoteDomainText)
            .. "; " .. (isHost and "Host newer: " or "Your copy newer: ") .. GetProfileSyncDomainLabel(candidate.localDomainText)
        elseif comparison == "different_lineage" then
          summary = "Different profile lineage"
        else
          summary = "Unknown sync state"
        end
        issueLines[#issueLines + 1] = "- " .. member .. ": " .. summary
      end
    end

    if #issueLines == 0 then
      return "All " .. tostring(sameCount) .. " joined member copy/copies currently match the " .. (isHost and "host" or "local") .. " profile revision counters and checksums."
    end
    local lines = {
      tostring(#issueLines) .. " joined member copy/copies need attention; " .. tostring(sameCount) .. " match the " .. (isHost and "host" or "local") .. " copy.",
    }
    for index = 1, math.min(#issueLines, 6) do
      lines[#lines + 1] = issueLines[index]
    end
    if #issueLines > 6 then
      lines[#lines + 1] = "- " .. tostring(#issueLines - 6) .. " more difference(s) are hidden in the table below."
    end
    return table.concat(lines, "\n")
  end

  local function RefreshSyncManagementView(editor, context)
    if not editor then
      return
    end

    context = type(context) == "table" and context or {}
    local activeSession = context.activeSession
    local activeSessionIsHost = context.activeSessionIsHost and true or false
    local access = type(context.access) == "table" and context.access or {}
    local inCombat = type(InCombatLockdown) == "function" and InCombatLockdown() and true or false

    local checkSyncBtn = editor.sessionCheckSyncBtn
    local pushProfileBtn = editor.sessionPushProfileBtn
    local profileSyncDropdown = editor.sessionProfileSyncMemberDropdown
    local recommendedRepairBtn = editor.sessionRecommendedRepairBtn
    local requestProfileBtn = editor.sessionRequestProfileBtn
    local previewProfileBtn = editor.sessionPreviewProfileBtn
    local syncBoardBtn = editor.sessionSyncBoardBtn

    local getProfileSyncCandidates = ResolveOptionalApiFunction("LIDCOUNCIL_GetActiveProfileSyncCandidates")
    local getProfileSyncStatus = ResolveOptionalApiFunction("LIDCOUNCIL_GetActiveProfileSyncStatus")
    local getQueueStatus = ResolveOptionalApiFunction("LIDCOUNCIL_GetSessionCommQueueStatus")
    local queueStatus = type(getQueueStatus) == "function" and getQueueStatus() or {}
    local syncCandidates = type(getProfileSyncCandidates) == "function" and getProfileSyncCandidates() or {}
    local syncStatus = type(getProfileSyncStatus) == "function" and getProfileSyncStatus(syncCandidates) or {
      activeSession = activeSession ~= nil,
      isHost = activeSessionIsHost,
    }
    editor.lootSessionAllSyncCandidates = syncCandidates
    editor.lootSessionProfileSyncStatusData = syncStatus
    editor.lootSessionCommQueueStatusData = queueStatus

    local actionableSyncCandidates = {}
    for _, candidate in ipairs(syncCandidates or {}) do
      local comparison = TrimText(candidate.comparison)
      if comparison == "remote_newer" or comparison == "diverged" then
        actionableSyncCandidates[#actionableSyncCandidates + 1] = candidate
      end
    end
    editor.lootSessionActionableSyncCandidates = actionableSyncCandidates
    editor.lootSessionSyncCandidates = syncCandidates

    local selectedSyncMember = TrimText(editor.lootSessionSelectedSyncMember)
    if not FindProfileSyncCandidateByKey(syncCandidates, selectedSyncMember) then
      selectedSyncMember = TrimText(type(syncCandidates[1]) == "table" and syncCandidates[1].memberKey or "")
      editor.lootSessionSelectedSyncMember = selectedSyncMember
    end
    local selectedSyncCandidate = FindProfileSyncCandidateByKey(syncCandidates, selectedSyncMember)
    local selectedSyncCandidateActionable = CanPullProfileSyncCandidate(selectedSyncCandidate)
    local canSelectProfileSyncCandidate = activeSession and activeSessionIsHost and access.canManageSession and #syncCandidates > 0 and not inCombat
    local selectedScopeModel = RefreshSyncScopePicker(editor, {
      candidate = selectedSyncCandidate,
      isHost = activeSessionIsHost and access.canManageSession,
    })
    local selectedScopeActions = type(selectedScopeModel) == "table" and type(selectedScopeModel.actions) == "table"
      and selectedScopeModel.actions
      or {}
    editor.lootSessionSelectedScopeActions = selectedScopeActions
    editor.lootSessionSelectedScopeHint = type(selectedScopeModel) == "table" and TrimText(selectedScopeModel.selectionHint) or ""

    if profileSyncDropdown then
      profileSyncDropdown:SetMenuBuilder(function(dropdown)
        local entries = {}
        for _, candidate in ipairs(editor.lootSessionSyncCandidates or {}) do
          local memberLabel = BuildProfileSyncCandidateLabel(candidate)
          entries[#entries + 1] = {
            text = memberLabel,
            searchText = table.concat({
              tostring(candidate.memberKey or ""),
              tostring(candidate.role or ""),
              tostring(candidate.remoteDomainText or ""),
            }, " "),
            checked = TrimText(candidate.memberKey) == TrimText(editor.lootSessionSelectedSyncMember),
            func = function()
              editor.lootSessionSelectedSyncMember = TrimText(candidate.memberKey)
              dropdown:SetSelectedValue(editor.lootSessionSelectedSyncMember)
              dropdown:SetText(memberLabel)
              if editor.Refresh then
                editor:Refresh()
              end
            end,
          }
        end
        return {
          width = math.max(320, tonumber(dropdown.dropdownWidth) or 320),
          enableSearch = true,
          searchPlaceholder = "Search joined member",
          entries = entries,
        }
      end)
      profileSyncDropdown:SetSelectedValue(selectedSyncCandidate and selectedSyncCandidate.memberKey or nil)
      profileSyncDropdown:SetText(
        selectedSyncCandidate and BuildProfileSyncCandidateLabel(selectedSyncCandidate)
          or "No joined member comparisons available"
      )
      if profileSyncDropdown.SetEnabled then
        profileSyncDropdown:SetEnabled(canSelectProfileSyncCandidate)
      elseif profileSyncDropdown.button and profileSyncDropdown.button.Enable then
        if canSelectProfileSyncCandidate then
          profileSyncDropdown.button:Enable()
        else
          profileSyncDropdown.button:Disable()
        end
      end
    end

    RefreshSyncDashboard(editor, {
      candidates = syncCandidates,
      syncStatus = syncStatus,
      isHost = activeSessionIsHost and access.canManageSession,
    })

    local pushScopeAction = type(selectedScopeActions.push) == "table" and selectedScopeActions.push or {}
    local pullScopeAction = type(selectedScopeActions.pull) == "table" and selectedScopeActions.pull or {}
    local previewScopeAction = type(selectedScopeActions.preview) == "table" and selectedScopeActions.preview or {}
    local selectedRecommendation = type(selectedSyncCandidate) == "table" and type(selectedSyncCandidate.safeRecommendation) == "table"
      and selectedSyncCandidate.safeRecommendation
      or nil

    if checkSyncBtn and checkSyncBtn.Enable then
      if activeSession and activeSessionIsHost and access.canManageSession and not inCombat then
        checkSyncBtn:Enable()
      else
        checkSyncBtn:Disable()
      end
    end
    if pushProfileBtn and pushProfileBtn.Enable then
      local queueBusy = type(queueStatus) == "table" and (queueStatus.busy == true or math.max(0, tonumber(queueStatus.pending) or 0) > 0)
      pushProfileBtn:SetText(
        pushScopeAction.selectionActive and pushScopeAction.compatibleCount > 0
          and ("Push Selected Repair (" .. tostring(pushScopeAction.compatibleCount) .. ")")
          or "Push Host Repair"
      )
      if activeSession and activeSessionIsHost and access.canManageSession and not inCombat and not queueBusy
        and (not pushScopeAction.selectionActive or pushScopeAction.compatibleCount > 0) then
        pushProfileBtn:Enable()
      else
        pushProfileBtn:Disable()
      end
    end
    if requestProfileBtn and requestProfileBtn.Enable then
      if pullScopeAction.selectionActive and pullScopeAction.compatibleCount > 0 then
        requestProfileBtn:SetText("Pull Selected Repair (" .. tostring(pullScopeAction.compatibleCount) .. ")")
      else
        requestProfileBtn:SetText(GetProfileSyncRequestButtonText(selectedSyncCandidate))
      end
      if activeSession and activeSessionIsHost and access.canManageSession and not inCombat
        and ((pullScopeAction.selectionActive and pullScopeAction.compatibleCount > 0) or (not pullScopeAction.selectionActive and selectedSyncCandidateActionable)) then
        requestProfileBtn:Enable()
      else
        requestProfileBtn:Disable()
      end
    end
    if previewProfileBtn and previewProfileBtn.Enable then
      if previewScopeAction.selectionActive and previewScopeAction.compatibleCount > 0 then
        previewProfileBtn:SetText("Preview Selected Repair (" .. tostring(previewScopeAction.compatibleCount) .. ")")
      else
        previewProfileBtn:SetText(selectedSyncCandidate and selectedSyncCandidate.precisePreviewReady and "Preview Precise Repair" or "Preview Member Repair")
      end
      if activeSession and activeSessionIsHost and access.canManageSession and selectedSyncCandidate and not inCombat
        and (not previewScopeAction.selectionActive or previewScopeAction.compatibleCount > 0) then
        previewProfileBtn:Enable()
      else
        previewProfileBtn:Disable()
      end
    end
    if recommendedRepairBtn and recommendedRepairBtn.Enable then
      local recommendationAction = string.lower(TrimText(type(selectedRecommendation) == "table" and selectedRecommendation.action or ""))
      local recommendationLabel = TrimText(type(selectedRecommendation) == "table" and selectedRecommendation.label or "")
      recommendedRepairBtn:SetText(recommendationLabel ~= "" and recommendationLabel or "Run Recommended Repair")
      if activeSession and activeSessionIsHost and access.canManageSession and selectedSyncCandidate and not inCombat and recommendationAction ~= "" and recommendationAction ~= "none" then
        recommendedRepairBtn:Enable()
      else
        recommendedRepairBtn:Disable()
      end
    end
    if syncBoardBtn and syncBoardBtn.Enable then
      if activeSession and activeSessionIsHost and access.canManageSession and not inCombat then
        syncBoardBtn:Enable()
      else
        syncBoardBtn:Disable()
      end
    end

    if editor.lootSessionTitle then
      editor.lootSessionTitle:SetText("Sync Management")
    end
    if editor.lootSessionStatus then
      if not activeSession then
        editor.lootSessionStatus:SetText("No active session")
        editor.lootSessionStatus:SetTextColor(1.00, 0.35, 0.25, 1.00)
        ApplyTrafficLight(editor.lootSessionStatusIndicator, "PENDING")
      elseif activeSessionIsHost and access.canManageSession and inCombat then
        editor.lootSessionStatus:SetText("Manual sync locked during combat")
        editor.lootSessionStatus:SetTextColor(1.00, 0.82, 0.32, 1.00)
        ApplyTrafficLight(editor.lootSessionStatusIndicator, "PARTIAL")
      elseif activeSessionIsHost and access.canManageSession then
        editor.lootSessionStatus:SetText("Host sync controls available")
        editor.lootSessionStatus:SetTextColor(0.20, 0.90, 0.20, 1.00)
        ApplyTrafficLight(editor.lootSessionStatusIndicator, "SYNCED")
      else
        editor.lootSessionStatus:SetText("Read-only sync overview")
        editor.lootSessionStatus:SetTextColor(1.00, 0.82, 0.32, 1.00)
        ApplyTrafficLight(editor.lootSessionStatusIndicator, "PARTIAL")
      end
    end

    if editor.lootSessionProfileSyncStatus then
      local statusText = BuildProfileSyncStatusText(syncStatus)
      if inCombat and activeSessionIsHost and access.canManageSession then
        if TrimText(statusText) ~= "" then
          statusText = statusText .. "\n"
        end
        statusText = statusText .. "Manual sync actions are disabled during combat."
      end
      local r, g, b, a = GetProfileSyncStatusColor(syncStatus.severity)
      if IsTutorialModeEnabled() and string.lower(TrimText(syncStatus.severity)) == "info" then
        r, g, b, a = 1.00, 0.82, 0.24, 1.00
      end
      editor.lootSessionProfileSyncStatus:SetText(statusText)
      editor.lootSessionProfileSyncStatus:SetTextColor(r, g, b, a)
    end
    if editor.lootSessionSyncSelectedHint then
      local hintText = activeSession and BuildSelectedProfileSyncHint(selectedSyncCandidate, activeSessionIsHost and access.canManageSession) or ""
      if TrimText(editor.lootSessionSelectedScopeHint) ~= "" then
        if hintText ~= "" then
          hintText = hintText .. "\n"
        end
        hintText = hintText .. editor.lootSessionSelectedScopeHint
      end
      if type(selectedRecommendation) == "table" and TrimText(selectedRecommendation.label) ~= "" then
        if hintText ~= "" then
          hintText = hintText .. "\n"
        end
        hintText = hintText .. "Recommended: " .. tostring(selectedRecommendation.label) .. ". " .. tostring(TrimText(selectedRecommendation.reason))
      end
      editor.lootSessionSyncSelectedHint:SetText(hintText)
      local hintColorKey = TrimText(type(selectedSyncCandidate) == "table" and selectedSyncCandidate.comparison or "")
      local hr, hg, hb, ha = GetProfileSyncComparisonColor(hintColorKey)
      if hintText == "" then
        hr, hg, hb, ha = 0.82, 0.82, 0.86, 1.00
      end
      editor.lootSessionSyncSelectedHint:SetTextColor(hr, hg, hb, ha)
    end

    RefreshSyncProgressPanel(editor, queueStatus)
    RefreshSyncHistoryPanel(editor, {
      activeSession = activeSession,
      activeSessionIsHost = activeSessionIsHost,
      access = access,
      inCombat = inCombat,
    })
    if type(queueStatus) == "table" and queueStatus.busy == true and type(C_Timer) == "table"
        and type(C_Timer.After) == "function" and not editor.__lootSessionSyncProgressRefreshQueued then
      editor.__lootSessionSyncProgressRefreshQueued = true
      C_Timer.After(1, function()
        if editor then
          editor.__lootSessionSyncProgressRefreshQueued = nil
          if editor.Refresh then
            editor:Refresh()
          end
        end
      end)
    end

    if editor.lootSessionSyncIntro then
      editor.lootSessionSyncIntro:SetText(
        BuildProfileSyncDifferenceSummary(syncCandidates, activeSessionIsHost and access.canManageSession)
        .. "\n\nCheck Differences requests detailed leaf checksum scopes from mismatched joined members. LidCouncil also refreshes them automatically when a joined member reports a mismatch. This step never replaces profile data."
      )
    end
    if editor.lootSessionSyncCheckHeader then
      editor.lootSessionSyncCheckHeader:SetText("1. Check Differences")
    end
    if editor.lootSessionSyncPushHeader then
      editor.lootSessionSyncPushHeader:SetText("2. Leaf Scope Picker")
    end
    local pushHelp = EnsurePushScopeHelp(editor)
    if pushHelp then
      local pushLines = {}
      if activeSession and activeSessionIsHost and access.canManageSession then
        local selectedMemberLabel = tostring(
          TrimText(selectedSyncMember) ~= "" and selectedSyncMember or "the selected joined member"
        )
        pushLines[#pushLines + 1] = "- First choose one joined member above, then click individual leaf rows if you want to narrow repair to exact scopes."
        if pushScopeAction.selectionActive and pushScopeAction.compatibleCount > 0 then
          pushLines[#pushLines + 1] = "- Current leaf filter: " .. tostring(pushScopeAction.compatibleCount) .. " selected scope"
            .. (pushScopeAction.compatibleCount == 1 and "" or "s")
            .. " for " .. selectedMemberLabel .. "."
        else
          pushLines[#pushLines + 1] = "- Leaving all rows clear keeps the broader repair behavior; selecting rows limits Push / Pull / Preview to those leaves."
        end
        if type(selectedSyncCandidate) == "table" and selectedSyncCandidate.patchPushReady then
          pushLines[#pushLines + 1] = "- Patch-ready leaves use smaller nested transfers when possible; other leaves fall back to precise snapshots."
        else
          pushLines[#pushLines + 1] = "- After selecting scopes, use the repair buttons below to decide whether host data should be pushed or member data should be pulled."
        end
      else
        pushLines[#pushLines + 1] = "- The Leaf Scope Picker shows which individual repair scopes differ for the selected joined member."
        pushLines[#pushLines + 1] = "- Push / Pull / Preview actions remain available only to the active session host."
      end
      pushHelp:SetText(table.concat(pushLines, "\n"))
      pushHelp:SetTextColor(0.78, 0.82, 0.92, 1.00)
      pushHelp:Show()
    end
    if editor.lootSessionSyncPullHeader then
      editor.lootSessionSyncPullHeader:SetText("3. Push or Pull Repair")
    end
    if editor.lootSessionSyncAutoHeader then
      editor.lootSessionSyncAutoHeader:SetText("Existing Sync Workflow Notes")
    end
    if editor.lootSessionSyncAutoText then
      editor.lootSessionSyncAutoText:SetText(ApplyDeveloperAccentMarkup(
        "|cffa87ae81. Player 1 starts a session|r\n" ..
        "- LidCouncil sends a session invite to the current party, raid, or instance group.\n" ..
        "- Synced with the invite: session ID, profile name, profile ID, host, session start time, and profile master.\n" ..
        "- Roll options are not sent in the invite itself. They come from the shared profile copy that already exists locally or from the automatic host snapshot after join.\n\n" ..
        "|cffa87ae82. Player 1 sends Session Invite later|r\n" ..
        "- The |cff4adf6bCurrent Session -> Send Session Invite|r button repeats the same invite for late joiners.\n" ..
        "- No profile data is pushed yet. The real profile sync starts only after the player accepts and joins.\n\n" ..
        "|cffa87ae83. Player 2 accepts the invite|r\n" ..
        "- The join reply syncs the member snapshot to the host: character key, joined time, addon/client version, class/spec, personal Auto Pass state, shared profile ID, revision counters, and checksums for |cff4adf6bSettings|r, |cff4adf6bPermissions|r, |cff4adf6bLoot Data|r, and separate |cff4adf6bSession Data|r.\n" ..
        "- The host compares that member copy against the host copy immediately.\n\n" ..
        "|cffa87ae84. Check Differences|r\n" ..
        "- Host only.\n" ..
        "- Requests detailed leaf checksum scopes for only the mismatched joined members.\n" ..
        "- LidCouncil also refreshes these scopes automatically when a joined member reports mismatched state.\n" ..
        "- No profile data is replaced during this step. It only prepares smaller repair scopes for the buttons below.\n" ..
        "- The dashboard |cff4adf6bReady|r column is separate from full sync: it turns green once |cff4adf6bSettings|r and |cff4adf6bPermissions|r are aligned enough for roll choices, even if Loot or Session history is still catching up.\n" ..
        "- Once those scopes are loaded, first choose the joined member in the Leaf Scope Picker section, then click one or more leaf rows to narrow Push / Pull / Preview to exact leaves.\n\n" ..
        "|cffa87ae85. Automatic shared profile repair on join|r\n" ..
        "- If the joiner has no local shared copy, the host automatically sends a smaller |cff4adf6bSettings + Permissions|r bootstrap first so roll choices can unlock sooner.\n" ..
        "- After that bootstrap, the remaining |cff4adf6bLoot Data|r and |cff4adf6bSession Data|r continue syncing in the background.\n" ..
        "- If the joiner has an older copy of the same profile lineage, the host automatically sends only the newer host domains when that is enough.\n" ..
        "- If the joiner has newer or diverged data, nothing is overwritten automatically. Review is required first.\n\n" ..
        "|cffa87ae86. Automatic runtime session sync|r\n" ..
        "- While the session is active, LidCouncil automatically syncs joined member status, personal Auto Pass changes, live roll choices, council votes, awards, unassigns, and the active Live Board state.\n" ..
        "- When the host stops the session, the automatic session sync flow ends."
      ))
    end
    if editor.lootSessionSyncManualHeader then
      editor.lootSessionSyncManualHeader:SetText("Manual Sync Reference")
    end
    if editor.lootSessionSyncManualText then
      editor.lootSessionSyncManualText:SetText(ApplyDeveloperAccentMarkup(
        "|cffa87ae81. Leaf Scope Picker|r\n" ..
        "- Host only.\n" ..
        "- Choose the joined member you want to inspect or repair.\n" ..
        "- Use the dashboard |cff4adf6bReady|r column when you want to see who can already receive roll choices before the rest of the shared history fully finishes.\n" ..
        "- Click individual leaf rows to narrow Push / Pull / Preview to exact scopes.\n" ..
        "- Leaving every row clear keeps the broader repair behavior.\n\n" ..
        "|cffa87ae82. Push Host Repair|r\n" ..
        "- Host only.\n" ..
        "- Sends your current host copy only for the domains that the host is newer in. Member-newer and conflict scopes are skipped for safety.\n" ..
        "- If Check Differences already loaded precise leaf scopes, only those smaller scopes are sent. Otherwise LidCouncil falls back to domain-level repair.\n" ..
        "- If you tick individual rows in the Leaf Scope Picker, only those compatible selected leaves are pushed to the selected member. Leaving it empty keeps the broader host repair behavior.\n" ..
        "- The nested Sync Detail resolver can also apply selected member rows to the host and immediately queue direct host repair for the affected leaf scopes.\n" ..
        "- For brand-new joiners without a shared copy, LidCouncil now prioritizes a smaller bootstrap first and lets the heavier loot/session history continue afterward.\n" ..
        "- Shared content: |cff4adf6bSettings|r, |cff4adf6bPermissions|r, |cff4adf6bLoot Data|r, and |cff4adf6bSession Data|r for the active shared profile.\n\n" ..
        "|cffa87ae82.5. Run Recommended Repair|r\n" ..
        "- Host only.\n" ..
        "- Uses the selected joined member plus the loaded compare state to choose the currently safest manual action: Check Differences, Push Host Repair, Pull Member Repair, or Preview Member Repair.\n" ..
        "- It remains manual. LidCouncil only preselects the matching leaf scopes for that action instead of repairing silently in the background.\n" ..
        "- If the recommendation is only \"Check Differences\", it now targets the selected joined member instead of rechecking everybody.\n\n" ..
        "|cffa87ae83. Pull Member Repair|r\n" ..
        "- Host only.\n" ..
        "- Requests only the selected joined-member scopes that are newer than the host copy.\n" ..
        "- If Check Differences already loaded precise leaf scopes, the pull can be limited to those smaller scopes.\n" ..
        "- If you tick individual rows in the Leaf Scope Picker, only those compatible selected leaves are pulled from the selected member. Leaving it empty keeps the broader member-repair behavior.\n" ..
        "- Applies the selected joined member repair snapshot to your local host copy.\n" ..
        "- Use this only after the difference table or preview confirms you want their copy.\n\n" ..
        "|cffa87ae84. Preview Member Repair|r\n" ..
        "- Host only.\n" ..
        "- Requests the differing joined-member scopes for inspection only.\n" ..
        "- If you tick individual rows in the Leaf Scope Picker, preview is narrowed to those selected leaves for the current member.\n" ..
        "- This fills the Sync Detail popup with concrete differences and does |cffff5a5anot|r replace your local host copy.\n\n" ..
        "|cffa87ae85. Send Board Sync|r\n" ..
        "- Host only.\n" ..
        "- Sends only the current Live Board recovery snapshot: dropped items, selected board item, voting state, and assignment state.\n" ..
        "- It does |cffff5a5anot|r replace profile settings, permissions, loot ledger history, or point rules.\n" ..
        "- Use it when the board is desynced after a reload, UI error, or crash.\n\n" ..
        "|cffa87ae86. Repair History and Undo|r\n" ..
        "- Host only.\n" ..
        "- Recent host-side repair applies are listed with timestamps so you can see what changed and from which joined member it came.\n" ..
        "- Undo reimports the last host-side repair snapshot for the active shared profile only. It is a local host safety net, not a raid-wide rollback."
      ))
    end
    if editor.lootSessionSyncReviewEmpty then
      if not activeSession then
        editor.lootSessionSyncReviewEmpty:SetText("Start a session to compare joined shared copies.")
      elseif #(editor.lootSessionAllSyncCandidates or {}) > 0 then
        editor.lootSessionSyncReviewEmpty:SetText("")
      else
        editor.lootSessionSyncReviewEmpty:SetText("No joined member profile comparisons are available yet.")
      end
    end
    if editor.lootSessionSyncDetailPopup and editor.lootSessionSyncDetailPopup:IsShown() and type(editor.lootSessionSyncDetailPopup.__candidate) == "table" then
      RefreshProfileSyncDetailPopup(editor, editor.lootSessionSyncDetailPopup, editor.lootSessionSyncDetailPopup.__candidate, syncStatus)
    end
  end

  local function LayoutSyncManagementSection(editor, page, rightContentWidth)
    if not editor then
      return 220
    end

    local progressTop = -78
    local progressHeight = LayoutSyncProgressPanel(editor, progressTop, rightContentWidth)

    local checkHeaderTop = progressTop - progressHeight - 24
    if editor.lootSessionSyncCheckHeader then
      editor.lootSessionSyncCheckHeader:ClearAllPoints()
      editor.lootSessionSyncCheckHeader:SetPoint("TOPLEFT", 18, checkHeaderTop)
    end

    local dashboardTop = checkHeaderTop - 28
    local dashboardHeight = LayoutSyncDashboard(editor, dashboardTop, rightContentWidth)

    local checkActionTop = dashboardTop - dashboardHeight - 14
    local checkButtonHeight = FlowLayoutControls({
      editor.sessionCheckSyncBtn,
    }, 18, checkActionTop, rightContentWidth, 10, 8)

    local pushHeaderTop = checkActionTop - checkButtonHeight - 24
    if editor.lootSessionSyncPushHeader then
      editor.lootSessionSyncPushHeader:ClearAllPoints()
      editor.lootSessionSyncPushHeader:SetPoint("TOPLEFT", 18, pushHeaderTop)
    end
    local requestTop = pushHeaderTop - 28
    local profileSyncDropdown = editor.sessionProfileSyncMemberDropdown
    local selectedHint = editor.lootSessionSyncSelectedHint
    local requestControlsHeight = 0
    if profileSyncDropdown then
      local dropdownWidth = math.max(220, rightContentWidth - 16)
      requestControlsHeight = math.max(profileSyncDropdown.buttonHeight or 22, 22)
      profileSyncDropdown.dropdownWidth = dropdownWidth
      profileSyncDropdown:SetSize(dropdownWidth + 24, profileSyncDropdown.buttonHeight or 22)
      if profileSyncDropdown.button then
        profileSyncDropdown.button:SetWidth(dropdownWidth)
      end
      profileSyncDropdown:ClearAllPoints()
      profileSyncDropdown:SetPoint("TOPLEFT", 18, requestTop)
    end

    local scopeFrameHeight = LayoutSyncScopePicker(editor, requestTop - requestControlsHeight - 10, rightContentWidth)
    local selectedHintTop = requestTop - requestControlsHeight - 10 - scopeFrameHeight - 10
    if selectedHint then
      selectedHint:ClearAllPoints()
      selectedHint:SetPoint("TOPLEFT", 18, selectedHintTop)
      selectedHint:SetWidth(rightContentWidth)
    end
    local selectedHintHeight = math.max(0, math.ceil((selectedHint and selectedHint:IsShown() and selectedHint:GetStringHeight() or 0)))

    local pullHeaderTop = selectedHintTop - selectedHintHeight - (selectedHintHeight > 0 and 18 or 24)
    if editor.lootSessionSyncPullHeader then
      editor.lootSessionSyncPullHeader:ClearAllPoints()
      editor.lootSessionSyncPullHeader:SetPoint("TOPLEFT", 18, pullHeaderTop)
    end
    local pushActionTop = pullHeaderTop - 28
    local pushButtonHeight = FlowLayoutControls({
      editor.sessionPushProfileBtn,
      editor.sessionRecommendedRepairBtn,
      editor.sessionRequestProfileBtn,
      editor.sessionPreviewProfileBtn,
      editor.sessionSyncBoardBtn,
    }, 18, pushActionTop, rightContentWidth, 10, 8)

    local pushHelpTop = pushActionTop - pushButtonHeight - 8
    local pushHelpHeight = 0
    if editor.lootSessionSyncPushHelp then
      editor.lootSessionSyncPushHelp:ClearAllPoints()
      editor.lootSessionSyncPushHelp:SetPoint("TOPLEFT", 18, pushHelpTop)
      editor.lootSessionSyncPushHelp:SetWidth(rightContentWidth)
      pushHelpHeight = math.max(0, math.ceil((editor.lootSessionSyncPushHelp:IsShown() and editor.lootSessionSyncPushHelp:GetStringHeight() or 0)))
    end

    local profileSyncStatusTop = pushHelpTop - pushHelpHeight - (pushHelpHeight > 0 and 10 or 0)
    if editor.lootSessionProfileSyncStatus then
      editor.lootSessionProfileSyncStatus:ClearAllPoints()
      editor.lootSessionProfileSyncStatus:SetPoint("TOPLEFT", 18, profileSyncStatusTop)
      editor.lootSessionProfileSyncStatus:SetWidth(rightContentWidth)
    end
    local profileSyncStatusHeight = math.max(18, math.ceil((editor.lootSessionProfileSyncStatus and editor.lootSessionProfileSyncStatus:IsShown() and editor.lootSessionProfileSyncStatus:GetStringHeight() or 0)))

    local historyTop = profileSyncStatusTop - profileSyncStatusHeight - 18
    local historyHeight = LayoutSyncHistoryPanel(editor, historyTop, rightContentWidth)

    local autoHeaderTop = historyTop - historyHeight - 18
    if editor.lootSessionSyncAutoHeader then
      editor.lootSessionSyncAutoHeader:ClearAllPoints()
      editor.lootSessionSyncAutoHeader:SetPoint("TOPLEFT", 18, autoHeaderTop)
    end
    local autoTextTop = autoHeaderTop - 28
    if editor.lootSessionSyncAutoText then
      editor.lootSessionSyncAutoText:ClearAllPoints()
      editor.lootSessionSyncAutoText:SetPoint("TOPLEFT", 18, autoTextTop)
      editor.lootSessionSyncAutoText:SetWidth(rightContentWidth)
    end
    local autoTextHeight = math.max(110, math.ceil((editor.lootSessionSyncAutoText and editor.lootSessionSyncAutoText:IsShown() and editor.lootSessionSyncAutoText:GetStringHeight() or 0)))

    local manualHeaderTop = autoTextTop - autoTextHeight - 18
    if editor.lootSessionSyncManualHeader then
      editor.lootSessionSyncManualHeader:ClearAllPoints()
      editor.lootSessionSyncManualHeader:SetPoint("TOPLEFT", 18, manualHeaderTop)
    end
    local manualTextTop = manualHeaderTop - 28
    if editor.lootSessionSyncManualText then
      editor.lootSessionSyncManualText:ClearAllPoints()
      editor.lootSessionSyncManualText:SetPoint("TOPLEFT", 18, manualTextTop)
      editor.lootSessionSyncManualText:SetWidth(rightContentWidth)
    end
    local manualTextHeight = math.max(96, math.ceil((editor.lootSessionSyncManualText and editor.lootSessionSyncManualText:IsShown() and editor.lootSessionSyncManualText:GetStringHeight() or 0)))

    local contentBottom = math.abs(manualTextTop) + manualTextHeight + 32
    if page then
      page.__contentHeight = math.max(520, contentBottom)
    end
    return contentBottom
  end

  return {
    RefreshSyncManagementView = RefreshSyncManagementView,
    LayoutSyncManagementSection = LayoutSyncManagementSection,
  }
end
