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

  local ApplyFrameSkin = deps.ApplyFrameSkin
  local TrimText = deps.TrimText
  local ResolveSessionDisplayInfo = deps.ResolveSessionDisplayInfo
  local ResolveSessionSyncState = deps.ResolveSessionSyncState
  local ResolveSessionIconData = deps.ResolveSessionIconData
  local ApplySessionIcon = deps.ApplySessionIcon
  local GetSessionClassColor = deps.GetSessionClassColor
  local GetSessionStatusColor = deps.GetSessionStatusColor
  local GetTrafficLightColor = deps.GetTrafficLightColor
  local GetSessionSyncLabel = deps.GetSessionSyncLabel
  local ApplyTrafficLight = deps.ApplyTrafficLight
  local FormatDateTime = deps.FormatDateTime
  local FormatDuration = deps.FormatDuration

  local function ResolveSessionSourceLabel(session, selfCharacter)
    local selfKey = TrimText(selfCharacter)
    if TrimText(type(session) == "table" and session.host or "") == selfKey then
      return "Host"
    elseif type(session) == "table" and session.sharedByUpdate then
      return "Update Sync"
    elseif type(session) == "table" and session.acceptedBySelf == false then
      return "Observed"
    end
    return "Manual Join"
  end

  local function BuildSessionHistoryKey(session)
    return table.concat({
      TrimText(type(session) == "table" and session.id or ""),
      tostring(tonumber(type(session) == "table" and session.startedAt) or 0),
      TrimText(type(session) == "table" and session.host or ""),
      TrimText(type(session) == "table" and session.profileName or ""),
    }, "::")
  end

  local function BuildSessionAwardStatsByCharacter(session, profile)
    local stats = {}
    if type(session) ~= "table" or type(profile) ~= "table" then
      return stats
    end

    local sessionId = TrimText(session.id)
    if sessionId == "" then
      return stats
    end

    local sessionMarker = "Session " .. sessionId
    for characterKey, ledgerRow in pairs(profile.lootLedger or {}) do
      if type(ledgerRow) == "table" and type(ledgerRow.items) == "table" then
        local itemCount = 0
        local pointTotal = 0
        for _, item in ipairs(ledgerRow.items) do
          local note = TrimText(type(item) == "table" and item.note or "")
          if note ~= "" and string.find(note, sessionMarker, 1, true) then
            itemCount = itemCount + 1
            pointTotal = pointTotal + (tonumber(type(item) == "table" and item.points) or 0)
          end
        end
        if itemCount > 0 or pointTotal ~= 0 then
          stats[TrimText(characterKey)] = {
            itemCount = itemCount,
            pointTotal = pointTotal,
          }
        end
      end
    end

    return stats
  end

  local function BuildSessionParticipantRowsData(session, profile, selfCharacter)
    local rows = {}
    if type(session) ~= "table" or type(session.members) ~= "table" then
      return rows
    end

    local awardStatsByCharacter = BuildSessionAwardStatsByCharacter(session, profile)
    for memberKey, member in pairs(session.members or {}) do
      local resolvedKey = TrimText(memberKey ~= "" and memberKey or (type(member) == "table" and member.name) or "")
      if resolvedKey ~= "" then
        local displayInfo = ResolveSessionDisplayInfo(profile, resolvedKey, member)
        local awardStats = type(awardStatsByCharacter[resolvedKey]) == "table" and awardStatsByCharacter[resolvedKey] or nil
        rows[#rows + 1] = {
          key = resolvedKey,
          name = resolvedKey,
          status = TrimText(type(member) == "table" and member.status or "JOINED"),
          joinedAt = tonumber(type(member) == "table" and member.joinedAt) or nil,
          leftAt = tonumber(type(member) == "table" and member.leftAt) or nil,
          addonVersion = TrimText(type(member) == "table" and member.addonVersion or ""),
          clientVersion = TrimText(type(member) == "table" and member.clientVersion or ""),
          className = displayInfo.className,
          classFile = displayInfo.classFile,
          specName = displayInfo.specName,
          specIcon = displayInfo.specIcon,
          syncState = ResolveSessionSyncState(member, resolvedKey, session, selfCharacter),
          itemCount = tonumber(awardStats and awardStats.itemCount) or 0,
          pointTotal = tonumber(awardStats and awardStats.pointTotal) or 0,
          isHost = resolvedKey == TrimText(session.host),
          isSelf = resolvedKey == TrimText(selfCharacter),
        }
      end
    end

    table.sort(rows, function(a, b)
      if a.isHost ~= b.isHost then
        return a.isHost
      end
      local aStatus = string.upper(TrimText(a.status))
      local bStatus = string.upper(TrimText(b.status))
      if aStatus ~= bStatus then
        if aStatus == "JOINED" then
          return true
        elseif bStatus == "JOINED" then
          return false
        end
      end
      return string.lower(tostring(a.name or "")) < string.lower(tostring(b.name or ""))
    end)

    return rows
  end

  local function BuildSessionHistoryRowsData(profile, selfCharacter)
    local rows = {}
    for _, session in ipairs(type(profile) == "table" and profile.sessions or {}) do
      if type(session) == "table" and session.endedAt then
        local memberCount = 0
        for _, member in pairs(session.members or {}) do
          local status = string.upper(TrimText(type(member) == "table" and member.status or "JOINED"))
          if status ~= "DECLINED" then
            memberCount = memberCount + 1
          end
        end
        local hostKey = TrimText(session.host)
        local hostMember = type(session.members) == "table" and session.members[hostKey] or nil
        local displayInfo = ResolveSessionDisplayInfo(profile, hostKey, hostMember)
        rows[#rows + 1] = {
          key = BuildSessionHistoryKey(session),
          status = TrimText(session.status or "FINISHED"),
          startedAt = tonumber(session.startedAt) or 0,
          endedAt = tonumber(session.endedAt) or 0,
          durationSeconds = tonumber(session.durationSeconds) or 0,
          host = hostKey ~= "" and hostKey or "Unknown",
          memberCount = memberCount,
          sourceLabel = ResolveSessionSourceLabel(session, selfCharacter),
          className = displayInfo.className,
          classFile = displayInfo.classFile,
          specName = displayInfo.specName,
          specIcon = displayInfo.specIcon,
          participants = BuildSessionParticipantRowsData(session, profile, selfCharacter),
        }
      end
    end

    table.sort(rows, function(a, b)
      return (tonumber(a.startedAt) or 0) > (tonumber(b.startedAt) or 0)
    end)
    return rows
  end

  local function EnsureSessionHistoryRows(editor, historyRowsFrame, count)
    editor.lootSessionHistoryRows = editor.lootSessionHistoryRows or {}
    while #(editor.lootSessionHistoryRows or {}) < count do
      local row = CreateFrame("Frame", nil, historyRowsFrame, "BackdropTemplate")
      ApplyFrameSkin(row)
      row:SetHeight(60)
      row:EnableMouse(true)

      local icon = row:CreateTexture(nil, "ARTWORK")
      icon:SetSize(18, 18)
      row.icon = icon

      local titleText = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
      titleText:SetJustifyH("LEFT")
      if titleText.SetMaxLines then
        titleText:SetMaxLines(1)
      end
      row.hostText = titleText

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

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

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

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

      local statusDot = row:CreateTexture(nil, "ARTWORK")
      statusDot:SetSize(8, 8)
      row.statusDot = statusDot

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

      local detailsFrame = CreateFrame("Frame", nil, row, "BackdropTemplate")
      ApplyFrameSkin(detailsFrame)
      if detailsFrame.SetBackdropColor then
        detailsFrame:SetBackdropColor(0.05, 0.05, 0.07, 0.98)
      end
      detailsFrame:Hide()
      row.detailsFrame = detailsFrame

      local detailsTitle = detailsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      detailsTitle:SetJustifyH("LEFT")
      detailsTitle:SetText("Participants")
      row.detailsTitle = detailsTitle

      local detailsHeaderName = detailsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      detailsHeaderName:SetJustifyH("LEFT")
      detailsHeaderName:SetText("Raider")
      row.detailsHeaderName = detailsHeaderName

      local detailsHeaderSync = detailsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      detailsHeaderSync:SetJustifyH("LEFT")
      detailsHeaderSync:SetText("Sync")
      row.detailsHeaderSync = detailsHeaderSync

      local detailsHeaderVersion = detailsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      detailsHeaderVersion:SetJustifyH("LEFT")
      detailsHeaderVersion:SetText("Version")
      row.detailsHeaderVersion = detailsHeaderVersion

      local detailsHeaderItems = detailsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      detailsHeaderItems:SetJustifyH("LEFT")
      detailsHeaderItems:SetText("Items")
      row.detailsHeaderItems = detailsHeaderItems

      local detailsHeaderPoints = detailsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      detailsHeaderPoints:SetJustifyH("LEFT")
      detailsHeaderPoints:SetText("Points")
      row.detailsHeaderPoints = detailsHeaderPoints

      local detailsHeaderStatus = detailsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      detailsHeaderStatus:SetJustifyH("LEFT")
      detailsHeaderStatus:SetText("Status")
      row.detailsHeaderStatus = detailsHeaderStatus

      local detailsHeaderJoined = detailsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      detailsHeaderJoined:SetJustifyH("LEFT")
      detailsHeaderJoined:SetText("Joined")
      row.detailsHeaderJoined = detailsHeaderJoined

      local detailsHeaderLeft = detailsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
      detailsHeaderLeft:SetJustifyH("LEFT")
      detailsHeaderLeft:SetText("Left")
      row.detailsHeaderLeft = detailsHeaderLeft

      row.participantRows = {}

      row:SetScript("OnMouseUp", function(_, mouseButton)
        if mouseButton ~= "LeftButton" or TrimText(row.historyKey) == "" then
          return
        end
        editor.lootSessionHistoryExpanded = editor.lootSessionHistoryExpanded or {}
        editor.lootSessionHistoryExpanded[row.historyKey] = not editor.lootSessionHistoryExpanded[row.historyKey]
        if editor.RefreshLootSessionPage then
          editor:RefreshLootSessionPage()
        end
      end)

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

  local function EnsureSessionHistoryParticipantRows(row, count)
    if not row then
      return
    end
    row.participantRows = row.participantRows or {}
    while #(row.participantRows) < count do
      local participant = CreateFrame("Frame", nil, row.detailsFrame)
      participant:SetHeight(22)

      local icon = participant:CreateTexture(nil, "ARTWORK")
      icon:SetSize(16, 16)
      participant.icon = icon

      local nameText = participant:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      nameText:SetJustifyH("LEFT")
      if nameText.SetMaxLines then
        nameText:SetMaxLines(1)
      end
      participant.nameText = nameText

      local syncDot = participant:CreateTexture(nil, "ARTWORK")
      syncDot:SetSize(8, 8)
      participant.syncDot = syncDot

      local syncText = participant:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      syncText:SetJustifyH("LEFT")
      if syncText.SetMaxLines then
        syncText:SetMaxLines(1)
      end
      participant.syncText = syncText

      local versionText = participant:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      versionText:SetJustifyH("LEFT")
      if versionText.SetMaxLines then
        versionText:SetMaxLines(1)
      end
      participant.versionText = versionText

      local itemsText = participant:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      itemsText:SetJustifyH("LEFT")
      if itemsText.SetMaxLines then
        itemsText:SetMaxLines(1)
      end
      participant.itemsText = itemsText

      local pointsText = participant:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      pointsText:SetJustifyH("LEFT")
      if pointsText.SetMaxLines then
        pointsText:SetMaxLines(1)
      end
      participant.pointsText = pointsText

      local statusText = participant:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      statusText:SetJustifyH("LEFT")
      if statusText.SetMaxLines then
        statusText:SetMaxLines(1)
      end
      participant.statusText = statusText

      local joinedText = participant:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      joinedText:SetJustifyH("LEFT")
      if joinedText.SetMaxLines then
        joinedText:SetMaxLines(1)
      end
      participant.joinedText = joinedText

      local leftText = participant:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      leftText:SetJustifyH("LEFT")
      if leftText.SetMaxLines then
        leftText:SetMaxLines(1)
      end
      participant.leftText = leftText

      row.participantRows[#row.participantRows + 1] = participant
    end
  end

  local function RefreshSessionHistoryRows(editor, historyRowsData, historyExpanded)
    EnsureSessionHistoryRows(editor, editor.lootSessionHistoryRowsFrame, math.max(1, #(historyRowsData or {})))
    local historyRowsHeight = 0
    for index, row in ipairs(editor.lootSessionHistoryRows or {}) do
      local data = historyRowsData[index]
      if data then
        local iconData = ResolveSessionIconData(data)
        local r, g, b = GetSessionClassColor(data.classFile)
        local participants = type(data.participants) == "table" and data.participants or {}
        local isExpanded = historyExpanded[data.key] and true or false
        row.historyKey = data.key
        ApplySessionIcon(row.icon, iconData)
        row.startedText:SetText(FormatDateTime(data.startedAt))
        row.hostText:SetText(data.host)
        row.hostText:SetTextColor(r, g, b, 1)
        row.hostMetaText:SetText(string.format(
          "%s | %s",
          data.specName ~= "" and data.specName or (data.className ~= "" and data.className or "Unknown"),
          FormatDuration(data.durationSeconds)
        ))
        row.membersText:SetText(tostring(tonumber(data.memberCount) or 0))
        row.sourceText:SetText(data.sourceLabel)
        local historySyncState = data.status == "FINISHED" and "SYNCED" or "PARTIAL"
        local statusR, statusG, statusB = GetTrafficLightColor(historySyncState)
        ApplyTrafficLight(row.statusDot, historySyncState)
        row.statusText:SetText(TrimText(data.status) ~= "" and data.status or "UNKNOWN")
        row.statusText:SetTextColor(statusR, statusG, statusB, 1)
        if row.detailsFrame then
          if isExpanded and #participants > 0 then
            EnsureSessionHistoryParticipantRows(row, #participants)
            row.detailsFrame:Show()
            row.detailsTitle:Show()
            row.detailsHeaderName:Show()
            row.detailsHeaderSync:Show()
            row.detailsHeaderVersion:Show()
            row.detailsHeaderItems:Show()
            row.detailsHeaderPoints:Show()
            row.detailsHeaderStatus:Show()
            row.detailsHeaderJoined:Show()
            if row.detailsHeaderLeft then row.detailsHeaderLeft:Show() end
            for participantIndex, participantRow in ipairs(row.participantRows or {}) do
              local participantData = participants[participantIndex]
              if participantData then
                local participantIconData = ResolveSessionIconData(participantData)
                local pr, pg, pb = GetSessionClassColor(participantData.classFile)
                local psr, psg, psb = GetTrafficLightColor(participantData.syncState)
                local ptr, ptg, ptb = GetSessionStatusColor(participantData.status)
                local participantLabel = participantData.name
                if participantData.isHost then
                  participantLabel = participantLabel .. " | Host"
                elseif participantData.isSelf then
                  participantLabel = participantLabel .. " | You"
                end
                ApplySessionIcon(participantRow.icon, participantIconData)
                participantRow.nameText:SetText(participantLabel)
                participantRow.nameText:SetTextColor(pr, pg, pb, 1)
                ApplyTrafficLight(participantRow.syncDot, participantData.syncState)
                participantRow.syncText:SetText(GetSessionSyncLabel(participantData.syncState))
                participantRow.syncText:SetTextColor(psr, psg, psb, 1)
                participantRow.versionText:SetText(TrimText(participantData.addonVersion) ~= "" and participantData.addonVersion or "Legacy")
                participantRow.itemsText:SetText(tostring(tonumber(participantData.itemCount) or 0))
                participantRow.pointsText:SetText(tostring(tonumber(participantData.pointTotal) or 0))
                participantRow.statusText:SetText(TrimText(participantData.status) ~= "" and participantData.status or "JOINED")
                participantRow.statusText:SetTextColor(ptr, ptg, ptb, 1)
                participantRow.joinedText:SetText(participantData.joinedAt and FormatDateTime(participantData.joinedAt) or "-")
                if participantRow.leftText then
                  participantRow.leftText:SetText(participantData.leftAt and FormatDateTime(participantData.leftAt) or "-")
                end
                participantRow:Show()
              else
                participantRow:Hide()
              end
            end
          else
            row.detailsFrame:Hide()
            if row.participantRows then
              for _, participantRow in ipairs(row.participantRows) do
                participantRow:Hide()
              end
            end
          end
        end
        local detailsHeight = 0
        if isExpanded and #participants > 0 then
          detailsHeight = 54 + (#participants * 24)
        end
        row.layoutHeight = isExpanded and (66 + detailsHeight) or 60
        historyRowsHeight = historyRowsHeight + row.layoutHeight + 6
        row:Show()
      else
        row.historyKey = nil
        row.layoutHeight = 60
        row:Hide()
      end
    end

    editor.lootSessionHistoryRowsHeight = math.max(0, historyRowsHeight > 0 and (historyRowsHeight - 6) or 0)
    return editor.lootSessionHistoryRowsHeight
  end

  local function LayoutSessionHistorySection(self, rightContentWidth)
    self.lootSessionHistoryHeader:ClearAllPoints()
    self.lootSessionHistoryHeader:SetPoint("TOPLEFT", 18, -78)

    local rowWidth = rightContentWidth
    local resultWidth = 96
    local sourceWidth = 108
    local membersWidth = 72
    local startedWidth = 126
    local hostX = startedWidth + 44
    local membersX = rowWidth - resultWidth - sourceWidth - membersWidth - 20
    local sourceX = rowWidth - resultWidth - sourceWidth - 12
    local resultX = rowWidth - resultWidth - 12
    local hostWidth = math.max(180, membersX - hostX - 12)

    self.lootSessionHistoryColumns:ClearAllPoints()
    self.lootSessionHistoryColumns:SetPoint("TOPLEFT", 18, -104)
    self.lootSessionHistoryColumns:SetSize(rowWidth, 22)
    self.lootSessionHistoryHeaderStarted:ClearAllPoints()
    self.lootSessionHistoryHeaderStarted:SetPoint("TOPLEFT", 10, -4)
    self.lootSessionHistoryHeaderStarted:SetWidth(startedWidth - 12)
    self.lootSessionHistoryHeaderHost:ClearAllPoints()
    self.lootSessionHistoryHeaderHost:SetPoint("TOPLEFT", hostX, -4)
    self.lootSessionHistoryHeaderHost:SetWidth(hostWidth)
    self.lootSessionHistoryHeaderMembers:ClearAllPoints()
    self.lootSessionHistoryHeaderMembers:SetPoint("TOPLEFT", membersX, -4)
    self.lootSessionHistoryHeaderMembers:SetWidth(membersWidth)
    self.lootSessionHistoryHeaderSource:ClearAllPoints()
    self.lootSessionHistoryHeaderSource:SetPoint("TOPLEFT", sourceX, -4)
    self.lootSessionHistoryHeaderSource:SetWidth(sourceWidth)
    self.lootSessionHistoryHeaderResult:ClearAllPoints()
    self.lootSessionHistoryHeaderResult:SetPoint("TOPLEFT", resultX + 12, -4)
    self.lootSessionHistoryHeaderResult:SetWidth(resultWidth - 12)

    local historyRowsHeight = tonumber(self.lootSessionHistoryRowsHeight) or 0
    self.lootSessionHistoryRowsFrame:ClearAllPoints()
    self.lootSessionHistoryRowsFrame:SetPoint("TOPLEFT", 18, -132)
    self.lootSessionHistoryRowsFrame:SetSize(rightContentWidth, math.max(1, historyRowsHeight))

    local runningTop = 0
    for index, row in ipairs(self.lootSessionHistoryRows or {}) do
      local rowHeight = tonumber(row.layoutHeight) or 60
      row:ClearAllPoints()
      row:SetPoint("TOPLEFT", 0, -runningTop)
      row:SetWidth(rightContentWidth)
      row:SetHeight(rowHeight)
      row.icon:ClearAllPoints()
      row.icon:SetPoint("TOPLEFT", hostX - 26, -10)
      row.startedText:ClearAllPoints()
      row.startedText:SetPoint("TOPLEFT", 10, -18)
      row.startedText:SetWidth(startedWidth - 12)
      row.hostText:ClearAllPoints()
      row.hostText:SetPoint("TOPLEFT", hostX, -8)
      row.hostText:SetWidth(hostWidth)
      row.hostMetaText:ClearAllPoints()
      row.hostMetaText:SetPoint("TOPLEFT", hostX, -26)
      row.hostMetaText:SetWidth(hostWidth)
      row.membersText:ClearAllPoints()
      row.membersText:SetPoint("TOPLEFT", membersX, -18)
      row.membersText:SetWidth(membersWidth)
      row.sourceText:ClearAllPoints()
      row.sourceText:SetPoint("TOPLEFT", sourceX, -18)
      row.sourceText:SetWidth(sourceWidth)
      row.statusDot:ClearAllPoints()
      row.statusDot:SetPoint("TOPLEFT", resultX, -18)
      row.statusText:ClearAllPoints()
      row.statusText:SetPoint("LEFT", row.statusDot, "RIGHT", 6, 0)
      row.statusText:SetWidth(resultWidth - 12)
      if row.detailsFrame then
        row.detailsFrame:ClearAllPoints()
        row.detailsFrame:SetPoint("TOPLEFT", 10, -58)
        row.detailsFrame:SetPoint("TOPRIGHT", -10, -58)
        row.detailsFrame:SetHeight(math.max(1, rowHeight - 66))
        row.detailsTitle:ClearAllPoints()
        row.detailsTitle:SetPoint("TOPLEFT", 10, -10)

        local detailsWidth = math.max(220, ((row.GetWidth and row:GetWidth()) or rightContentWidth) - 40)
        local showLeftColumn = detailsWidth >= 620
        local showJoinedColumn = detailsWidth >= 520
        local showStatusColumn = detailsWidth >= 460
        local detailLeftWidth = showLeftColumn and 112 or 0
        local detailJoinedWidth = showJoinedColumn and 112 or 0
        local detailStatusWidth = showStatusColumn and 72 or 0
        local detailPointsWidth = 52
        local detailItemsWidth = 46
        local detailNameX = 26
        local detailLeftX = detailsWidth - detailLeftWidth - 10
        local detailJoinedX = detailLeftX - (showJoinedColumn and (detailJoinedWidth + 8) or 0)
        local detailStatusX = detailJoinedX - (showStatusColumn and (detailStatusWidth + 8) or 0)
        local detailPointsX = detailStatusX - detailPointsWidth - 8
        local detailItemsX = detailPointsX - detailItemsWidth - 8
        local detailNameWidth = math.max(120, detailItemsX - detailNameX - 16)

        row.detailsHeaderName:ClearAllPoints()
        row.detailsHeaderName:SetPoint("TOPLEFT", detailNameX, -30)
        row.detailsHeaderName:SetWidth(detailNameWidth)
        row.detailsHeaderSync:SetShown(false)
        row.detailsHeaderVersion:SetShown(false)
        row.detailsHeaderItems:ClearAllPoints()
        row.detailsHeaderItems:SetPoint("TOPLEFT", detailItemsX, -30)
        row.detailsHeaderItems:SetWidth(detailItemsWidth)
        row.detailsHeaderItems:SetShown(true)
        row.detailsHeaderPoints:ClearAllPoints()
        row.detailsHeaderPoints:SetPoint("TOPLEFT", detailPointsX, -30)
        row.detailsHeaderPoints:SetWidth(detailPointsWidth)
        row.detailsHeaderPoints:SetShown(true)
        row.detailsHeaderStatus:ClearAllPoints()
        row.detailsHeaderStatus:SetPoint("TOPLEFT", detailStatusX, -30)
        row.detailsHeaderStatus:SetWidth(detailStatusWidth)
        row.detailsHeaderStatus:SetShown(showStatusColumn)
        row.detailsHeaderJoined:ClearAllPoints()
        row.detailsHeaderJoined:SetPoint("TOPLEFT", detailJoinedX, -30)
        row.detailsHeaderJoined:SetWidth(detailJoinedWidth)
        row.detailsHeaderJoined:SetShown(showJoinedColumn)
        if row.detailsHeaderLeft then
          row.detailsHeaderLeft:ClearAllPoints()
          row.detailsHeaderLeft:SetPoint("TOPLEFT", detailLeftX, -30)
          row.detailsHeaderLeft:SetWidth(detailLeftWidth)
          row.detailsHeaderLeft:SetShown(showLeftColumn)
        end

        for participantIndex, participantRow in ipairs(row.participantRows or {}) do
          participantRow:ClearAllPoints()
          participantRow:SetPoint("TOPLEFT", 10, -(48 + ((participantIndex - 1) * 24)))
          participantRow:SetPoint("TOPRIGHT", -10, -(48 + ((participantIndex - 1) * 24)))
          participantRow.icon:ClearAllPoints()
          participantRow.icon:SetPoint("TOPLEFT", 0, -3)
          participantRow.nameText:ClearAllPoints()
          participantRow.nameText:SetPoint("LEFT", participantRow.icon, "RIGHT", 8, 0)
          participantRow.nameText:SetWidth(math.max(96, detailNameWidth - 24))
          participantRow.syncDot:Hide()
          participantRow.syncText:Hide()
          participantRow.versionText:Hide()
          participantRow.itemsText:ClearAllPoints()
          participantRow.itemsText:SetPoint("TOPLEFT", detailItemsX, 0)
          participantRow.itemsText:SetWidth(detailItemsWidth)
          participantRow.pointsText:ClearAllPoints()
          participantRow.pointsText:SetPoint("TOPLEFT", detailPointsX, 0)
          participantRow.pointsText:SetWidth(detailPointsWidth)
          participantRow.statusText:ClearAllPoints()
          participantRow.statusText:SetPoint("TOPLEFT", detailStatusX, 0)
          participantRow.statusText:SetWidth(detailStatusWidth)
          participantRow.statusText:SetShown(showStatusColumn)
          participantRow.joinedText:ClearAllPoints()
          participantRow.joinedText:SetPoint("TOPLEFT", detailJoinedX, 0)
          participantRow.joinedText:SetWidth(detailJoinedWidth)
          participantRow.joinedText:SetShown(showJoinedColumn)
          if participantRow.leftText then
            participantRow.leftText:ClearAllPoints()
            participantRow.leftText:SetPoint("TOPLEFT", detailLeftX, 0)
            participantRow.leftText:SetWidth(detailLeftWidth)
            participantRow.leftText:SetShown(showLeftColumn)
          end
        end
      end
      runningTop = runningTop + rowHeight + 6
    end

    return -140 - historyRowsHeight
  end

  return {
    BuildSessionHistoryRowsData = BuildSessionHistoryRowsData,
    RefreshSessionHistoryRows = RefreshSessionHistoryRows,
    LayoutSessionHistorySection = LayoutSessionHistorySection,
  }
end
