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

local shared = INTERNAL_API.LIDCOUNCIL_LootRuntimeUiShared_Impl
if type(shared) ~= "table" then
  error("LidCouncil missing loot runtime UI shared helpers.")
end

local ApplyFrameSkin = shared.ApplyFrameSkin
local GetLiveBoardHistoryEntries = shared.GetLiveBoardHistoryEntries
local TrimText = shared.TrimText
local FormatSelectionLabel = shared.FormatSelectionLabel
local ResolveBoardProfileName = shared.ResolveBoardProfileName
local UIState = shared.UIState

local HISTORY_LIST_LEFT = 16
local HISTORY_LIST_WIDTH = 220
local HISTORY_DETAIL_LEFT = 268
local HISTORY_DETAIL_RIGHT = -32
local HISTORY_DETAIL_WIDTH = 540

local function RaisePopupFrameAboveEditor(frame)
  if not frame then
    return
  end
  if frame.SetFrameStrata then
    frame:SetFrameStrata("FULLSCREEN_DIALOG")
  end
  if frame.SetToplevel then
    frame:SetToplevel(true)
  end
  local editor = rawget(_G, "LIDCOUNCIL_Editor")
  if editor and editor.GetFrameLevel and frame.SetFrameLevel then
    frame:SetFrameLevel((editor:GetFrameLevel() or 0) + 120)
  end
  if frame.Raise then
    frame:Raise()
  end
end

local function StripTemplateButtonTextures(button)
  if not button or button.__lid_historyTemplateStripped then
    return
  end

  button.__lid_historyTemplateStripped = true
  local normalTexture = button.GetNormalTexture and button:GetNormalTexture() or nil
  local pushedTexture = button.GetPushedTexture and button:GetPushedTexture() or nil
  local highlightTexture = button.GetHighlightTexture and button:GetHighlightTexture() or nil
  local disabledTexture = button.GetDisabledTexture and button:GetDisabledTexture() or nil

  if normalTexture and button.SetNormalTexture then button:SetNormalTexture("") end
  if pushedTexture and button.SetPushedTexture then button:SetPushedTexture("") end
  if highlightTexture and button.SetHighlightTexture then button:SetHighlightTexture("") end
  if disabledTexture and button.SetDisabledTexture then button:SetDisabledTexture("") end

  local regions = { button:GetRegions() }
  for _, region in ipairs(regions) do
    if region and region.GetObjectType and region:GetObjectType() == "Texture" then
      region:SetTexture(nil)
      region:Hide()
    end
  end
end

local function EnsureHistoryListButtonSkin(button)
  if not button or button.__lid_historySkinned then
    return
  end

  button.__lid_historySkinned = true
  StripTemplateButtonTextures(button)

  local bg = button:CreateTexture(nil, "BACKGROUND")
  bg:SetAllPoints(true)
  bg:SetTexture("Interface\\Buttons\\WHITE8x8")
  bg:SetVertexColor(0.09, 0.07, 0.09, 0.90)
  button.__baseBg = bg

  local hover = button:CreateTexture(nil, "HIGHLIGHT")
  hover:SetAllPoints(true)
  hover:SetTexture("Interface\\Buttons\\WHITE8x8")
  hover:SetVertexColor(1.00, 0.88, 0.32, 0.10)
  button.__hoverBg = hover
end

local function CreateHistoryListButton(parent, width, height)
  local button = CreateFrame("Button", nil, parent)
  button:SetSize(width or 192, height or 24)
  button:RegisterForClicks("LeftButtonUp")

  local text = button:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  text:SetPoint("LEFT", 8, 0)
  text:SetPoint("RIGHT", -8, 0)
  text:SetJustifyH("LEFT")
  text:SetText("")
  button.__text = text

  button.SetText = function(self, value)
    if self.__text then
      self.__text:SetText(tostring(value or ""))
    end
  end

  button.GetFontString = function(self)
    return self.__text
  end

  EnsureHistoryListButtonSkin(button)
  return button
end

local function SetHistoryListButtonSelected(button, selected)
  if not button then
    return
  end
  EnsureHistoryListButtonSkin(button)
  if not button.__selectionBg then
    local bg = button:CreateTexture(nil, "BORDER", nil, 1)
    bg:SetTexture("Interface\\Buttons\\WHITE8x8")
    bg:SetAllPoints(true)
    bg:SetVertexColor(0, 0, 0, 0)
    button.__selectionBg = bg
  end
  if not button.__selectionAccent then
    local accent = button:CreateTexture(nil, "ARTWORK", nil, 1)
    accent:SetTexture("Interface\\Buttons\\WHITE8x8")
    accent:SetPoint("TOPLEFT", 1, -1)
    accent:SetPoint("BOTTOMLEFT", 1, 1)
    accent:SetWidth(4)
    accent:SetVertexColor(0, 0, 0, 0)
    button.__selectionAccent = accent
  end
  if selected then
    if button.__baseBg then
      button.__baseBg:SetVertexColor(0.14, 0.10, 0.08, 0.94)
    end
    button.__selectionBg:SetVertexColor(0.90, 0.74, 0.18, 0.26)
    button.__selectionAccent:SetVertexColor(1.00, 0.88, 0.32, 0.96)
  else
    if button.__baseBg then
      button.__baseBg:SetVertexColor(0.09, 0.07, 0.09, 0.90)
    end
    button.__selectionBg:SetVertexColor(0, 0, 0, 0)
    button.__selectionAccent:SetVertexColor(0, 0, 0, 0)
  end
  if button.SetButtonState then
    button:SetButtonState(selected and "PUSHED" or "NORMAL", false)
  end
  local fontString = button.GetFontString and button:GetFontString() or nil
  if fontString and fontString.SetTextColor then
    if selected then
      fontString:SetTextColor(1.00, 0.88, 0.32, 1.00)
    else
      fontString:SetTextColor(0.92, 0.92, 0.96, 1.00)
    end
  end
end

local function FormatBoardTimestamp(value)
  local timestamp = tonumber(value)
  if not timestamp or timestamp <= 0 or type(date) ~= "function" then
    return "Unknown time"
  end
  return date("%d-%m-%Y %H:%M", timestamp)
end

local function BuildHistoryBoardDetailText(board)
  if type(board) ~= "table" then
    return "No board history selected."
  end

  local lines = {}
  lines[#lines + 1] = string.format("%s | %s", TrimText(board.label ~= "" and board.label or "Live Board"), FormatBoardTimestamp(board.updatedAt or board.createdAt))
  if TrimText(board.profileName) ~= "" then
    lines[#lines + 1] = "Profile: " .. TrimText(board.profileName)
  end
  if TrimText(board.sourceProfileName) ~= "" then
    lines[#lines + 1] = "Source Profile: " .. TrimText(board.sourceProfileName)
  end
  if TrimText(board.sessionId) ~= "" then
    lines[#lines + 1] = "Session: " .. TrimText(board.sessionId)
  end
  lines[#lines + 1] = board.isSimulation and "Mode: Simulation" or "Mode: Live"
  lines[#lines + 1] = ""

  for _, itemKey in ipairs(board.itemOrder or {}) do
    local item = type(board.items) == "table" and board.items[itemKey] or nil
    if type(item) == "table" then
      local title = TrimText(item.link ~= "" and item.link or (item.name ~= "" and item.name or itemKey))
      if tonumber(item.itemLevel) then
        title = title .. " | iLvl " .. tostring(item.itemLevel)
      end
      lines[#lines + 1] = title
      if TrimText(item.awardedTo) ~= "" then
        lines[#lines + 1] = string.format("  Assigned: %s (%d)", TrimText(item.awardedTo), tonumber(item.awardedPoints) or 0)
      else
        lines[#lines + 1] = "  Assigned: Pending"
      end
      if TrimText(item.hostVoteFor) ~= "" then
        lines[#lines + 1] = "  Vote: " .. TrimText(item.hostVoteFor)
      end

      local members = {}
      for memberKey in pairs(item.selections or {}) do
        members[#members + 1] = memberKey
      end
      table.sort(members, function(a, b)
        return string.lower(tostring(a)) < string.lower(tostring(b))
      end)
      for _, memberKey in ipairs(members) do
        local selection = item.selections[memberKey]
        local label = FormatSelectionLabel(selection)
        local suffix = TrimText(selection and selection.note or "")
        if suffix ~= "" then
          suffix = " | " .. suffix
        end
        lines[#lines + 1] = string.format("  - %s: %s%s", tostring(memberKey), tostring(label), suffix)
      end
      lines[#lines + 1] = ""
    end
  end

  if #lines == 0 then
    return "No history available."
  end
  return table.concat(lines, "\n")
end

local function GetHistoryBoardStatus(board)
  if type(board) ~= "table" then
    return "Empty", 0, 0
  end

  local totalItems = 0
  local awardedItems = 0
  for _, itemKey in ipairs(board.itemOrder or {}) do
    local item = type(board.items) == "table" and board.items[itemKey] or nil
    if type(item) == "table" then
      totalItems = totalItems + 1
      if tonumber(item.awardedAt) or TrimText(item.awardedTo) ~= "" then
        awardedItems = awardedItems + 1
      end
    end
  end

  if totalItems <= 0 then
    return "Empty", awardedItems, totalItems
  end
  if awardedItems <= 0 then
    return "Pending", awardedItems, totalItems
  end
  if awardedItems >= totalItems then
    return "Completed", awardedItems, totalItems
  end
  return "In Progress", awardedItems, totalItems
end

local function FormatHistoryRollLabel(selection)
  local label = TrimText(selection and (selection.label or selection.optionKey) or "Pending")
  if label == "" then
    label = "Pending"
  end
  return label
end

local function CountHistoryVotesForMember(item, memberKey)
  local targetKey = string.lower(TrimText(memberKey))
  if type(item) ~= "table" or targetKey == "" then
    return 0
  end
  local count = 0
  for _, votedTarget in pairs(item.votesByVoter or {}) do
    if string.lower(TrimText(votedTarget)) == targetKey then
      count = count + 1
    end
  end
  return count
end

local function BuildHistoryMemberRows(item)
  local rows = {}
  if type(item) ~= "table" then
    return rows
  end

  for memberKey, selection in pairs(item.selections or {}) do
    local selectionKey = string.lower(TrimText(selection and (selection.optionKey or selection.label) or "pending"))
    local rollValue = tonumber(selection and selection.rollValue)
    rows[#rows + 1] = {
      memberKey = tostring(memberKey or ""),
      rollLabel = FormatHistoryRollLabel(selection),
      rollKey = selectionKey,
      dice = rollValue,
      diceText = rollValue and tostring(math.floor(rollValue + 0.5)) or "-",
      votes = CountHistoryVotesForMember(item, memberKey),
      note = TrimText(selection and selection.note or ""),
      isPending = selectionKey == "" or selectionKey == "pending",
    }
  end

  table.sort(rows, function(left, right)
    if left.isPending ~= right.isPending then
      return not left.isPending
    end
    local leftRoll = string.lower(tostring(left.rollLabel or ""))
    local rightRoll = string.lower(tostring(right.rollLabel or ""))
    if leftRoll ~= rightRoll then
      return leftRoll < rightRoll
    end
    local leftDice = tonumber(left.dice) or -1
    local rightDice = tonumber(right.dice) or -1
    if leftDice ~= rightDice then
      return leftDice > rightDice
    end
    return string.lower(tostring(left.memberKey or "")) < string.lower(tostring(right.memberKey or ""))
  end)
  return rows
end

local function BuildHistoryItemRowsData(board)
  local rows = {}
  if type(board) ~= "table" then
    return rows
  end

  for _, itemKey in ipairs(board.itemOrder or {}) do
    local item = type(board.items) == "table" and board.items[itemKey] or nil
    if type(item) == "table" then
      local title = TrimText(item.link ~= "" and item.link or (item.name ~= "" and item.name or itemKey))
      if title == "" then
        title = tostring(itemKey or "Unknown Item")
      end
      local assignedTo = TrimText(item.awardedTo)
      local assignedText = assignedTo ~= "" and string.format("Assigned: %s (%d pts)", assignedTo, tonumber(item.awardedPoints) or 0) or "Assigned: Pending"
      if string.upper(TrimText(item.tradeState)) == "TRADED" and tonumber(item.tradedAt) then
        assignedText = assignedTo ~= "" and string.format("Traded: %s (%d pts)", assignedTo, tonumber(item.awardedPoints) or 0) or "Traded"
      end
      local memberRows = BuildHistoryMemberRows(item)
      local resolvedRolls = 0
      for _, memberRow in ipairs(memberRows) do
        if not memberRow.isPending then
          resolvedRolls = resolvedRolls + 1
        end
      end
      rows[#rows + 1] = {
        key = TrimText(item.key ~= "" and item.key or itemKey),
        title = title,
        icon = item.icon,
        itemLevel = tonumber(item.itemLevel),
        quality = tonumber(item.quality),
        assignedText = assignedText,
        rollSummary = string.format("%d/%d rolls", resolvedRolls, #memberRows),
        memberRows = memberRows,
      }
    end
  end
  return rows
end

local function GetHistoryDetailWidth(frame)
  local width = frame and frame.detailScroll and frame.detailScroll.GetWidth and frame.detailScroll:GetWidth() or HISTORY_DETAIL_WIDTH
  width = math.floor((tonumber(width) or HISTORY_DETAIL_WIDTH) - 34)
  if width < 320 then
    width = 320
  end
  return width
end

local function ScrollHistoryFrame(scrollFrame, delta)
  if not scrollFrame or not scrollFrame.GetVerticalScrollRange or not scrollFrame.SetVerticalScroll then
    return
  end
  local range = math.max(0, tonumber(scrollFrame:GetVerticalScrollRange()) or 0)
  if range <= 0 then
    scrollFrame:SetVerticalScroll(0)
    return
  end
  local current = tonumber(scrollFrame:GetVerticalScroll()) or 0
  local nextOffset = current - ((tonumber(delta) or 0) * 42)
  if nextOffset < 0 then
    nextOffset = 0
  elseif nextOffset > range then
    nextOffset = range
  end
  scrollFrame:SetVerticalScroll(nextOffset)
end

local function BindHistoryMouseWheel(frame, scrollFrame)
  if not frame or not scrollFrame or frame.__lid_historyWheelBound then
    return
  end
  frame.__lid_historyWheelBound = true
  if frame.EnableMouse then
    frame:EnableMouse(true)
  end
  if frame.EnableMouseWheel then
    frame:EnableMouseWheel(true)
  end
  if frame.SetScript then
    frame:SetScript("OnMouseWheel", function(_, delta)
      ScrollHistoryFrame(scrollFrame, delta)
    end)
  end
end

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

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

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

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

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

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

    if row.__owner and row.__owner.detailScroll then
      BindHistoryMouseWheel(memberRow, row.__owner.detailScroll)
    end
    row.memberRows[#row.memberRows + 1] = memberRow
  end
end

local function EnsureHistoryItemRows(frame, count)
  if not frame or not frame.historyItemsFrame then
    return
  end
  frame.historyItemRows = frame.historyItemRows or {}
  while #frame.historyItemRows < count do
    local row = CreateFrame("Frame", nil, frame.historyItemsFrame, "BackdropTemplate")
    ApplyFrameSkin(row)
    row:SetHeight(54)
    row:EnableMouse(true)

    local arrowText = row:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    arrowText:SetJustifyH("CENTER")
    row.arrowText = arrowText

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

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

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

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

    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:SetText("Rolls")
    detailsTitle:SetJustifyH("LEFT")
    row.detailsTitle = detailsTitle

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

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

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

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

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

    row.memberRows = {}
    row:SetScript("OnMouseUp", function(self, mouseButton)
      if mouseButton ~= "LeftButton" or TrimText(self.__itemKey) == "" or not self.__owner then
        return
      end
      local owner = self.__owner
      local boardId = TrimText(self.__boardId)
      owner.historyExpandedItems = owner.historyExpandedItems or {}
      owner.historyExpandedItems[boardId] = owner.historyExpandedItems[boardId] or {}
      owner.historyExpandedItems[boardId][self.__itemKey] = not owner.historyExpandedItems[boardId][self.__itemKey]
      if owner.RefreshHistory then
        owner:RefreshHistory(owner.__lastProfileName)
      end
    end)

    BindHistoryMouseWheel(row, frame.detailScroll)
    BindHistoryMouseWheel(detailsFrame, frame.detailScroll)
    frame.historyItemRows[#frame.historyItemRows + 1] = row
  end
end

local function LayoutHistoryItemMemberRow(memberRow, width, index)
  local memberWidth = math.max(120, math.floor(width * 0.34))
  local rollWidth = math.max(90, math.floor(width * 0.20))
  local diceWidth = 44
  local votesWidth = 48
  local noteX = 8 + memberWidth + rollWidth + diceWidth + votesWidth
  local noteWidth = math.max(80, width - noteX - 8)
  memberRow:ClearAllPoints()
  memberRow:SetPoint("TOPLEFT", 0, -44 - ((index - 1) * 22))
  memberRow:SetSize(width, 22)
  memberRow.memberText:ClearAllPoints()
  memberRow.memberText:SetPoint("LEFT", 8, 0)
  memberRow.memberText:SetWidth(memberWidth - 8)
  memberRow.rollText:ClearAllPoints()
  memberRow.rollText:SetPoint("LEFT", 8 + memberWidth, 0)
  memberRow.rollText:SetWidth(rollWidth)
  memberRow.diceText:ClearAllPoints()
  memberRow.diceText:SetPoint("LEFT", 8 + memberWidth + rollWidth, 0)
  memberRow.diceText:SetWidth(diceWidth)
  memberRow.votesText:ClearAllPoints()
  memberRow.votesText:SetPoint("LEFT", 8 + memberWidth + rollWidth + diceWidth, 0)
  memberRow.votesText:SetWidth(votesWidth)
  memberRow.noteText:ClearAllPoints()
  memberRow.noteText:SetPoint("LEFT", noteX, 0)
  memberRow.noteText:SetWidth(noteWidth)
end

local function LayoutHistoryItemRow(row, width, expanded, memberCount)
  row.arrowText:ClearAllPoints()
  row.arrowText:SetPoint("TOPLEFT", 8, -10)
  row.arrowText:SetSize(14, 16)
  row.icon:ClearAllPoints()
  row.icon:SetPoint("TOPLEFT", 28, -10)
  row.titleText:ClearAllPoints()
  row.titleText:SetPoint("TOPLEFT", 62, -7)
  row.titleText:SetWidth(math.max(120, width - 250))
  row.metaText:ClearAllPoints()
  row.metaText:SetPoint("TOPLEFT", 62, -28)
  row.metaText:SetWidth(math.max(120, width - 250))
  row.assignmentText:ClearAllPoints()
  row.assignmentText:SetPoint("TOPRIGHT", -12, -15)
  row.assignmentText:SetWidth(210)

  local detailsHeight = expanded and (48 + (memberCount * 22)) or 0
  if expanded then
    local detailsWidth = math.max(260, width - 16)
    row.detailsFrame:ClearAllPoints()
    row.detailsFrame:SetPoint("TOPLEFT", 8, -50)
    row.detailsFrame:SetSize(detailsWidth, detailsHeight)
    row.detailsFrame:Show()
    row.detailsTitle:ClearAllPoints()
    row.detailsTitle:SetPoint("TOPLEFT", 8, -6)
    row.detailsTitle:SetWidth(detailsWidth - 16)

    local memberWidth = math.max(120, math.floor(detailsWidth * 0.34))
    local rollWidth = math.max(90, math.floor(detailsWidth * 0.20))
    local diceWidth = 44
    local votesWidth = 48
    local headerY = -26
    row.headerMember:ClearAllPoints()
    row.headerMember:SetPoint("TOPLEFT", 8, headerY)
    row.headerMember:SetWidth(memberWidth - 8)
    row.headerRoll:ClearAllPoints()
    row.headerRoll:SetPoint("TOPLEFT", 8 + memberWidth, headerY)
    row.headerRoll:SetWidth(rollWidth)
    row.headerDice:ClearAllPoints()
    row.headerDice:SetPoint("TOPLEFT", 8 + memberWidth + rollWidth, headerY)
    row.headerDice:SetWidth(diceWidth)
    row.headerVotes:ClearAllPoints()
    row.headerVotes:SetPoint("TOPLEFT", 8 + memberWidth + rollWidth + diceWidth, headerY)
    row.headerVotes:SetWidth(votesWidth)
    row.headerNote:ClearAllPoints()
    row.headerNote:SetPoint("TOPLEFT", 8 + memberWidth + rollWidth + diceWidth + votesWidth, headerY)
    row.headerNote:SetWidth(math.max(80, detailsWidth - (8 + memberWidth + rollWidth + diceWidth + votesWidth) - 8))
  else
    row.detailsFrame:Hide()
  end

  row:SetSize(width, expanded and (58 + detailsHeight) or 54)
  row.layoutHeight = expanded and (58 + detailsHeight) or 54
end

local function SetupHistoryDetailArea(frame)
  local summary = frame.detailContent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  summary:SetPoint("TOPLEFT", 8, 0)
  summary:SetJustifyH("LEFT")
  summary:SetJustifyV("TOP")
  summary:SetText("")
  frame.detailSummary = summary

  local itemRowsFrame = CreateFrame("Frame", nil, frame.detailContent)
  itemRowsFrame:SetPoint("TOPLEFT", 8, -70)
  itemRowsFrame:SetSize(HISTORY_DETAIL_WIDTH - 16, 1)
  frame.historyItemsFrame = itemRowsFrame
  frame.historyItemRows = {}
  frame.historyExpandedItems = {}

  BindHistoryMouseWheel(frame.detailContent, frame.detailScroll)
  BindHistoryMouseWheel(itemRowsFrame, frame.detailScroll)
end

local function RefreshHistoryBoardDetails(frame, board, boardStatus, awardedItems, totalItems)
  if not frame or not frame.detailContent then
    return
  end

  local width = GetHistoryDetailWidth(frame)
  frame.detailContent:SetWidth(width)
  if frame.detailSummary then
    frame.detailSummary:SetWidth(width - 8)
  end

  if type(board) ~= "table" then
    if frame.detailSummary then
      frame.detailSummary:SetText("No board history selected.")
    end
    for _, row in ipairs(frame.historyItemRows or {}) do
      row:Hide()
    end
    frame.detailContent:SetHeight(64)
    return
  end

  local summaryLines = {
    string.format("%s | %s", TrimText(board.label ~= "" and board.label or "Live Board"), FormatBoardTimestamp(board.updatedAt or board.createdAt)),
    string.format("Mode: %s | Status: %s (%d/%d assigned)", board.isSimulation and "Simulation / Example Data" or "Live", tostring(boardStatus), tonumber(awardedItems) or 0, tonumber(totalItems) or 0),
  }
  if TrimText(board.profileName) ~= "" then
    summaryLines[#summaryLines + 1] = "Profile: " .. TrimText(board.profileName)
  end
  if TrimText(board.sessionId) ~= "" then
    summaryLines[#summaryLines + 1] = "Session: " .. TrimText(board.sessionId)
  end
  if board.isSimulation then
    summaryLines[#summaryLines + 1] = "|cffffb84dSimulation history uses preview members and does not reflect real raid history.|r"
  end
  if frame.detailSummary then
    frame.detailSummary:SetText(table.concat(summaryLines, "\n"))
  end

  local summaryHeight = math.max(58, math.ceil((frame.detailSummary and frame.detailSummary.GetStringHeight and frame.detailSummary:GetStringHeight()) or 58))
  local rowsTop = -(summaryHeight + 14)
  if frame.historyItemsFrame then
    frame.historyItemsFrame:ClearAllPoints()
    frame.historyItemsFrame:SetPoint("TOPLEFT", 8, rowsTop)
    frame.historyItemsFrame:SetWidth(width - 16)
  end

  local itemRows = BuildHistoryItemRowsData(board)
  EnsureHistoryItemRows(frame, #itemRows)

  local boardId = TrimText(board.boardId)
  local expandedForBoard = type(frame.historyExpandedItems) == "table" and type(frame.historyExpandedItems[boardId]) == "table" and frame.historyExpandedItems[boardId] or {}
  local y = 0
  local rowWidth = math.max(260, width - 16)
  for index, itemData in ipairs(itemRows) do
    local row = frame.historyItemRows[index]
    local expanded = expandedForBoard[itemData.key] and true or false
    row.__owner = frame
    row.__profileName = frame.__lastProfileName
    row.__boardId = boardId
    row.__itemKey = itemData.key
    row.arrowText:SetText(expanded and "v" or ">")
    row.icon:SetTexture(itemData.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
    row.titleText:SetText(itemData.title)
    row.metaText:SetText(string.format("%s%s", itemData.itemLevel and ("iLvl " .. tostring(itemData.itemLevel) .. " | ") or "", itemData.rollSummary))
    row.assignmentText:SetText(itemData.assignedText)
    local assignmentPending = TrimText(itemData.assignedText):find("Pending", 1, true) and true or false
    row.assignmentText:SetTextColor(assignmentPending and 1.00 or 0.52, assignmentPending and 0.82 or 0.90, assignmentPending and 0.32 or 0.52, 1.00)

    EnsureHistoryItemMemberRows(row, #itemData.memberRows)
    LayoutHistoryItemRow(row, rowWidth, expanded, #itemData.memberRows)
    row:ClearAllPoints()
    row:SetPoint("TOPLEFT", 0, -y)

    if expanded then
      for memberIndex, memberRow in ipairs(row.memberRows or {}) do
        local memberData = itemData.memberRows[memberIndex]
        if memberData then
          memberRow.memberText:SetText(memberData.memberKey)
          memberRow.rollText:SetText(memberData.rollLabel)
          memberRow.diceText:SetText(memberData.diceText)
          memberRow.votesText:SetText(tostring(tonumber(memberData.votes) or 0))
          memberRow.noteText:SetText(memberData.note ~= "" and memberData.note or "-")
          if memberData.isPending then
            memberRow.rollText:SetTextColor(0.70, 0.70, 0.76, 1.00)
            memberRow.diceText:SetTextColor(0.70, 0.70, 0.76, 1.00)
          else
            memberRow.rollText:SetTextColor(0.92, 0.92, 0.96, 1.00)
            memberRow.diceText:SetTextColor(1.00, 0.88, 0.32, 1.00)
          end
          memberRow:Show()
          LayoutHistoryItemMemberRow(memberRow, math.max(260, rowWidth - 16), memberIndex)
        else
          memberRow:Hide()
        end
      end
    else
      for _, memberRow in ipairs(row.memberRows or {}) do
        memberRow:Hide()
      end
    end

    row:Show()
    y = y + (row.layoutHeight or 54) + 6
  end
  for index = #itemRows + 1, #(frame.historyItemRows or {}) do
    frame.historyItemRows[index]:Hide()
  end
  if frame.historyItemsFrame then
    frame.historyItemsFrame:SetHeight(math.max(1, y))
  end

  local emptyExtra = #itemRows == 0 and 28 or 0
  if #itemRows == 0 and frame.detailSummary then
    frame.detailSummary:SetText(frame.detailSummary:GetText() .. "\n\nNo dropped item history exists for this board snapshot.")
  end
  frame.detailContent:SetHeight(math.max(1, summaryHeight + 24 + y + emptyExtra))
  if frame.detailScroll and frame.detailScroll.UpdateScrollChildRect then
    frame.detailScroll:UpdateScrollChildRect()
  end
  if frame.detailScroll and frame.detailScroll.GetVerticalScrollRange and frame.detailScroll.GetVerticalScroll and frame.detailScroll.SetVerticalScroll then
    local range = math.max(0, tonumber(frame.detailScroll:GetVerticalScrollRange()) or 0)
    local current = tonumber(frame.detailScroll:GetVerticalScroll()) or 0
    if current > range then
      frame.detailScroll:SetVerticalScroll(range)
    end
  end
end

local function CreateBoardHistoryPopup()
  if UIState.historyPopup then
    return UIState.historyPopup
  end

  local frame = CreateFrame("Frame", "LIDCOUNCIL_LiveBoardHistory", UIParent, "BackdropTemplate")
  frame:SetSize(860, 520)
  frame:SetPoint("CENTER", 0, 0)
  frame:SetFrameStrata("FULLSCREEN_DIALOG")
  frame:SetMovable(true)
  frame:EnableMouse(true)
  frame:SetToplevel(true)
  frame:RegisterForDrag("LeftButton")
  frame:SetScript("OnDragStart", frame.StartMoving)
  frame:SetScript("OnDragStop", frame.StopMovingOrSizing)
  ApplyFrameSkin(frame)
  frame:HookScript("OnShow", RaisePopupFrameAboveEditor)
  frame:Hide()

  local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
  title:SetPoint("TOPLEFT", 16, -14)
  title:SetText("LidCouncil Live Board History")
  frame.title = title

  local closeBtn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
  closeBtn:SetSize(70, 22)
  closeBtn:SetPoint("TOPRIGHT", -16, -12)
  closeBtn:SetText("Close")
  closeBtn:SetScript("OnClick", function()
    frame:Hide()
  end)
  frame.closeBtn = closeBtn

  local listScroll = CreateFrame("ScrollFrame", nil, frame, "UIPanelScrollFrameTemplate")
  listScroll:SetPoint("TOPLEFT", HISTORY_LIST_LEFT, -44)
  listScroll:SetPoint("BOTTOMLEFT", HISTORY_LIST_LEFT, 16)
  listScroll:SetWidth(HISTORY_LIST_WIDTH)
  frame.listScroll = listScroll

  local listContent = CreateFrame("Frame", nil, listScroll)
  listContent:SetSize(196, 1)
  listScroll:SetScrollChild(listContent)
  frame.listContent = listContent
  frame.historyButtons = {}
  BindHistoryMouseWheel(listScroll, listScroll)
  BindHistoryMouseWheel(listContent, listScroll)

  local detailScroll = CreateFrame("ScrollFrame", nil, frame, "UIPanelScrollFrameTemplate")
  detailScroll:SetPoint("TOPLEFT", HISTORY_DETAIL_LEFT, -44)
  detailScroll:SetPoint("BOTTOMRIGHT", HISTORY_DETAIL_RIGHT, 16)
  frame.detailScroll = detailScroll
  BindHistoryMouseWheel(detailScroll, detailScroll)

  local detailContent = CreateFrame("Frame", nil, detailScroll)
  detailContent:SetSize(HISTORY_DETAIL_WIDTH, 1)
  detailScroll:SetScrollChild(detailContent)
  frame.detailContent = detailContent

  SetupHistoryDetailArea(frame)

  function frame:RefreshHistory(profileName)
    self.__lastProfileName = profileName
    local entries = GetLiveBoardHistoryEntries(profileName)
    self.entries = entries
    self.selectedBoardId = self.selectedBoardId or TrimText(type(entries[1]) == "table" and entries[1].boardId or "")

    while #self.historyButtons < #entries do
      local button = CreateHistoryListButton(self.listContent, 192, 24)
      button:SetScript("OnClick", function(btn)
        self.selectedBoardId = TrimText(btn.__boardId)
        self:RefreshHistory(profileName)
      end)
      self.historyButtons[#self.historyButtons + 1] = button
    end

    local selectedBoard = nil
    for index, entry in ipairs(entries) do
      local button = self.historyButtons[index]
      button:ClearAllPoints()
      button:SetPoint("TOPLEFT", 0, -((index - 1) * 28))
      button.__boardId = TrimText(type(entry) == "table" and entry.boardId or "")
      local boardStatus, awardedItems, totalItems = GetHistoryBoardStatus(entry)
      button:SetText(string.format("%s | %s | %s %d/%d", FormatBoardTimestamp(entry.updatedAt or entry.createdAt), entry.isSimulation and "Sim" or "Live", boardStatus, awardedItems, totalItems))
      if self.selectedBoardId ~= "" and self.selectedBoardId == button.__boardId then
        selectedBoard = entry
      end
      SetHistoryListButtonSelected(button, self.selectedBoardId ~= "" and self.selectedBoardId == button.__boardId)
      button:Show()
    end
    for index = #entries + 1, #self.historyButtons do
      self.historyButtons[index]:Hide()
    end
    self.listContent:SetHeight(math.max(1, #entries * 28))

    if not selectedBoard and type(entries[1]) == "table" then
      selectedBoard = entries[1]
      self.selectedBoardId = TrimText(entries[1].boardId)
    end

    local boardStatus, awardedItems, totalItems = GetHistoryBoardStatus(selectedBoard)
    RefreshHistoryBoardDetails(self, selectedBoard, boardStatus, awardedItems, totalItems)
  end

  detailScroll:SetScript("OnSizeChanged", function()
    if frame:IsShown() and frame.RefreshHistory and TrimText(frame.__lastProfileName) ~= "" then
      frame:RefreshHistory(frame.__lastProfileName)
    end
  end)

  UIState.historyPopup = frame
  return frame
end

local function OpenBoardHistoryPopup(frame, profileName)
  local resolvedProfileName = TrimText(profileName or ResolveBoardProfileName(frame))
  if resolvedProfileName == "" then
    return
  end
  local popup = CreateBoardHistoryPopup()
  popup:RefreshHistory(resolvedProfileName)
  popup:Show()
end

local function CreateEmbeddedBoardHistoryFrame()
  if UIState.embeddedHistoryFrame then
    return UIState.embeddedHistoryFrame
  end

  local frame = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
  frame:SetFrameStrata("DIALOG")
  ApplyFrameSkin(frame)
  frame:Hide()

  local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
  title:SetPoint("TOPLEFT", 16, -14)
  title:SetText("LidCouncil Live Board History")
  frame.title = title

  local listScroll = CreateFrame("ScrollFrame", nil, frame, "UIPanelScrollFrameTemplate")
  listScroll:SetPoint("TOPLEFT", HISTORY_LIST_LEFT, -44)
  listScroll:SetPoint("BOTTOMLEFT", HISTORY_LIST_LEFT, 16)
  listScroll:SetWidth(HISTORY_LIST_WIDTH)
  frame.listScroll = listScroll

  local listContent = CreateFrame("Frame", nil, listScroll)
  listContent:SetSize(196, 1)
  listScroll:SetScrollChild(listContent)
  frame.listContent = listContent
  frame.historyButtons = {}
  BindHistoryMouseWheel(listScroll, listScroll)
  BindHistoryMouseWheel(listContent, listScroll)

  local detailScroll = CreateFrame("ScrollFrame", nil, frame, "UIPanelScrollFrameTemplate")
  detailScroll:SetPoint("TOPLEFT", HISTORY_DETAIL_LEFT, -44)
  detailScroll:SetPoint("BOTTOMRIGHT", HISTORY_DETAIL_RIGHT, 16)
  frame.detailScroll = detailScroll
  BindHistoryMouseWheel(detailScroll, detailScroll)

  local detailContent = CreateFrame("Frame", nil, detailScroll)
  detailContent:SetSize(HISTORY_DETAIL_WIDTH, 1)
  detailScroll:SetScrollChild(detailContent)
  frame.detailContent = detailContent

  SetupHistoryDetailArea(frame)

  function frame:RefreshHistory(profileName)
    self.__lastProfileName = profileName
    local entries = GetLiveBoardHistoryEntries(profileName)
    self.entries = entries
    self.selectedBoardId = self.selectedBoardId or TrimText(type(entries[1]) == "table" and entries[1].boardId or "")

    while #self.historyButtons < #entries do
      local button = CreateHistoryListButton(self.listContent, 192, 24)
      button:SetScript("OnClick", function(btn)
        self.selectedBoardId = TrimText(btn.__boardId)
        self:RefreshHistory(profileName)
      end)
      self.historyButtons[#self.historyButtons + 1] = button
    end

    local selectedBoard = nil
    for index, entry in ipairs(entries) do
      local button = self.historyButtons[index]
      button:ClearAllPoints()
      button:SetPoint("TOPLEFT", 0, -((index - 1) * 28))
      button.__boardId = TrimText(type(entry) == "table" and entry.boardId or "")
      local boardStatus, awardedItems, totalItems = GetHistoryBoardStatus(entry)
      button:SetText(string.format("%s | %s | %s %d/%d", FormatBoardTimestamp(entry.updatedAt or entry.createdAt), entry.isSimulation and "Sim" or "Live", boardStatus, awardedItems, totalItems))
      if self.selectedBoardId ~= "" and self.selectedBoardId == button.__boardId then
        selectedBoard = entry
      end
      SetHistoryListButtonSelected(button, self.selectedBoardId ~= "" and self.selectedBoardId == button.__boardId)
      button:Show()
    end
    for index = #entries + 1, #self.historyButtons do
      self.historyButtons[index]:Hide()
    end
    self.listContent:SetHeight(math.max(1, #entries * 28))

    if not selectedBoard and type(entries[1]) == "table" then
      selectedBoard = entries[1]
      self.selectedBoardId = TrimText(entries[1].boardId)
    end

    local boardStatus, awardedItems, totalItems = GetHistoryBoardStatus(selectedBoard)
    RefreshHistoryBoardDetails(self, selectedBoard, boardStatus, awardedItems, totalItems)
  end

  detailScroll:SetScript("OnSizeChanged", function()
    if frame:IsShown() and frame.RefreshHistory and TrimText(frame.__lastProfileName) ~= "" then
      frame:RefreshHistory(frame.__lastProfileName)
    end
  end)

  UIState.embeddedHistoryFrame = frame
  return frame
end

local function AttachLiveLootBoardHistory(parent, profileName)
  if not parent then
    return
  end

  local resolvedProfileName = TrimText(profileName)
  if resolvedProfileName == "" then
    return
  end

  if UIState.boardFrame and UIState.boardFrame.__embeddedParent then
    UIState.boardFrame:Hide()
  end

  local frame = CreateEmbeddedBoardHistoryFrame()
  frame:SetParent(parent)
  frame:ClearAllPoints()
  frame:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0)
  frame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 0)
  if parent.GetFrameLevel and frame.SetFrameLevel then
    frame:SetFrameLevel(parent:GetFrameLevel() + 20)
  end
  frame:RefreshHistory(resolvedProfileName)
  frame:Show()
end

INTERNAL_API.LIDCOUNCIL_LootRuntimeUiHistory_Impl = {
  FormatBoardTimestamp = FormatBoardTimestamp,
  BuildHistoryBoardDetailText = BuildHistoryBoardDetailText,
  CreateBoardHistoryPopup = CreateBoardHistoryPopup,
  OpenBoardHistoryPopup = OpenBoardHistoryPopup,
  CreateEmbeddedBoardHistoryFrame = CreateEmbeddedBoardHistoryFrame,
  AttachLiveLootBoardHistory = AttachLiveLootBoardHistory,
}

INTERNAL_API.LIDCOUNCIL_AttachLiveLootBoardHistory_Impl = AttachLiveLootBoardHistory
