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_CreateLootSessionSyncDashboardHelpers_Impl = function(deps)
  deps = type(deps) == "table" and deps or {}

  local ApplyFrameSkin = deps.ApplyFrameSkin
  local TrimText = deps.TrimText
  local GetSessionClassColor = deps.GetSessionClassColor
  local GetProfileSyncDomainState = deps.GetProfileSyncDomainState
  local GetProfileSyncDomainStateLabel = deps.GetProfileSyncDomainStateLabel
  local GetProfileSyncDomainStateColor = deps.GetProfileSyncDomainStateColor
  local GetProfileSyncComparisonLabel = deps.GetProfileSyncComparisonLabel
  local GetProfileSyncComparisonColor = deps.GetProfileSyncComparisonColor
  local GetProfileSyncBootstrapLabel = deps.GetProfileSyncBootstrapLabel
  local GetProfileSyncBootstrapStateColor = deps.GetProfileSyncBootstrapStateColor
  local OpenProfileSyncDetail = deps.OpenProfileSyncDetail

  local DOMAIN_ORDER = { "settings", "permissions", "loot", "session" }
  local STATUS_COLORS = {
    same = { 0.25, 0.86, 0.42, 1.00 },
    ready = { 0.25, 0.86, 0.42, 1.00 },
    pending = { 1.00, 0.82, 0.32, 1.00 },
    review = { 0.94, 0.34, 0.28, 1.00 },
    background = { 0.70, 0.64, 0.94, 1.00 },
    declined = { 0.94, 0.34, 0.28, 1.00 },
    local_newer = { 0.28, 0.72, 1.00, 1.00 },
    remote_newer = { 1.00, 0.82, 0.32, 1.00 },
    diverged = { 0.94, 0.34, 0.28, 1.00 },
    different_lineage = { 0.94, 0.62, 0.28, 1.00 },
    lineage = { 0.94, 0.62, 0.28, 1.00 },
    unknown = { 0.78, 0.78, 0.84, 1.00 },
    member_newer = { 1.00, 0.82, 0.32, 1.00 },
    host_newer = { 0.28, 0.72, 1.00, 1.00 },
    conflict = { 0.94, 0.34, 0.28, 1.00 },
    queued = { 1.00, 0.82, 0.32, 1.00 },
    sending = { 0.38, 0.72, 1.00, 1.00 },
    waiting_receipt = { 0.40, 0.86, 0.96, 1.00 },
    waiting_apply = { 0.76, 0.84, 1.00, 1.00 },
  }
  local FALLBACK_DOMAIN_LABELS = {
    same = "Identical",
    background = "Background",
    member_newer = "Member newer",
    host_newer = "Host newer",
    conflict = "Both changed",
    lineage = "Lineage",
  }
  local FALLBACK_COMPARISON_LABELS = {
    same = "Aligned",
    local_newer = "Local copy newer",
    remote_newer = "Member newer",
    diverged = "Conflict",
    different_lineage = "Different lineage",
  }
  local FALLBACK_BOOTSTRAP_LABELS = {
    ready = "Roll-Ready",
    pending = "Bootstrap Pending",
    review = "Bootstrap Needs Review",
    declined = "Bootstrap Declined",
    lineage = "Different Lineage",
  }

  local function GetStatusColor(key)
    return unpack(STATUS_COLORS[key] or STATUS_COLORS.unknown)
  end

  local function ResolveSharedOrFallbackLabel(sharedLabelFn, key, fallbackLabels, fallbackLabel)
    local resolvedKey = string.lower(TrimText(key))
    if type(sharedLabelFn) == "function" then
      local label = TrimText(sharedLabelFn(resolvedKey))
      if label ~= "" then
        return label
      end
    end
    return fallbackLabels[resolvedKey] or fallbackLabel
  end

  local function GetComparisonColor(comparison)
    if type(GetProfileSyncComparisonColor) == "function" then
      return GetProfileSyncComparisonColor(comparison)
    end
    return GetStatusColor(comparison)
  end

  local function GetDomainColor(state)
    if type(GetProfileSyncDomainStateColor) == "function" then
      return GetProfileSyncDomainStateColor(state)
    end
    return GetStatusColor(state)
  end

  local function GetBootstrapColor(state)
    if type(GetProfileSyncBootstrapStateColor) == "function" then
      return GetProfileSyncBootstrapStateColor(state)
    end
    return GetStatusColor(state)
  end

  local function GetComparisonKey(candidate)
    candidate = type(candidate) == "table" and candidate or {}
    local cachedKey = string.lower(TrimText(candidate.__dashboardComparisonKey))
    if cachedKey ~= "" then
      return cachedKey
    end
    local effectiveComparison = TrimText(candidate.effectiveComparison)
    return string.lower(effectiveComparison ~= "" and effectiveComparison or TrimText(candidate.comparison))
  end

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

  local function GetDomainState(candidate, domain)
    candidate = type(candidate) == "table" and candidate or {}
    local cachedStates = type(candidate.__dashboardDomainStates) == "table" and candidate.__dashboardDomainStates or nil
    local cachedState = type(cachedStates) == "table" and cachedStates[string.lower(TrimText(domain))] or nil
    if TrimText(cachedState) ~= "" then
      return TrimText(cachedState)
    end
    if type(GetProfileSyncDomainState) == "function" then
      local resolvedState = TrimText(GetProfileSyncDomainState(candidate, domain))
      if resolvedState ~= "" then
        return resolvedState
      end
    end
    local comparison = GetComparisonKey(candidate)
    if comparison == "different_lineage" then
      return "lineage"
    end
    if string.lower(TrimText(domain)) == "loot"
      and string.lower(TrimText(candidate.visibleState)) == "background"
    then
      return "background"
    end
    local remote = HasDomain(
      type(candidate.effectiveRemoteNewerDomains) == "table" and candidate.effectiveRemoteNewerDomains or candidate.remoteNewerDomains,
      domain
    )
    local localNewer = HasDomain(
      type(candidate.effectiveLocalNewerDomains) == "table" and candidate.effectiveLocalNewerDomains or candidate.localNewerDomains,
      domain
    )
    if remote and localNewer then
      return "conflict"
    elseif remote then
      return "member_newer"
    elseif localNewer then
      return "host_newer"
    end
    return "same"
  end

  local function GetDomainLabel(state)
    return ResolveSharedOrFallbackLabel(
      GetProfileSyncDomainStateLabel,
      state,
      FALLBACK_DOMAIN_LABELS,
      "Unknown"
    )
  end

  local function GetComparisonLabel(comparison)
    return ResolveSharedOrFallbackLabel(
      GetProfileSyncComparisonLabel,
      comparison,
      FALLBACK_COMPARISON_LABELS,
      "Unknown"
    )
  end

  local function GetBootstrapState(candidate)
    candidate = type(candidate) == "table" and candidate or {}
    local dashboardBootstrapState = TrimText(candidate.__dashboardBootstrapState)
    local state = string.lower(dashboardBootstrapState ~= "" and dashboardBootstrapState or TrimText(candidate.bootstrapState))
    if state ~= "" then
      return state
    end
    if GetComparisonKey(candidate) == "different_lineage" then
      return "lineage"
    elseif GetComparisonKey(candidate) == "unknown" or TrimText(candidate.profileId) == "" then
      return "pending"
    end
    local settingsState = GetDomainState(candidate, "settings")
    local permissionsState = GetDomainState(candidate, "permissions")
    if settingsState == "same" and permissionsState == "same" then
      return "ready"
    end
    if settingsState == "member_newer" or settingsState == "conflict"
        or permissionsState == "member_newer" or permissionsState == "conflict" then
      return "review"
    end
    if settingsState == "host_newer" or permissionsState == "host_newer" then
      return "pending"
    end
    return "unknown"
  end

  local function GetBootstrapLabel(state)
    return ResolveSharedOrFallbackLabel(
      GetProfileSyncBootstrapLabel,
      state,
      FALLBACK_BOOTSTRAP_LABELS,
      "Bootstrap Unknown"
    )
  end

  local function GetBootstrapProgress(candidate)
    candidate = type(candidate) == "table" and candidate or {}
    local bootstrapState = GetBootstrapState(candidate)
    if bootstrapState == "ready" then
      return "Roll-Ready", "ready"
    end
    local progressLabel = TrimText(candidate.__dashboardBootstrapProgress)
    if progressLabel ~= "" then
      return progressLabel, TrimText(candidate.__dashboardBootstrapProgressColor)
    end
    if string.lower(TrimText(candidate.visibleState)) == "waiting_apply"
      or string.upper(TrimText(candidate.applyAckPhase)) == "QUEUED"
    then
      return "Received; waiting for combat", "waiting_apply"
    end
    return GetBootstrapLabel(bootstrapState), bootstrapState
  end

  local function GetArchiveState(candidate)
    candidate = type(candidate) == "table" and candidate or {}
    local state = string.lower(TrimText(candidate.__dashboardArchiveState))
    if state ~= "" then
      return state
    end
    if candidate.backgroundOnlyLootDifference == true then
      return "backlog"
    end
    return GetComparisonKey(candidate) == "same" and "aligned" or "unknown"
  end

  local function GetArchivePhase(candidate)
    candidate = type(candidate) == "table" and candidate or {}
    return string.lower(TrimText(candidate.__dashboardArchivePhase))
  end

  local function FormatArchiveAge(seconds)
    seconds = math.max(0, tonumber(seconds) or 0)
    if seconds >= 120 then
      return tostring(math.floor(seconds / 60)) .. "m"
    end
    return tostring(math.ceil(seconds)) .. "s"
  end

  local function GetActionLabel(candidate, isHost)
    candidate = type(candidate) == "table" and candidate or {}
    local cachedLabel = TrimText(candidate.__dashboardActionLabel)
    if cachedLabel ~= "" then
      return cachedLabel
    end
    local recommendation = type(candidate.safeRecommendation) == "table" and candidate.safeRecommendation or nil
    if isHost and type(recommendation) == "table" and TrimText(recommendation.label) ~= "" then
      return TrimText(recommendation.label)
    end
    local comparison = GetComparisonKey(candidate)
    if comparison == "same" then
      return "No action"
    elseif comparison == "local_newer" then
      if isHost then
        return candidate.precisePushReady and "Push precise" or "Push host repair"
      end
      return "Wait for host push"
    elseif comparison == "remote_newer" then
      if isHost then
        return candidate.precisePullReady and "Pull precise" or "Pull member repair"
      end
      return "Host may pull"
    elseif comparison == "diverged" then
      if isHost then
        return candidate.precisePreviewReady and "Preview precise" or "Preview first"
      end
      return "Review with host"
    elseif comparison == "different_lineage" then
      return "Full snapshot"
    end
    return "Inspect"
  end

  local function ShowStateHelpTooltip(owner, isHost)
    if not owner or type(GameTooltip) ~= "table" then
      return
    end
    GameTooltip:SetOwner(owner, "ANCHOR_RIGHT")
    GameTooltip:ClearLines()
    GameTooltip:AddLine("Sync State Help", 1.00, 0.82, 0.28, true)
    GameTooltip:AddLine("Host Newer: the session host has the newer shared-profile data for that scope. The safe direction is usually Push Host Repair.", 0.92, 0.92, 0.96, true)
    GameTooltip:AddLine("Member Newer: the selected member has the newer shared-profile data for that scope. The host can Preview or Pull that repair.", 0.92, 0.92, 0.96, true)
    GameTooltip:AddLine("Conflict: both sides changed the compared scope. Preview first, inspect the exact differences, and then decide what to merge.", 0.92, 0.92, 0.96, true)
    GameTooltip:AddLine("Lineage: the shared profile identity/profile ID is different. This is not just older or newer data; it is a different profile family and should be reviewed carefully before overwrite.", 0.92, 0.92, 0.96, true)
    GameTooltip:AddLine("Current Session State: canonical active lobby/session data. It is separate from completed Session History.", 0.72, 0.82, 0.95, true)
    GameTooltip:AddLine("Session History: completed-session records delivered as optional P3 background archive data.", 0.72, 0.82, 0.95, true)
    GameTooltip:AddLine("P3 Delivery: queued, sending, receipt-pending, or apply-pending transport work. A pending receipt is delivery evidence, not by itself a data mismatch.", 0.72, 0.82, 0.95, true)
    if not isHost then
      GameTooltip:AddLine("Non-host note: your view can use Local Newer / Remote Newer instead of Host Newer / Member Newer.", 0.72, 0.82, 0.95, true)
    end
    GameTooltip:Show()
  end

  local function BuildSummaryText(candidates, isHost, syncStatus)
    local counts = {
      same = 0,
      local_newer = 0,
      remote_newer = 0,
      diverged = 0,
      different_lineage = 0,
    }
    local bootstrapCounts = {
      ready = 0,
      pending = 0,
      review = 0,
      lineage = 0,
      declined = 0,
      unknown = 0,
    }
    local archiveCounts = {
      queued = 0,
      sending = 0,
      waiting_ack = 0,
      waiting_apply = 0,
      ack_timeout = 0,
    }
    for _, candidate in ipairs(type(candidates) == "table" and candidates or {}) do
      local comparison = GetComparisonKey(candidate)
      if counts[comparison] ~= nil then
        counts[comparison] = counts[comparison] + 1
      end
      local bootstrapState = GetBootstrapState(candidate)
      if bootstrapCounts[bootstrapState] ~= nil then
        bootstrapCounts[bootstrapState] = bootstrapCounts[bootstrapState] + 1
      else
        bootstrapCounts.unknown = bootstrapCounts.unknown + 1
      end
      local archivePhase = GetArchivePhase(candidate)
      if archiveCounts[archivePhase] ~= nil then
        archiveCounts[archivePhase] = archiveCounts[archivePhase] + 1
      end
    end
    local archiveParts = {}
    local archiveLabels = {
      queued = "queued",
      sending = "sending",
      waiting_ack = "receipt pending",
      waiting_apply = "waiting for combat",
      ack_timeout = "recovering",
    }
    for _, phase in ipairs({ "queued", "sending", "waiting_ack", "waiting_apply", "ack_timeout" }) do
      local count = math.max(0, tonumber(archiveCounts[phase]) or 0)
      if count > 0 then
        archiveParts[#archiveParts + 1] = tostring(count) .. " " .. tostring(archiveLabels[phase])
      end
    end
    local archiveSummary = #archiveParts > 0
      and ("P3 delivery: " .. table.concat(archiveParts, ", ") .. ".")
      or "P3 delivery: no active transfer or receipt wait."
    syncStatus = type(syncStatus) == "table" and syncStatus or {}
    if (type(candidates) ~= "table") or #candidates == 0 then
      return "No joined member comparisons are available yet. Accept joins first, then refresh the difference table."
    end
    if isHost then
      return string.format(
        "%d roll-ready, %d waiting bootstrap, %d need review, %d aligned, %d need host repair, %d newer member copies, %d conflicts, %d lineage mismatch(es).",
        bootstrapCounts.ready,
        bootstrapCounts.pending,
        bootstrapCounts.review,
        counts.same,
        counts.local_newer,
        counts.remote_newer,
        counts.diverged,
        counts.different_lineage
      ) .. "\n" .. archiveSummary
    end
    local summary = string.format(
      "%d roll-ready, %d waiting bootstrap, %d need review, %d aligned with your local copy, %d remote copies are newer, %d local copies are newer, %d conflicts, %d lineage mismatch(es).",
      bootstrapCounts.ready,
      bootstrapCounts.pending,
      bootstrapCounts.review,
      counts.same,
      counts.remote_newer,
      counts.local_newer,
      counts.diverged,
      counts.different_lineage
    )
    summary = summary .. "\n" .. archiveSummary
    local message = TrimText(syncStatus.message)
    if message ~= "" then
      summary = summary .. "\nBackground: " .. message
    elseif counts.local_newer > 0 then
      summary = summary .. "\nBackground: wait for the session host to keep pushing the newer shared-profile scopes."
    elseif counts.remote_newer > 0 then
      summary = summary .. "\nBackground: your local copy looks newer for some scopes; the host can preview or pull them."
    end
    return summary
  end

  local function EnsureDashboard(editor)
    if not editor then
      return nil
    end
    if editor.lootSessionSyncDashboardFrame then
      return editor.lootSessionSyncDashboardFrame
    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 summaryText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    summaryText:SetJustifyH("LEFT")
    summaryText:SetJustifyV("TOP")
    frame.summaryText = summaryText

    frame.cards = {}
    for _ = 1, 7 do
      local card = CreateFrame("Frame", nil, frame, "BackdropTemplate")
      ApplyFrameSkin(card)
      if card.SetBackdropColor then
        card:SetBackdropColor(0.07, 0.08, 0.11, 0.96)
      end
      local countText = card:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
      countText:SetJustifyH("LEFT")
      local labelText = card:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      labelText:SetJustifyH("LEFT")
      local dot = card:CreateTexture(nil, "ARTWORK")
      dot:SetSize(8, 8)
      card.countText = countText
      card.labelText = labelText
      card.dot = dot
      frame.cards[#frame.cards + 1] = card
    end

    local matrixLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    matrixLabel:SetJustifyH("LEFT")
    matrixLabel:SetText("Joined Copy Matrix and Roll-Ready State")
    frame.matrixLabel = matrixLabel

    local matrixInfoBtn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
    matrixInfoBtn:SetSize(18, 18)
    matrixInfoBtn:SetText("?")
    matrixInfoBtn:SetScript("OnEnter", function(self)
      ShowStateHelpTooltip(self, frame.__isHost)
    end)
    matrixInfoBtn:SetScript("OnLeave", function()
      if type(GameTooltip) == "table" then
        GameTooltip:Hide()
      end
    end)
    frame.matrixInfoBtn = matrixInfoBtn

    local columns = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    ApplyFrameSkin(columns)
    if columns.SetBackdropColor then
      columns:SetBackdropColor(0.07, 0.08, 0.11, 0.98)
    end
    frame.columns = columns

    local headerKeys = {
      { key = "member", text = "Member" },
      { key = "overall", text = "Overall" },
      { key = "ready", text = "Bootstrap Phase" },
      { key = "settings", text = "Settings" },
      { key = "permissions", text = "Permissions" },
      { key = "loot", text = "Loot" },
      { key = "session", text = "Current" },
      { key = "action", text = "Next Step" },
      { key = "archive", text = "P3 Delivery" },
    }
    frame.headers = {}
    for _, headerDef in ipairs(headerKeys) do
      local text = columns:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      text:SetJustifyH("LEFT")
      text:SetText(headerDef.text)
      frame.headers[headerDef.key] = text
    end

    local rowsFrame = CreateFrame("Frame", nil, frame)
    frame.rowsFrame = rowsFrame
    frame.rows = {}

    local emptyText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    emptyText:SetJustifyH("LEFT")
    emptyText:SetJustifyV("TOP")
    emptyText:SetText("No joined member profile comparisons are available yet.")
    frame.emptyText = emptyText

    editor.lootSessionSyncDashboardFrame = frame
    return frame
  end

  local function EnsureRows(editor, frame, count)
    frame.rows = frame.rows or {}
    while #frame.rows < count do
      local row = CreateFrame("Frame", nil, frame.rowsFrame, "BackdropTemplate")
      ApplyFrameSkin(row)
      if row.SetBackdropColor then
        row:SetBackdropColor(0.06, 0.07, 0.10, 0.96)
      end
      row:EnableMouse(true)
      row:SetScript("OnMouseUp", function(self)
        local candidate = self.__candidate
        if type(candidate) == "table" then
          editor.lootSessionSelectedSyncMember = TrimText(candidate.memberKey)
          if type(OpenProfileSyncDetail) == "function" then
            OpenProfileSyncDetail(editor, candidate)
          end
        end
      end)
      row:SetScript("OnEnter", function(self)
        local tooltipText = TrimText(self.__archiveTooltip)
        if tooltipText == "" or type(GameTooltip) ~= "table" then
          return
        end
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:ClearLines()
        GameTooltip:AddLine("P3 Archive", 1.00, 0.82, 0.28, true)
        GameTooltip:AddLine(tooltipText, 0.92, 0.92, 0.96, true)
        GameTooltip:AddLine("Click to open Sync Detail.", 0.72, 0.82, 0.95, true)
        GameTooltip:Show()
      end)
      row:SetScript("OnLeave", function()
        if type(GameTooltip) == "table" then
          GameTooltip:Hide()
        end
      end)

      row.memberText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.memberText:SetJustifyH("LEFT")
      row.overallText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.overallText:SetJustifyH("LEFT")
      row.readyText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.readyText:SetJustifyH("LEFT")
      row.settingsText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.settingsText:SetJustifyH("LEFT")
      row.permissionsText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.permissionsText:SetJustifyH("LEFT")
      row.lootText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.lootText:SetJustifyH("LEFT")
      row.sessionText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.sessionText:SetJustifyH("LEFT")
      row.actionText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.actionText:SetJustifyH("LEFT")
      row.archiveText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      row.archiveText:SetJustifyH("LEFT")
      frame.rows[#frame.rows + 1] = row
    end
  end

  local function RefreshSyncDashboard(editor, model)
    local frame = EnsureDashboard(editor)
    if not frame then
      return
    end

    model = type(model) == "table" and model or {}
    local candidates = type(model.candidates) == "table" and model.candidates or {}
    local isHost = model.isHost and true or false
    frame.__isHost = isHost
    frame.summaryText:SetText(BuildSummaryText(candidates, isHost, model.syncStatus))

    local cards = {
      { label = GetBootstrapLabel("ready"), count = 0, color = "ready", colorType = "bootstrap" },
      { label = "P3 Data Aligned", count = 0, color = "same", colorType = "comparison" },
      { label = "P3 Delivery", count = 0, color = "background", colorType = "status" },
      { label = GetComparisonLabel("local_newer"), count = 0, color = "local_newer", colorType = "comparison" },
      { label = GetComparisonLabel("remote_newer"), count = 0, color = "remote_newer", colorType = "comparison" },
      { label = GetComparisonLabel("diverged"), count = 0, color = "diverged", colorType = "comparison" },
      { label = GetComparisonLabel("different_lineage"), count = 0, color = "different_lineage", colorType = "comparison" },
    }

    for _, candidate in ipairs(candidates) do
      if GetBootstrapState(candidate) == "ready" then
        cards[1].count = cards[1].count + 1
      end
      local archiveState = GetArchiveState(candidate)
      if archiveState == "aligned" then
        cards[2].count = cards[2].count + 1
      elseif archiveState == "backlog" then
        cards[3].count = cards[3].count + 1
      end
      local comparison = GetComparisonKey(candidate)
      if comparison == "local_newer" then
        cards[4].count = cards[4].count + 1
      elseif comparison == "remote_newer" then
        cards[5].count = cards[5].count + 1
      elseif comparison == "diverged" then
        cards[6].count = cards[6].count + 1
      elseif comparison == "different_lineage" then
        cards[7].count = cards[7].count + 1
      end
    end

    for index, card in ipairs(frame.cards or {}) do
      local data = cards[index] or {}
      local r, g, b
      if data.colorType == "bootstrap" then
        r, g, b = GetBootstrapColor(data.color)
      elseif data.colorType == "status" then
        r, g, b = GetStatusColor(data.color)
      else
        r, g, b = GetComparisonColor(data.color)
      end
      if card.countText then
        card.countText:SetText(tostring(data.count or 0))
        card.countText:SetTextColor(r, g, b, 1.00)
      end
      if card.labelText then
        card.labelText:SetText(tostring(data.label or ""))
        card.labelText:SetTextColor(0.86, 0.88, 0.94, 1.00)
      end
      if card.dot and card.dot.SetColorTexture then
        card.dot:SetColorTexture(r, g, b, 1.00)
      end
      if card.SetBackdropBorderColor then
        card:SetBackdropBorderColor(r * 0.8, g * 0.8, b * 0.8, 0.9)
      end
    end

    EnsureRows(editor, frame, #candidates)
    for index, row in ipairs(frame.rows or {}) do
      local candidate = candidates[index]
      if candidate then
        row.__candidate = candidate
        local classR, classG, classB = GetSessionClassColor(candidate.classFile)
        local overallColorKey = GetComparisonKey(candidate)
        local overallR, overallG, overallB = GetComparisonColor(overallColorKey)
        local memberLabel = tostring(candidate.memberKey or "-")
        if candidate.isSessionHost then
          memberLabel = memberLabel .. " (Host)"
        end
        row.memberText:SetText(memberLabel)
        row.memberText:SetTextColor(classR, classG, classB, 1.00)
        row.overallText:SetText(GetComparisonLabel(overallColorKey))
        row.overallText:SetTextColor(overallR, overallG, overallB, 1.00)
        local bootstrapProgressLabel, bootstrapProgressColor = GetBootstrapProgress(candidate)
        row.readyText:SetText(bootstrapProgressLabel)
        do
          local readyR, readyG, readyB = GetStatusColor(bootstrapProgressColor)
          row.readyText:SetTextColor(readyR, readyG, readyB, 1.00)
        end

        for _, domain in ipairs(DOMAIN_ORDER) do
          local label = GetDomainLabel(GetDomainState(candidate, domain))
          local r, g, b = GetDomainColor(GetDomainState(candidate, domain))
          local field = row[domain .. "Text"]
          if field then
            field:SetText(label)
            field:SetTextColor(r, g, b, 1.00)
          end
        end

        row.actionText:SetText(GetActionLabel(candidate, isHost))
        row.actionText:SetTextColor(0.86, 0.88, 0.94, 1.00)
        local archiveLabel = TrimText(candidate.__dashboardArchiveProgress)
        local archiveColor = TrimText(candidate.__dashboardArchiveProgressColor)
        local archivePhase = GetArchivePhase(candidate)
        if archivePhase == "waiting_ack" then
          archiveLabel = "Receipt pending"
        elseif archivePhase == "waiting_apply" then
          archiveLabel = "Receipt confirmed; apply pending"
        end
        if archiveLabel == "" then
          archiveLabel = "No receipt pending"
          archiveColor = "unknown"
        end
        row.archiveText:SetText(archiveLabel)
        do
          local archiveR, archiveG, archiveB = GetStatusColor(archiveColor)
          row.archiveText:SetTextColor(archiveR, archiveG, archiveB, 1.00)
        end
        local archiveAgeSeconds = math.max(0, tonumber(candidate.__dashboardArchiveAgeSeconds) or 0)
        local archiveScope = TrimText(candidate.__dashboardArchiveScope)
        if archiveAgeSeconds > 0 then
          row.__archiveTooltip = (archiveScope ~= "" and archiveScope or "P3")
            .. " delivery has been active for " .. FormatArchiveAge(archiveAgeSeconds)
            .. ". Current state: " .. archiveLabel .. "."
        elseif GetArchiveState(candidate) == "aligned" then
          row.__archiveTooltip = "This member's P3 archive data is aligned and no P3 transfer or receipt wait is active."
        else
          row.__archiveTooltip = "Current P3 delivery state: " .. archiveLabel
            .. ". Open Sync Detail to inspect delivery scope and recent decisions."
        end

        local selected = string.lower(TrimText(editor.lootSessionSelectedSyncMember)) == string.lower(TrimText(candidate.memberKey))
        if row.SetBackdropBorderColor then
          if selected then
            row:SetBackdropBorderColor(1.00, 0.82, 0.32, 1.00)
          else
            row:SetBackdropBorderColor(overallR * 0.75, overallG * 0.75, overallB * 0.75, 0.75)
          end
        end
        row:Show()
      else
        row.__candidate = nil
        row:Hide()
      end
    end

    frame.__rowCount = #candidates
    if #candidates == 0 then
      frame.emptyText:Show()
      frame.rowsFrame:Hide()
    else
      frame.emptyText:Hide()
      frame.rowsFrame:Show()
    end
    frame.__rowsHeight = math.max(28, #candidates * 28)
  end

  local function LayoutSyncDashboard(editor, topOffset, rightContentWidth)
    local frame = EnsureDashboard(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.summaryText:ClearAllPoints()
    frame.summaryText:SetPoint("TOPLEFT", 14, -14)
    frame.summaryText:SetWidth(width - 28)
    local summaryHeight = math.max(34, math.ceil(frame.summaryText:GetStringHeight()))

    local cardsPerRow = width >= 760 and 5 or (width >= 620 and 4 or (width >= 480 and 3 or 2))
    local cardGap = 10
    local cardWidth = math.floor((width - 28 - ((cardsPerRow - 1) * cardGap)) / cardsPerRow)
    cardWidth = math.max(112, cardWidth)
    local cardHeight = 56
    local cardsTop = -14 - summaryHeight - 12
    local cardRows = 0
    for index, card in ipairs(frame.cards or {}) do
      local rowIndex = math.floor((index - 1) / cardsPerRow)
      local columnIndex = (index - 1) % cardsPerRow
      local x = 14 + (columnIndex * (cardWidth + cardGap))
      local y = cardsTop - (rowIndex * (cardHeight + 8))
      card:ClearAllPoints()
      card:SetPoint("TOPLEFT", x, y)
      card:SetSize(cardWidth, cardHeight)
      card.countText:ClearAllPoints()
      card.countText:SetPoint("TOPLEFT", 10, -10)
      card.labelText:ClearAllPoints()
      card.labelText:SetPoint("TOPLEFT", 10, -34)
      card.dot:ClearAllPoints()
      card.dot:SetPoint("TOPRIGHT", -10, -12)
      cardRows = math.max(cardRows, rowIndex + 1)
    end
    local cardsHeight = math.max(0, cardRows * cardHeight + math.max(0, cardRows - 1) * 8)

    local matrixTop = cardsTop - cardsHeight - 18
    frame.matrixLabel:ClearAllPoints()
    frame.matrixLabel:SetPoint("TOPLEFT", 14, matrixTop)
    if frame.matrixInfoBtn then
      frame.matrixInfoBtn:ClearAllPoints()
      frame.matrixInfoBtn:SetPoint("LEFT", frame.matrixLabel, "RIGHT", 6, 0)
    end

    local columnsTop = matrixTop - 26
    frame.columns:ClearAllPoints()
    frame.columns:SetPoint("TOPLEFT", 14, columnsTop)
    frame.columns:SetSize(width - 28, 22)

    local innerWidth = width - 28
    local memberWidth = math.max(122, math.floor(innerWidth * 0.18))
    local overallWidth = math.max(104, math.floor(innerWidth * 0.10))
    local readyWidth = math.max(116, math.floor(innerWidth * 0.08))
    local domainWidth = math.max(64, math.floor(innerWidth * 0.085))
    local actionWidth = math.max(96, math.floor(innerWidth * 0.15))
    local archiveWidth = math.max(84, innerWidth - memberWidth - overallWidth - readyWidth - (domainWidth * 4) - actionWidth - 18)

    local columnOffsets = {
      member = 8,
      overall = 8 + memberWidth,
      ready = 8 + memberWidth + overallWidth,
      settings = 8 + memberWidth + overallWidth + readyWidth,
      permissions = 8 + memberWidth + overallWidth + readyWidth + domainWidth,
      loot = 8 + memberWidth + overallWidth + readyWidth + (domainWidth * 2),
      session = 8 + memberWidth + overallWidth + readyWidth + (domainWidth * 3),
      action = 8 + memberWidth + overallWidth + readyWidth + (domainWidth * 4),
      archive = 8 + memberWidth + overallWidth + readyWidth + (domainWidth * 4) + actionWidth,
    }
    local columnWidths = {
      member = memberWidth,
      overall = overallWidth,
      ready = readyWidth,
      settings = domainWidth,
      permissions = domainWidth,
      loot = domainWidth,
      session = domainWidth,
      action = actionWidth,
      archive = archiveWidth,
    }

    for key, header in pairs(frame.headers or {}) do
      header:ClearAllPoints()
      header:SetPoint("TOPLEFT", columnOffsets[key] or 8, -4)
      header:SetWidth(columnWidths[key] or 80)
    end

    local rowsTop = columnsTop - 26
    frame.rowsFrame:ClearAllPoints()
    frame.rowsFrame:SetPoint("TOPLEFT", 14, rowsTop)
    frame.rowsFrame:SetSize(width - 28, math.max(1, tonumber(frame.__rowsHeight) or 28))

    for index, row in ipairs(frame.rows or {}) do
      row:ClearAllPoints()
      row:SetPoint("TOPLEFT", 0, -((index - 1) * 28))
      row:SetSize(width - 28, 24)
      for key, offset in pairs(columnOffsets) do
        local field = row[key .. "Text"]
        if field then
          field:ClearAllPoints()
          field:SetPoint("TOPLEFT", offset, -4)
          field:SetWidth(columnWidths[key] or 80)
        end
      end
    end

    frame.emptyText:ClearAllPoints()
    frame.emptyText:SetPoint("TOPLEFT", 14, rowsTop)
    frame.emptyText:SetWidth(width - 28)
    local emptyHeight = math.max(28, math.ceil(frame.emptyText:GetStringHeight()))
    local tableHeight = (tonumber(frame.__rowCount) or 0) > 0 and (tonumber(frame.__rowsHeight) or 28) or emptyHeight
    local totalHeight = math.abs(rowsTop) + tableHeight + 16
    frame:SetHeight(totalHeight)
    return totalHeight
  end

  return {
    RefreshSyncDashboard = RefreshSyncDashboard,
    LayoutSyncDashboard = LayoutSyncDashboard,
  }
end
