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

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

local BindMouseWheelScroll = INTERNAL_API.LIDCOUNCIL_BindMouseWheelScroll_Impl or function() end

local RootDB = RequireApi("LIDCOUNCIL_RootDB", _G.LIDCOUNCIL_RootDB)
local ApplyFrameSkin = RequireApi("LIDCOUNCIL_ApplyFrameSkin", _G.LIDCOUNCIL_ApplyFrameSkin)
local MakeYellowSubHeader = RequireApi("LIDCOUNCIL_MakeYellowSubHeader", _G.LIDCOUNCIL_MakeYellowSubHeader)
local GetCharacterKey = RequireApi("LIDCOUNCIL_GetCharacterKey", _G.LIDCOUNCIL_GetCharacterKey)
local GetEditorThemePalette = type(INTERNAL_API.LIDCOUNCIL_GetEditorThemePalette_Impl) == "function" and INTERNAL_API.LIDCOUNCIL_GetEditorThemePalette_Impl or nil

local GetLootProfile = RequireApi("LIDCOUNCIL_GetLootProfile", _G.LIDCOUNCIL_GetLootProfile)
local GetLootProfileNames = RequireApi("LIDCOUNCIL_GetLootProfileNames", _G.LIDCOUNCIL_GetLootProfileNames)
local GetPrimaryLootProfileName = RequireApi("LIDCOUNCIL_GetPrimaryLootProfileName", _G.LIDCOUNCIL_GetPrimaryLootProfileName)
local GetLootProfileRoleEntries = RequireApi("LIDCOUNCIL_GetLootProfileRoleEntries", _G.LIDCOUNCIL_GetLootProfileRoleEntries)
local GetLootProfileAccessInfo = RequireApi("LIDCOUNCIL_GetLootProfileAccessInfo", _G.LIDCOUNCIL_GetLootProfileAccessInfo)
local GetLootProfileAccessInfoForRole = RequireApi("LIDCOUNCIL_GetLootProfileAccessInfoForRole", _G.LIDCOUNCIL_GetLootProfileAccessInfoForRole)
local GetLootProfilePermissionPolicyRoleState = RequireApi("LIDCOUNCIL_GetLootProfilePermissionPolicyRoleState", _G.LIDCOUNCIL_GetLootProfilePermissionPolicyRoleState)
local SetCurrentLootProfile = RequireApi("LIDCOUNCIL_SetCurrentLootProfile", _G.LIDCOUNCIL_SetCurrentLootProfile)
local CreateLootProfile = RequireApi("LIDCOUNCIL_CreateLootProfile", _G.LIDCOUNCIL_CreateLootProfile)
local DeleteLootProfile = RequireApi("LIDCOUNCIL_DeleteLootProfile", _G.LIDCOUNCIL_DeleteLootProfile)
local CopyLootProfile = RequireApi("LIDCOUNCIL_CopyLootProfile", _G.LIDCOUNCIL_CopyLootProfile)
local ResetSimulationProfileContent = RequireApi("LIDCOUNCIL_ResetSimulationProfileContent", _G.LIDCOUNCIL_ResetSimulationProfileContent)
local SetLootProfileMemberRole = RequireApi("LIDCOUNCIL_SetLootProfileMemberRole", _G.LIDCOUNCIL_SetLootProfileMemberRole)
local SetLootProfilePermissionPolicyValue = RequireApi("LIDCOUNCIL_SetLootProfilePermissionPolicyValue", _G.LIDCOUNCIL_SetLootProfilePermissionPolicyValue)
local ResetLootProfilePermissionPolicy = RequireApi("LIDCOUNCIL_ResetLootProfilePermissionPolicy", _G.LIDCOUNCIL_ResetLootProfilePermissionPolicy)
local TransferLootProfileMaster = RequireApi("LIDCOUNCIL_TransferLootProfileMaster", _G.LIDCOUNCIL_TransferLootProfileMaster)
local SetLootProfileEnabled = RequireApi("LIDCOUNCIL_SetLootProfileEnabled", _G.LIDCOUNCIL_SetLootProfileEnabled)
local SetLootProfileRaid = RequireApi("LIDCOUNCIL_SetLootProfileRaid", _G.LIDCOUNCIL_SetLootProfileRaid)
local ApplyLootRollTemplate = RequireApi("LIDCOUNCIL_ApplyLootRollTemplate", _G.LIDCOUNCIL_ApplyLootRollTemplate)
local AddLootRollOption = RequireApi("LIDCOUNCIL_AddLootRollOption", _G.LIDCOUNCIL_AddLootRollOption)
local UpdateLootRollOption = RequireApi("LIDCOUNCIL_UpdateLootRollOption", _G.LIDCOUNCIL_UpdateLootRollOption)
local MoveLootRollOption = RequireApi("LIDCOUNCIL_MoveLootRollOption", _G.LIDCOUNCIL_MoveLootRollOption)
local RemoveLootRollOption = RequireApi("LIDCOUNCIL_RemoveLootRollOption", _G.LIDCOUNCIL_RemoveLootRollOption)
local UpdateLootPointMode = RequireApi("LIDCOUNCIL_UpdateLootPointMode", _G.LIDCOUNCIL_UpdateLootPointMode)
local UpdateLootLidTableRow = RequireApi("LIDCOUNCIL_UpdateLootLidTableRow", _G.LIDCOUNCIL_UpdateLootLidTableRow)
local AddLootPointRule = RequireApi("LIDCOUNCIL_AddLootPointRule", _G.LIDCOUNCIL_AddLootPointRule)
local RemoveLootPointRule = RequireApi("LIDCOUNCIL_RemoveLootPointRule", _G.LIDCOUNCIL_RemoveLootPointRule)
local StartLootSession = RequireApi("LIDCOUNCIL_StartLootSession", _G.LIDCOUNCIL_StartLootSession)
local StopLootSession = RequireApi("LIDCOUNCIL_StopLootSession", _G.LIDCOUNCIL_StopLootSession)
local GetActiveLootSession = RequireApi("LIDCOUNCIL_GetActiveLootSession", _G.LIDCOUNCIL_GetActiveLootSession)
local AddLootLedgerEntry = RequireApi("LIDCOUNCIL_AddLootLedgerEntry", _G.LIDCOUNCIL_AddLootLedgerEntry)
local RemoveLootLedgerEntry = RequireApi("LIDCOUNCIL_RemoveLootLedgerEntry", _G.LIDCOUNCIL_RemoveLootLedgerEntry)
local GetLootLedgerCharacters = RequireApi("LIDCOUNCIL_GetLootLedgerCharacters", _G.LIDCOUNCIL_GetLootLedgerCharacters)

local function ResolveOptionalApiFunction(name)
  local api = rawget(_G, "LIDCOUNCIL_API")
  if type(api) == "table" and type(api[name]) == "function" then
    return api[name]
  end
  local value = rawget(_G, name)
  if type(value) == "function" then
    return value
  end
  return nil
end

local CreateCustomDropdownControl = ResolveOptionalApiFunction("LIDCOUNCIL_CreateCustomDropdownControl")

local function TrimText(value)
  value = tostring(value or "")
  value = value:gsub("^%s+", "")
  value = value:gsub("%s+$", "")
  return value
end

local function FormatDateTime(ts)
  local value = tonumber(ts) or 0
  if value <= 0 then
    return "Unknown"
  end
  if date then
    return tostring(date("%Y-%m-%d %H:%M:%S", value))
  end
  return tostring(value)
end

local function FormatDuration(seconds)
  local total = math.max(0, math.floor(tonumber(seconds) or 0))
  local hours = math.floor(total / 3600)
  local minutes = math.floor((total % 3600) / 60)
  local secs = total % 60
  if hours > 0 then
    return string.format("%dh %02dm %02ds", hours, minutes, secs)
  end
  return string.format("%dm %02ds", minutes, secs)
end

local function CreateSimpleEditBox(parent, width, height)
  local edit = CreateFrame("EditBox", nil, parent, "InputBoxTemplate")
  edit:SetSize(width or 120, height or 22)
  edit:SetAutoFocus(false)
  edit:SetTextInsets(6, 6, 0, 0)
  edit:SetFontObject("GameFontHighlightSmall")
  return edit
end

local function SetListButtonSelected(button, selected)
  if not button then return end
  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
  local themePalette = GetEditorThemePalette and GetEditorThemePalette() or nil
  local selectionBg = type(themePalette) == "table" and themePalette.selectionBg or { 0.90, 0.74, 0.18, 0.26 }
  local selectionAccent = type(themePalette) == "table" and themePalette.selectionAccent or { 1.00, 0.88, 0.32, 0.96 }
  if button.__selectionBg then
    if selected then
      button.__selectionBg:SetVertexColor(selectionBg[1] or 0.90, selectionBg[2] or 0.74, selectionBg[3] or 0.18, selectionBg[4] or 0.26)
    else
      button.__selectionBg:SetVertexColor(0, 0, 0, 0)
    end
  end
  if button.__selectionAccent then
    if selected then
      button.__selectionAccent:SetVertexColor(selectionAccent[1] or 1.00, selectionAccent[2] or 0.88, selectionAccent[3] or 0.32, selectionAccent[4] or 0.96)
    else
      button.__selectionAccent:SetVertexColor(0, 0, 0, 0)
    end
  end
  if button.SetButtonState then
    button:SetButtonState(selected and "PUSHED" or "NORMAL", false)
  end
end

local function SetTextListButtonTextColor(button, color)
  if not button then
    return
  end
  local fontString = (button.GetFontString and button:GetFontString()) or button.Text or nil
  if not fontString or not fontString.SetTextColor then
    return
  end

  local resolved = type(color) == "table" and color or nil
  fontString:SetTextColor(
    tonumber(resolved and resolved[1]) or 0.82,
    tonumber(resolved and resolved[2]) or 0.82,
    tonumber(resolved and resolved[3]) or 0.86,
    tonumber(resolved and resolved[4]) or 1
  )
end

local function EnsureTextListButtons(buttons, parent, count, onClick)
  buttons = buttons or {}
  while #buttons < count do
    local index = #buttons + 1
    local button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
    button:SetSize(208, 20)
    button:SetScript("OnClick", function(self)
      if onClick then
        onClick(self.__value)
      end
    end)
    buttons[index] = button
  end
  return buttons
end

local function CreateSectionShell(page, leftWidth)
  local navBg = CreateFrame("Frame", nil, page, "BackdropTemplate")
  navBg:SetPoint("TOPLEFT", 0, -44)
  navBg:SetPoint("BOTTOMLEFT", 0, 0)
  navBg:SetWidth(leftWidth or 240)
  ApplyFrameSkin(navBg)

  local rightBg = CreateFrame("Frame", nil, page, "BackdropTemplate")
  rightBg:SetPoint("TOPLEFT", navBg, "TOPRIGHT", 12, 0)
  rightBg:SetPoint("BOTTOMRIGHT", 0, 0)
  ApplyFrameSkin(rightBg)
  rightBg:SetClipsChildren(true)

  return navBg, rightBg
end

local function ClampLayoutValue(value, minValue, maxValue)
  local resolvedValue = tonumber(value) or tonumber(minValue) or 0
  local resolvedMin = tonumber(minValue) or resolvedValue
  local resolvedMax = tonumber(maxValue) or resolvedValue
  if resolvedMin > resolvedMax then
    resolvedMin, resolvedMax = resolvedMax, resolvedMin
  end
  if resolvedValue < resolvedMin then
    return resolvedMin
  end
  if resolvedValue > resolvedMax then
    return resolvedMax
  end
  return resolvedValue
end

local function GetResponsiveSectionLayout(page, defaultNavWidth, minNavWidth, maxNavWidth, ratio)
  local gap = 12
  local pageWidth = (page and page.GetWidth and page:GetWidth()) or 980
  local minRightWidth = 260
  local desiredWidth = math.floor((pageWidth - gap) * (tonumber(ratio) or 0.28))
  local minWidth = tonumber(minNavWidth) or 180
  local maxWidth = tonumber(maxNavWidth) or tonumber(defaultNavWidth) or 320
  local maxAllowedWidth = math.max(minWidth, pageWidth - gap - minRightWidth)
  local navWidth = ClampLayoutValue(desiredWidth, minWidth, math.min(maxWidth, maxAllowedWidth))
  local rightWidth = math.max(minRightWidth, pageWidth - navWidth - gap)
  return pageWidth, navWidth, rightWidth
end

local function SetControlWidth(control, width)
  if control and control.__lidcouncilCustomDropdown and control.SetDropdownWidth then
    control:SetDropdownWidth(width)
  elseif control and control.SetWidth then
    control:SetWidth(math.max(1, math.floor(width or 1)))
  end
end

local function GetFontStringWidth(label, fallback)
  if label and label.GetStringWidth then
    local width = tonumber(label:GetStringWidth())
    if width and width > 0 then
      return math.ceil(width)
    end
  end
  return tonumber(fallback) or 0
end

local function CreateSectionDivider(parent)
  local divider = parent:CreateTexture(nil, "BORDER")
  divider:SetTexture("Interface\\Buttons\\WHITE8x8")
  divider:SetHeight(1)
  divider:SetVertexColor(0.90, 0.74, 0.18, 0.26)
  return divider
end

local function FlowLayoutControls(controls, startX, startTop, availableWidth, gapX, gapY)
  local currentX = tonumber(startX) or 0
  local currentTop = tonumber(startTop) or 0
  local leftX = currentX
  local maxRight = leftX + math.max(1, math.floor(availableWidth or 1))
  local spacingX = tonumber(gapX) or 0
  local spacingY = tonumber(gapY) or spacingX
  local rowHeight = 0
  local totalHeight = 0
  local shownCount = 0

  for _, control in ipairs(controls or {}) do
    if control and control.IsShown and control:IsShown() then
      local width = math.floor((control.GetWidth and control:GetWidth()) or 0)
      local height = math.floor((control.GetHeight and control:GetHeight()) or 0)
      if shownCount > 0 and (currentX + width) > maxRight then
        currentX = leftX
        currentTop = currentTop - rowHeight - spacingY
        totalHeight = totalHeight + rowHeight + spacingY
        rowHeight = 0
      end

      control:ClearAllPoints()
      control:SetPoint("TOPLEFT", currentX, currentTop)
      currentX = currentX + width + spacingX
      rowHeight = math.max(rowHeight, height)
      shownCount = shownCount + 1
    end
  end

  if shownCount == 0 then
    return 0
  end
  return totalHeight + rowHeight
end

local function ApplyMiniActionButtonStyle(button)
  if not button then
    return
  end

  local themePalette = GetEditorThemePalette and GetEditorThemePalette() or nil
  local normal = type(themePalette) == "table" and themePalette.normal or { 0.08, 0.05, 0.06, 0.96 }
  local hover = type(themePalette) == "table" and themePalette.hover or { 0.14, 0.14, 0.18, 0.98 }
  local border = type(themePalette) == "table" and themePalette.border or { 0.74, 0.22, 0.30, 0.95 }
  local textColor = type(themePalette) == "table" and themePalette.text or { 1.00, 1.00, 1.00, 1.00 }

  button:SetBackdrop({
    bgFile = "Interface\\Buttons\\WHITE8x8",
    edgeFile = "Interface\\Buttons\\WHITE8x8",
    edgeSize = 1,
  })
  button:SetBackdropColor(normal[1] or 0.08, normal[2] or 0.05, normal[3] or 0.06, normal[4] or 0.96)
  button:SetBackdropBorderColor(border[1] or 1, border[2] or 1, border[3] or 1, border[4] or 0.95)
  if button.SetNormalTexture then button:SetNormalTexture("") end
  if button.SetPushedTexture then button:SetPushedTexture("") end
  if button.SetHighlightTexture then button:SetHighlightTexture("") end
  if button.SetDisabledTexture then button:SetDisabledTexture("") end

  local highlight = button.__lidHighlight
  if not highlight then
    highlight = button:CreateTexture(nil, "HIGHLIGHT")
    highlight:SetAllPoints()
    highlight:SetTexture("Interface\\Buttons\\WHITE8x8")
    button.__lidHighlight = highlight
  end
  highlight:SetVertexColor(hover[1] or 1, hover[2] or 1, hover[3] or 1, 0.10)

  local label = button:GetFontString()
  if not label then
    label = button:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    label:SetPoint("CENTER", 0, 0)
    if button.SetFontString then
      button:SetFontString(label)
    end
  end
  if label then
    label:SetTextColor(textColor[1] or 1, textColor[2] or 1, textColor[3] or 1, textColor[4] or 1)
  end
end

local function LayoutRollOptionRow(row, availableWidth)
  if not row then
    return
  end

  local rowWidth = math.max(220, math.floor(availableWidth or 220))
  local gap = 8
  local categoryGap = 14
  local priorityWidth = 22
  local moveWidth = 22
  local inlineIconTrigger = row.__iconDropdownIsTrigger and true or false
  local iconPreviewWidth = inlineIconTrigger and 0 or 18
  local showIconPreview = (not inlineIconTrigger) and rowWidth >= 320
  local showIconSelector = inlineIconTrigger and (rowWidth >= 320) or (rowWidth >= 400)
  local showColor = rowWidth >= 500
  local showPointDetails = rowWidth >= 560
  local showCategory = rowWidth >= 700
  local iconDropdownWidth = showIconSelector and (inlineIconTrigger and 24 or ClampLayoutValue(math.floor(rowWidth * 0.18), 76, 136)) or 0
  local behaviorWidth = showPointDetails and ClampLayoutValue(math.floor(rowWidth * 0.16), 92, 132) or 0
  local sectionWidth = showCategory and ClampLayoutValue(math.floor(rowWidth * 0.14), 84, 116) or 0
  local colorWidth = showColor and 24 or 0

  local function ResolveDescriptionWidth(includePointDetails, includeCategory)
    local fixedWidth = 26 + priorityWidth + moveWidth + moveWidth
    local gapWidth = gap + 2 + gap
    if showIconPreview then
      fixedWidth = fixedWidth + iconPreviewWidth
      gapWidth = gapWidth + gap
    end
    if showIconSelector then
      fixedWidth = fixedWidth + iconDropdownWidth
      gapWidth = gapWidth + gap
    end
    if showColor then
      fixedWidth = fixedWidth + colorWidth
      gapWidth = gapWidth + gap
    end
    if includePointDetails then
      fixedWidth = fixedWidth + behaviorWidth
      gapWidth = gapWidth + gap
    end
    if includeCategory then
      fixedWidth = fixedWidth + sectionWidth
      gapWidth = gapWidth + categoryGap
    end
    return rowWidth - fixedWidth - gapWidth
  end

  local descriptionWidth = ResolveDescriptionWidth(showPointDetails, showCategory)
  if descriptionWidth < 108 and showCategory then
    showCategory = false
    sectionWidth = 0
    descriptionWidth = ResolveDescriptionWidth(showPointDetails, showCategory)
  end
  if descriptionWidth < 108 and showPointDetails then
    showPointDetails = false
    showCategory = false
    behaviorWidth = 0
    sectionWidth = 0
    descriptionWidth = ResolveDescriptionWidth(showPointDetails, showCategory)
  end
  descriptionWidth = ClampLayoutValue(descriptionWidth, 92, 260)

  row:SetSize(rowWidth, 26)
  SetControlWidth(row.nameEdit, descriptionWidth)
  if row.iconPreview then
    row.iconPreview:SetShown(showIconPreview or showIconSelector)
  end
  if row.iconDropdown then
    SetControlWidth(row.iconDropdown, iconDropdownWidth)
    row.iconDropdown:SetShown(showIconSelector)
  end
  if row.colorButton then
    row.colorButton:SetShown(showColor)
  end
  if row.pointBehaviorDropdown then
    SetControlWidth(row.pointBehaviorDropdown, behaviorWidth)
    row.pointBehaviorDropdown:SetShown(showPointDetails)
  end
  if row.popupSectionDropdown then
    SetControlWidth(row.popupSectionDropdown, sectionWidth)
    row.popupSectionDropdown:SetShown(showCategory)
  end

  if row.indexLabel then
    row.indexLabel:ClearAllPoints()
    row.indexLabel:SetPoint("LEFT", 0, 0)
  end
  if row.moveUpBtn then
    row.moveUpBtn:ClearAllPoints()
    row.moveUpBtn:SetPoint("LEFT", row.indexLabel, "RIGHT", gap, 0)
  end
  if row.moveDownBtn then
    row.moveDownBtn:ClearAllPoints()
    row.moveDownBtn:SetPoint("LEFT", row.moveUpBtn, "RIGHT", 2, 0)
  end
  if row.nameEdit then
    row.nameEdit:ClearAllPoints()
    row.nameEdit:SetPoint("LEFT", row.moveDownBtn, "RIGHT", gap, 0)
  end
  if row.iconPreview and showIconPreview then
    row.iconPreview:ClearAllPoints()
    row.iconPreview:SetPoint("LEFT", row.nameEdit, "RIGHT", gap, 0)
  end
  if row.iconDropdown and showIconSelector then
    row.iconDropdown:ClearAllPoints()
    row.iconDropdown:SetPoint("LEFT", showIconPreview and row.iconPreview or row.nameEdit, "RIGHT", gap, 0)
  end
  if row.colorButton and showColor then
    row.colorButton:ClearAllPoints()
    row.colorButton:SetPoint("LEFT", showIconSelector and row.iconDropdown or (showIconPreview and row.iconPreview or row.nameEdit), "RIGHT", gap, 0)
  end
  if row.pointBehaviorDropdown and showPointDetails then
    row.pointBehaviorDropdown:ClearAllPoints()
    row.pointBehaviorDropdown:SetPoint("LEFT", showColor and row.colorButton or (showIconSelector and row.iconDropdown or (showIconPreview and row.iconPreview or row.nameEdit)), "RIGHT", gap, 0)
  end
  if row.popupSectionDropdown and showCategory then
    row.popupSectionDropdown:ClearAllPoints()
    row.popupSectionDropdown:SetPoint("LEFT", row.pointBehaviorDropdown, "RIGHT", categoryGap, 0)
  end
  if row.colorLabel then
    row.colorLabel:Hide()
  end

  local descriptionX = 88
  local iconX = descriptionX + descriptionWidth + gap
  local colorX = iconX
  if showIconPreview or showIconSelector then
    colorX = iconX
      + (showIconPreview and iconPreviewWidth or 0)
      + (showIconPreview and showIconSelector and gap or 0)
      + (showIconSelector and iconDropdownWidth or 0)
      + gap
  end
  local pointDetailsX = colorX + (showColor and colorWidth + gap or 0)
  local categoryX = pointDetailsX + (showPointDetails and behaviorWidth or 0) + (showPointDetails and categoryGap or 0)

  row.__layout = {
    priorityX = 0,
    priorityWidth = 80,
    descriptionX = descriptionX,
    descriptionWidth = descriptionWidth,
    iconX = iconX,
    iconWidth = (showIconPreview and iconPreviewWidth or 0) + (showIconSelector and ((showIconPreview and gap or 0) + iconDropdownWidth) or 0),
    colorX = colorX,
    colorWidth = colorWidth,
    pointDetailsX = pointDetailsX,
    pointDetailsWidth = behaviorWidth,
    categoryX = categoryX,
    categoryWidth = sectionWidth,
    showIcon = showIconPreview or showIconSelector,
    showColor = showColor,
    showPointDetails = showPointDetails,
    showCategory = showCategory,
    compactHint = (not showPointDetails and not showCategory) and "|cffff5a5a(!) Compact view: widen the editor to see Point Details and Category.|r"
      or ((not showCategory) and "|cffff5a5a(!) Compact view: widen the editor to see Category.|r" or nil),
  }
end

local function LayoutLidTableEditorRow(row, availableWidth)
  if not row then
    return 52, 68, 72, 72, 72, 72
  end

  local rowWidth = math.max(220, math.floor(availableWidth or 220))
  local gap = 10
  local pointsWidth = ClampLayoutValue(math.floor(rowWidth * 0.11), 44, 52)
  local itemLevelWidth = ClampLayoutValue(math.floor(rowWidth * 0.16), 54, 72)
  local setPieceWidth = ClampLayoutValue(math.floor(rowWidth * 0.18), 58, 78)
  local offSpecWidth = ClampLayoutValue(math.floor(rowWidth * 0.16), 54, 72)
  local transmogWidth = ClampLayoutValue(math.floor(rowWidth * 0.16), 54, 72)
  local disenchantWidth = ClampLayoutValue(math.floor(rowWidth * 0.16), 54, 72)
  local showSetPiece = rowWidth >= 360
  local showOffSpec = rowWidth >= 470
  local showTransmog = rowWidth >= 580
  local showDisenchant = rowWidth >= 690

  local function FitsCurrentLayout(includeSetPiece, includeOffSpec, includeTransmog, includeDisenchant)
    local usedWidth = 48 + pointsWidth + itemLevelWidth + gap
    if includeSetPiece then
      usedWidth = usedWidth + setPieceWidth + gap
    end
    if includeOffSpec then
      usedWidth = usedWidth + offSpecWidth + gap
    end
    if includeTransmog then
      usedWidth = usedWidth + transmogWidth + (includeDisenchant and gap or 0)
    end
    if includeDisenchant then
      usedWidth = usedWidth + disenchantWidth
    end
    return usedWidth <= rowWidth
  end

  while not FitsCurrentLayout(showSetPiece, showOffSpec, showTransmog, showDisenchant) do
    if showDisenchant then
      showDisenchant = false
    elseif showTransmog then
      showTransmog = false
    elseif showOffSpec then
      showOffSpec = false
    elseif showSetPiece then
      showSetPiece = false
    else
      local reduceItem = math.min(math.max(0, itemLevelWidth - 48), 8)
      itemLevelWidth = itemLevelWidth - reduceItem
      if reduceItem <= 0 then
        break
      end
    end
  end

  row:SetSize(rowWidth, 22)
  SetControlWidth(row.pointsEdit, pointsWidth)
  SetControlWidth(row.itemLevelEdit, itemLevelWidth)
  if row.setPieceEdit then
    SetControlWidth(row.setPieceEdit, setPieceWidth)
    row.setPieceEdit:SetShown(showSetPiece)
  end
  if row.offSpecEdit then
    SetControlWidth(row.offSpecEdit, offSpecWidth)
    row.offSpecEdit:SetShown(showOffSpec)
  end
  if row.transmogEdit then
    SetControlWidth(row.transmogEdit, transmogWidth)
    row.transmogEdit:SetShown(showTransmog)
  end
  if row.disenchantEdit then
    SetControlWidth(row.disenchantEdit, disenchantWidth)
    row.disenchantEdit:SetShown(showDisenchant)
  end

  if row.pointsEdit then
    row.pointsEdit:ClearAllPoints()
    row.pointsEdit:SetPoint("LEFT", 48, 0)
  end
  if row.itemLevelEdit then
    row.itemLevelEdit:ClearAllPoints()
    row.itemLevelEdit:SetPoint("LEFT", row.pointsEdit, "RIGHT", gap, 0)
  end
  if row.setPieceEdit and showSetPiece then
    row.setPieceEdit:ClearAllPoints()
    row.setPieceEdit:SetPoint("LEFT", row.itemLevelEdit, "RIGHT", gap, 0)
  end
  if row.offSpecEdit and showOffSpec then
    row.offSpecEdit:ClearAllPoints()
    row.offSpecEdit:SetPoint("LEFT", showSetPiece and row.setPieceEdit or row.itemLevelEdit, "RIGHT", gap, 0)
  end
  if row.transmogEdit and showTransmog then
    row.transmogEdit:ClearAllPoints()
    row.transmogEdit:SetPoint("LEFT", showOffSpec and row.offSpecEdit or (showSetPiece and row.setPieceEdit or row.itemLevelEdit), "RIGHT", gap, 0)
  end
  if row.disenchantEdit and showDisenchant then
    row.disenchantEdit:ClearAllPoints()
    row.disenchantEdit:SetPoint("LEFT", showTransmog and row.transmogEdit or (showOffSpec and row.offSpecEdit or (showSetPiece and row.setPieceEdit or row.itemLevelEdit)), "RIGHT", gap, 0)
  end

  row.__layout = {
    pointsWidth = pointsWidth,
    itemLevelWidth = itemLevelWidth,
    setPieceWidth = setPieceWidth,
    offSpecWidth = offSpecWidth,
    transmogWidth = transmogWidth,
    disenchantWidth = disenchantWidth,
    showSetPiece = showSetPiece,
    showOffSpec = showOffSpec,
    showTransmog = showTransmog,
    showDisenchant = showDisenchant,
  }

  return pointsWidth, itemLevelWidth, showSetPiece and setPieceWidth or 0, showOffSpec and offSpecWidth or 0, showTransmog and transmogWidth or 0, showDisenchant and disenchantWidth or 0
end

local ROLL_TEMPLATE_CHOICES = {
  { value = "DEFAULT_WOW", text = "Default WoW Template" },
  { value = "LIDCOUNCIL", text = "LidCouncil Template" },
}

local function NormalizeRollTemplateValue(value)
  local text = string.upper(TrimText(value))
  if text == "LIDCOUNCIL" then
    return "LIDCOUNCIL"
  end
  if text == "DEFAULT_WOW" or text == "DEFAULTWOW" or text == "WOW" or text == "DEFAULT" then
    return "DEFAULT_WOW"
  end
  return "CUSTOM"
end

local function ResolveRollTemplate(profile)
  local rollTemplate = profile and profile.settings and profile.settings.rollTemplate or nil
  return NormalizeRollTemplateValue(rollTemplate)
end

local function GetRollTemplateText(value)
  local resolved = NormalizeRollTemplateValue(value)
  if resolved == "LIDCOUNCIL" then
    return "LidCouncil Template"
  end
  if resolved == "DEFAULT_WOW" then
    return "Default WoW Template"
  end
  return "Custom"
end

local function BuildRollOptionsText(profile)
  local options = profile and profile.settings and profile.settings.rollOptions or {}
  if type(options) ~= "table" or #options == 0 then
    return "No roll options configured yet."
  end
  return ""
end

local DEFAULT_ROLL_OPTION_ICON_CHOICES = {
  { label = "Default / None", texture = nil },
  { label = "Dice", texture = "Interface\\Buttons\\UI-GroupLoot-Dice-Up" },
  { label = "Coin", texture = "Interface\\Buttons\\UI-GroupLoot-Coin-Up" },
  { label = "Pass", texture = "Interface\\Buttons\\UI-GroupLoot-Pass-Up" },
  { label = "Question Mark", texture = "Interface\\Icons\\INV_Misc_QuestionMark" },
  { label = "Bag", texture = "Interface\\Icons\\INV_Misc_Bag_10_Blue" },
  { label = "Chest", texture = "Interface\\Icons\\INV_Chest_Cloth_17" },
  { label = "Gem", texture = "Interface\\Icons\\INV_Misc_Gem_Pearl_04" },
  { label = "Coin Stack", texture = "Interface\\Icons\\INV_Misc_Coin_02" },
  { label = "Sword", texture = "Interface\\Icons\\INV_Sword_04" },
  { label = "Shield", texture = "Interface\\Icons\\INV_Shield_06" },
  { label = "Helm", texture = "Interface\\Icons\\INV_Helmet_15" },
  { label = "Ring", texture = "Interface\\Icons\\INV_Jewelry_Ring_55" },
  { label = "Trinket", texture = "Interface\\Icons\\INV_Jewelry_Talisman_01" },
  { label = "Cloak", texture = "Interface\\Icons\\INV_Misc_Cape_18" },
  { label = "Boots", texture = "Interface\\Icons\\INV_Boots_Cloth_03" },
  { label = "Gloves", texture = "Interface\\Icons\\INV_Gauntlets_19" },
  { label = "Book", texture = "Interface\\Icons\\INV_Misc_Book_09" },
}

local function ResolveTextureAsset(value)
  if value == nil or value == false then
    return nil
  end
  if type(value) == "number" then
    return value
  end
  local text = TrimText(value)
  if text == "" then
    return nil
  end
  local numeric = tonumber(text)
  if numeric then
    return numeric
  end
  return text
end

local function ResolveDefaultRollOptionIcon(key, label)
  local text = string.lower(TrimText(key ~= "" and key or label))
  if text == "need" then
    return "Interface\\Buttons\\UI-GroupLoot-Dice-Up"
  elseif text == "greed" then
    return "Interface\\Buttons\\UI-GroupLoot-Coin-Up"
  elseif text == "pass" then
    return "Interface\\Buttons\\UI-GroupLoot-Pass-Up"
  end
  return nil
end

local function ResolveRollOptionIcon(option)
  if type(option) ~= "table" then
    return nil
  end
  local resolved = ResolveTextureAsset(option.icon)
  if resolved ~= nil then
    return resolved
  end
  return ResolveDefaultRollOptionIcon(option.key, option.label)
end

local function GetRollOptionIconLabel(texture)
  local resolved = ResolveTextureAsset(texture)
  if resolved == nil then
    return "Default / None"
  end
  for _, entry in ipairs(DEFAULT_ROLL_OPTION_ICON_CHOICES) do
    if tostring(entry.texture or "") == tostring(resolved or "") then
      return tostring(entry.label or "Icon")
    end
  end

  local iconNames = _G.LIDCOUNCIL_ICON_FILE_NAMES
  local api = rawget(_G, "LIDCOUNCIL_API")
  if type(iconNames) ~= "table" and type(api) == "table" then
    iconNames = api.LIDCOUNCIL_ICON_FILE_NAMES
  end
  if type(resolved) == "number" and type(iconNames) == "table" and iconNames[resolved] then
    return tostring(iconNames[resolved])
  end

  local text = tostring(resolved)
  if text:find("\\", 1, true) then
    return text:match("([^\\]+)$") or text
  end
  return text
end

local function EnsureRollOptionRows(rows, editor, parent, count)
  rows = rows or {}
  while #rows < count do
    local row = CreateFrame("Frame", nil, parent)
    row:SetSize(640, 26)

    local indexLabel = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    indexLabel:SetPoint("LEFT", 0, 0)
    indexLabel:SetWidth(22)
    indexLabel:SetJustifyH("LEFT")
    row.indexLabel = indexLabel

    local moveUpBtn = CreateFrame("Button", nil, row, "BackdropTemplate")
    moveUpBtn:SetSize(22, 22)
    moveUpBtn:SetPoint("LEFT", 26, 0)
    ApplyMiniActionButtonStyle(moveUpBtn)
    if moveUpBtn.GetFontString then
      local label = moveUpBtn:GetFontString()
      if label then
        label:SetText("^")
      end
    end
    row.moveUpBtn = moveUpBtn

    local moveDownBtn = CreateFrame("Button", nil, row, "BackdropTemplate")
    moveDownBtn:SetSize(22, 22)
    moveDownBtn:SetPoint("LEFT", moveUpBtn, "RIGHT", 2, 0)
    ApplyMiniActionButtonStyle(moveDownBtn)
    if moveDownBtn.GetFontString then
      local label = moveDownBtn:GetFontString()
      if label then
        label:SetText("v")
      end
    end
    row.moveDownBtn = moveDownBtn

    local nameEdit = CreateSimpleEditBox(row, 114, 22)
    nameEdit:SetPoint("LEFT", moveDownBtn, "RIGHT", 8, 0)
    nameEdit:SetMaxLetters(40)
    row.nameEdit = nameEdit

    local iconDropdown
    if type(CreateCustomDropdownControl) == "function" then
      iconDropdown = CreateCustomDropdownControl(editor, row, {
        width = 24,
        buttonHeight = 24,
        text = "",
      })
      iconDropdown:SetPoint("LEFT", nameEdit, "RIGHT", 8, 0)
      row.__iconDropdownIsTrigger = true
      if iconDropdown.button then
        if iconDropdown.button.text then
          iconDropdown.button.text:Hide()
        end
        if iconDropdown.button.arrow then
          iconDropdown.button.arrow:Hide()
        end
        local iconPreview = iconDropdown.button:CreateTexture(nil, "ARTWORK")
        iconPreview:SetPoint("TOPLEFT", 3, -3)
        iconPreview:SetPoint("BOTTOMRIGHT", -3, 3)
        row.iconPreview = iconPreview
      end
    else
      local iconPreview = row:CreateTexture(nil, "ARTWORK")
      iconPreview:SetSize(18, 18)
      iconPreview:SetPoint("LEFT", nameEdit, "RIGHT", 8, 0)
      row.iconPreview = iconPreview
      iconDropdown = CreateSimpleEditBox(row, 132, 22)
      iconDropdown:SetPoint("LEFT", row.iconPreview, "RIGHT", 8, 0)
    end
    row.iconDropdown = iconDropdown

    local pointBehaviorDropdown
    if type(CreateCustomDropdownControl) == "function" then
      pointBehaviorDropdown = CreateCustomDropdownControl(editor, row, {
        width = 124,
        buttonHeight = 22,
        text = "Itemlevel Based",
      })
      pointBehaviorDropdown:SetPoint("LEFT", iconDropdown, "RIGHT", 8, 0)
    else
      pointBehaviorDropdown = CreateSimpleEditBox(row, 118, 22)
      pointBehaviorDropdown:SetPoint("LEFT", iconDropdown, "RIGHT", 8, 0)
    end
    row.pointBehaviorDropdown = pointBehaviorDropdown

    local popupSectionDropdown
    if type(CreateCustomDropdownControl) == "function" then
      popupSectionDropdown = CreateCustomDropdownControl(editor, row, {
        width = 118,
        buttonHeight = 22,
        text = "Need",
      })
      popupSectionDropdown:SetPoint("LEFT", pointBehaviorDropdown, "RIGHT", 8, 0)
    else
      popupSectionDropdown = CreateSimpleEditBox(row, 118, 22)
      popupSectionDropdown:SetPoint("LEFT", pointBehaviorDropdown, "RIGHT", 8, 0)
    end
    row.popupSectionDropdown = popupSectionDropdown

    local colorButton = CreateFrame("Button", nil, row, "BackdropTemplate")
    colorButton:SetSize(24, 24)
    colorButton:SetPoint("LEFT", iconDropdown, "RIGHT", 8, 0)
    colorButton:SetBackdrop({
      bgFile = "Interface\\Buttons\\WHITE8x8",
      edgeFile = "Interface\\Buttons\\WHITE8x8",
      edgeSize = 1,
    })
    colorButton:SetBackdropColor(0.08, 0.05, 0.06, 0.96)
    do
      local themePalette = GetEditorThemePalette and GetEditorThemePalette() or nil
      local border = type(themePalette) == "table" and themePalette.border or { 0.74, 0.22, 0.30, 0.95 }
      colorButton:SetBackdropBorderColor(border[1] or 0.74, border[2] or 0.22, border[3] or 0.30, border[4] or 0.95)
    end
    local swatch = colorButton:CreateTexture(nil, "ARTWORK")
    swatch:SetPoint("TOPLEFT", 3, -3)
    swatch:SetPoint("BOTTOMRIGHT", -3, 3)
    swatch:SetTexture("Interface\\Buttons\\WHITE8x8")
    row.colorButton = colorButton
    row.colorSwatch = swatch

    local colorLabel = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    colorLabel:SetPoint("LEFT", colorButton, "RIGHT", 8, 0)
    colorLabel:SetText("Color")
    row.colorLabel = colorLabel

    rows[#rows + 1] = row
  end
  return rows
end

local function EnsureLidTableEditorRows(rows, parent, count)
  rows = rows or {}
  while #rows < count do
    local index = #rows + 1
    local row = CreateFrame("Frame", nil, parent)
    row:SetSize(520, 22)
    row.__rowIndex = index

    local rankLabel = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    rankLabel:SetPoint("LEFT", 0, 0)
    rankLabel:SetWidth(40)
    rankLabel:SetJustifyH("LEFT")
    rankLabel:SetText(string.format("%02d", index))
    row.rankLabel = rankLabel

    local pointsEdit = CreateSimpleEditBox(row, 56, 22)
    pointsEdit:SetPoint("LEFT", 48, 0)
    pointsEdit:SetMaxLetters(4)
    row.pointsEdit = pointsEdit

    local itemLevelEdit = CreateSimpleEditBox(row, 84, 22)
    itemLevelEdit:SetPoint("LEFT", pointsEdit, "RIGHT", 12, 0)
    itemLevelEdit:SetMaxLetters(4)
    row.itemLevelEdit = itemLevelEdit

    local setPieceEdit = CreateSimpleEditBox(row, 84, 22)
    setPieceEdit:SetPoint("LEFT", itemLevelEdit, "RIGHT", 12, 0)
    setPieceEdit:SetMaxLetters(4)
    row.setPieceEdit = setPieceEdit

    local offSpecEdit = CreateSimpleEditBox(row, 72, 22)
    offSpecEdit:SetPoint("LEFT", setPieceEdit, "RIGHT", 12, 0)
    offSpecEdit:SetMaxLetters(4)
    row.offSpecEdit = offSpecEdit

    local transmogEdit = CreateSimpleEditBox(row, 72, 22)
    transmogEdit:SetPoint("LEFT", offSpecEdit, "RIGHT", 12, 0)
    transmogEdit:SetMaxLetters(4)
    row.transmogEdit = transmogEdit

    local disenchantEdit = CreateSimpleEditBox(row, 72, 22)
    disenchantEdit:SetPoint("LEFT", transmogEdit, "RIGHT", 12, 0)
    disenchantEdit:SetMaxLetters(4)
    row.disenchantEdit = disenchantEdit

    rows[index] = row
  end
  return rows
end

local LOOT_ITEM_MENU_PAGE_SIZE = 14
local raidEntriesCache
local raidEncounterEntriesCache = {}
local raidLootEntriesCache = {}

local RAID_LOOT_DIFFICULTIES = {
  { id = 14, name = "Normal" },
  { id = 15, name = "Heroic" },
  { id = 16, name = "Mythic" },
}

local function ExtractItemID(value)
  if type(value) == "number" then
    return math.floor(value)
  end
  local text = TrimText(value)
  if text == "" then
    return nil
  end
  local direct = tonumber(text)
  if direct then
    return math.floor(direct)
  end
  local linkID = text:match("item:(%d+)")
  if linkID then
    return tonumber(linkID)
  end
  return nil
end

local function ExtractItemLinkName(value)
  local text = tostring(value or "")
  return TrimText(text:match("%[(.-)%]") or "")
end

local function FormatPointRuleItemLabel(rule)
  if type(rule) ~= "table" then
    return "?"
  end
  local name = TrimText(rule.itemName or "")
  local itemLevel = tonumber(rule.itemLevel)
  if name ~= "" then
    if itemLevel then
      return string.format("%s (iLvl %d)", name, itemLevel)
    end
    return name
  end
  local linkName = ExtractItemLinkName(rule.itemLink)
  if linkName ~= "" then
    if itemLevel then
      return string.format("%s (iLvl %d)", linkName, itemLevel)
    end
    return linkName
  end
  local itemID = tonumber(rule.itemID)
  if itemID then
    if itemLevel then
      return string.format("Item #%d (iLvl %d)", itemID, itemLevel)
    end
    return "Item #" .. tostring(itemID)
  end
  return "?"
end

local function FormatRaidLootItemLabel(item)
  if type(item) ~= "table" then
    return "?"
  end

  local itemID = tonumber(item.itemID)
  local label = tostring(item.name or ("Item #" .. tostring(itemID or "?")))
  if tonumber(item.itemLevel) then
    label = string.format("%s (iLvl %d)", label, tonumber(item.itemLevel))
  end
  if TrimText(item.encounterName) ~= "" then
    label = label .. " - " .. tostring(item.encounterName)
  end
  return label
end

local function NormalizeInstanceTuple(a, b, c)
  if type(a) == "number" then
    return tonumber(a), TrimText(b)
  end
  if type(b) == "number" then
    return tonumber(b), TrimText(a)
  end
  if type(c) == "number" then
    return tonumber(c), TrimText(a ~= nil and a or b)
  end
  return nil, ""
end

local function NormalizeEncounterTuple(a, b, c, d, e, f, g)
  if type(a) == "number" then
    return tonumber(a), TrimText(b)
  end
  if type(c) == "number" then
    return tonumber(c), TrimText(a)
  end
  if type(b) == "number" then
    return tonumber(b), TrimText(a)
  end
  if type(g) == "number" then
    return tonumber(g), TrimText(a)
  end
  return nil, TrimText(a or b or "")
end

local function ResolveDetailedLootItemLevel(itemID, itemLink)
  local link = TrimText(itemLink)
  if link ~= "" and type(GetDetailedItemLevelInfo) == "function" then
    local ok, level = pcall(GetDetailedItemLevelInfo, link)
    if ok and tonumber(level) and tonumber(level) > 0 then
      return tonumber(level)
    end
  end
  local resolvedItemID = ExtractItemID(itemID or itemLink)
  if resolvedItemID and type(GetItemInfo) == "function" then
    local _, _, _, itemLevel = GetItemInfo(resolvedItemID)
    if tonumber(itemLevel) and tonumber(itemLevel) > 100 then
      return tonumber(itemLevel)
    end
  end
  return nil
end

local function NormalizeLootInfo(raw1, raw2, raw3, raw4, raw5, raw6, raw7, raw8)
  local itemID, name, icon, slot, link, quality, itemLevel
  if type(raw1) == "table" then
    itemID = ExtractItemID(raw1.itemID or raw1.id or raw1.link)
    name = TrimText(raw1.name or raw1.itemName or "")
    icon = raw1.icon or raw1.texture
    slot = TrimText(raw1.slot or raw1.invType or "")
    link = raw1.link or raw1.itemLink
    quality = tonumber(raw1.quality)
    itemLevel = tonumber(raw1.itemLevel or raw1.itemLvl or raw1.ilvl or raw1.previewItemLevel)
    if itemLevel and itemLevel < 100 then
      itemLevel = nil
    end
  else
    itemID = ExtractItemID(raw1 or raw6)
    name = TrimText(raw2 or "")
    icon = raw3
    slot = TrimText(raw4 or "")
    link = raw6
    quality = tonumber(raw7 or raw8)
    itemLevel = tonumber(raw5)
    if itemLevel and itemLevel < 100 then
      itemLevel = nil
    end
  end

  if name == "" and type(link) == "string" then
    name = ExtractItemLinkName(link)
  end
  if itemID and type(GetItemInfo) == "function" then
    local itemName, itemLink, itemQuality, resolvedItemLevel, _, _, _, _, _, itemIcon = GetItemInfo(itemID)
    if name == "" then
      name = TrimText(itemName or "")
    end
    if not link or link == "" then
      link = itemLink
    end
    if not icon then
      icon = itemIcon
    end
    if not quality then
      quality = tonumber(itemQuality)
    end
    if not itemLevel and tonumber(resolvedItemLevel) and tonumber(resolvedItemLevel) > 100 then
      itemLevel = tonumber(resolvedItemLevel)
    end
  end
  if not itemLevel then
    itemLevel = ResolveDetailedLootItemLevel(itemID, link)
  end
  if not itemID and name == "" then
    return nil
  end
  return {
    itemID = itemID,
    name = name ~= "" and name or ("Item #" .. tostring(itemID or "?")),
    icon = icon,
    slot = slot,
    link = link,
    quality = quality,
    itemLevel = itemLevel,
  }
end

local function EnsureEncounterJournalLoaded()
  if type(EncounterJournal_LoadUI) == "function" then
    pcall(EncounterJournal_LoadUI)
  end
  return type(EJ_GetNumTiers) == "function"
    and type(EJ_SelectTier) == "function"
    and type(EJ_GetInstanceByIndex) == "function"
    and type(EJ_GetEncounterInfoByIndex) == "function"
end

local function CollectRaidEntries()
  if type(raidEntriesCache) == "table" then
    return raidEntriesCache
  end

  local raids = {}
  if not EnsureEncounterJournalLoaded() then
    return raids
  end

  local currentTier = type(EJ_GetCurrentTier) == "function" and tonumber(EJ_GetCurrentTier()) or nil
  local tierCount = tonumber(EJ_GetNumTiers()) or 0
  for tierIndex = 1, tierCount do
    pcall(EJ_SelectTier, tierIndex)
    local tierName = type(EJ_GetTierInfo) == "function" and TrimText((select(1, EJ_GetTierInfo(tierIndex)))) or ("Tier " .. tostring(tierIndex))
    local instanceIndex = 1
    while true do
      local ok, r1, r2, r3 = pcall(EJ_GetInstanceByIndex, instanceIndex, true)
      if not ok or r1 == nil then
        break
      end
      local instanceID, instanceName = NormalizeInstanceTuple(r1, r2, r3)
      if instanceID then
        raids[#raids + 1] = {
          instanceID = instanceID,
          name = instanceName ~= "" and instanceName or ("Raid " .. tostring(instanceID)),
          tier = tierIndex,
          tierName = tierName,
        }
      end
      instanceIndex = instanceIndex + 1
    end
  end
  if currentTier then
    pcall(EJ_SelectTier, currentTier)
  end

  table.sort(raids, function(a, b)
    if tonumber(a.tier) == tonumber(b.tier) then
      return string.lower(tostring(a.name or "")) < string.lower(tostring(b.name or ""))
    end
    return (tonumber(a.tier) or 0) > (tonumber(b.tier) or 0)
  end)

  raidEntriesCache = raids
  return raids
end

local function GetRaidLootDifficultyName(difficultyID)
  local resolvedDifficultyID = tonumber(difficultyID)
  for _, difficulty in ipairs(RAID_LOOT_DIFFICULTIES) do
    if tonumber(difficulty.id) == resolvedDifficultyID then
      return tostring(difficulty.name or resolvedDifficultyID)
    end
  end
  return nil
end

local function CollectRaidEncounterEntries(instanceID)
  local resolvedInstanceID = tonumber(instanceID)
  if not resolvedInstanceID then
    return {}
  end
  if type(raidEncounterEntriesCache[resolvedInstanceID]) == "table" then
    return raidEncounterEntriesCache[resolvedInstanceID]
  end

  local encounters = {}
  if not EnsureEncounterJournalLoaded() then
    return encounters
  end

  local currentInstance = type(EJ_GetCurrentInstance) == "function" and tonumber(EJ_GetCurrentInstance()) or nil
  if type(EJ_SelectInstance) == "function" then
    pcall(EJ_SelectInstance, resolvedInstanceID)
  end

  local encounterIndex = 1
  while true do
    local ok, e1, e2, e3, e4, e5, e6, e7 = pcall(EJ_GetEncounterInfoByIndex, encounterIndex, resolvedInstanceID)
    if not ok or e1 == nil then
      break
    end
    local encounterID, encounterName = NormalizeEncounterTuple(e1, e2, e3, e4, e5, e6, e7)
    if encounterID then
      encounters[#encounters + 1] = {
        encounterID = encounterID,
        name = encounterName ~= "" and encounterName or ("Boss " .. tostring(encounterIndex)),
        index = encounterIndex,
      }
    end
    encounterIndex = encounterIndex + 1
  end

  if currentInstance and type(EJ_SelectInstance) == "function" then
    pcall(EJ_SelectInstance, currentInstance)
  end

  raidEncounterEntriesCache[resolvedInstanceID] = encounters
  return encounters
end

local function BuildRaidLootCacheKey(instanceID, encounterID, difficultyID, classID, specID)
  return tostring(tonumber(instanceID) or "") .. ":"
    .. tostring(tonumber(encounterID) or "ALL") .. ":"
    .. tostring(tonumber(difficultyID) or "CURRENT") .. ":"
    .. tostring(tonumber(classID) or "ALL") .. ":"
    .. tostring(tonumber(specID) or "ALL")
end

local function ApplyEncounterJournalDifficulty(difficultyID)
  local resolvedDifficultyID = tonumber(difficultyID)
  local currentDifficulty = type(EJ_GetDifficulty) == "function" and tonumber(EJ_GetDifficulty()) or nil
  if resolvedDifficultyID and type(EJ_SetDifficulty) == "function" then
    pcall(EJ_SetDifficulty, resolvedDifficultyID)
  end
  return currentDifficulty
end

local function RestoreEncounterJournalDifficulty(difficultyID)
  local resolvedDifficultyID = tonumber(difficultyID)
  if resolvedDifficultyID and type(EJ_SetDifficulty) == "function" then
    pcall(EJ_SetDifficulty, resolvedDifficultyID)
  end
end

local function ApplyEncounterJournalLootFilter(classID, specID)
  local currentClassID, currentSpecID
  if type(EJ_GetLootFilter) == "function" then
    local ok, c, s = pcall(EJ_GetLootFilter)
    if ok then
      currentClassID = tonumber(c)
      currentSpecID = tonumber(s)
    end
  end
  if type(EJ_SetLootFilter) == "function" then
    pcall(EJ_SetLootFilter, tonumber(classID) or 0, tonumber(specID) or 0)
  end
  return currentClassID, currentSpecID
end

local function RestoreEncounterJournalLootFilter(classID, specID)
  if type(EJ_SetLootFilter) == "function" then
    pcall(EJ_SetLootFilter, tonumber(classID) or 0, tonumber(specID) or 0)
  end
end

local function CollectRaidLootEntries(instanceID, encounterID, difficultyID, classID, specID)
  local resolvedInstanceID = tonumber(instanceID)
  if not resolvedInstanceID then
    return {}
  end
  local resolvedEncounterID = tonumber(encounterID)
  local resolvedDifficultyID = tonumber(difficultyID)
  local resolvedClassID = tonumber(classID)
  local resolvedSpecID = tonumber(specID)
  local implicitDifficultyID = (not resolvedDifficultyID) and type(EJ_GetDifficulty) == "function" and tonumber(EJ_GetDifficulty()) or nil
  local effectiveDifficultyID = resolvedDifficultyID or implicitDifficultyID
  local cacheKey = BuildRaidLootCacheKey(resolvedInstanceID, resolvedEncounterID, effectiveDifficultyID, resolvedClassID, resolvedSpecID)
  if type(raidLootEntriesCache[cacheKey]) == "table" then
    return raidLootEntriesCache[cacheKey]
  end

  local items = {}
  if not EnsureEncounterJournalLoaded() then
    return items
  end

  local getLootInfo = type(C_EncounterJournal) == "table" and type(C_EncounterJournal.GetLootInfoByIndex) == "function"
    and C_EncounterJournal.GetLootInfoByIndex
    or nil
  local currentInstance = type(EJ_GetCurrentInstance) == "function" and tonumber(EJ_GetCurrentInstance()) or nil
  local currentDifficulty = ApplyEncounterJournalDifficulty(resolvedDifficultyID)
  local currentClassID, currentSpecID = ApplyEncounterJournalLootFilter(resolvedClassID, resolvedSpecID)
  local difficultyName = GetRaidLootDifficultyName(effectiveDifficultyID)
  local seen = {}

  if type(EJ_SelectInstance) == "function" then
    pcall(EJ_SelectInstance, resolvedInstanceID)
  end

  local encounterEntries = {}
  if resolvedEncounterID then
    for _, encounter in ipairs(CollectRaidEncounterEntries(resolvedInstanceID)) do
      if tonumber(encounter.encounterID) == resolvedEncounterID then
        encounterEntries[#encounterEntries + 1] = encounter
        break
      end
    end
    if #encounterEntries == 0 then
      encounterEntries[#encounterEntries + 1] = {
        encounterID = resolvedEncounterID,
        name = "Selected Boss",
      }
    end
  else
    encounterEntries = CollectRaidEncounterEntries(resolvedInstanceID)
  end

  for _, encounter in ipairs(encounterEntries) do
    local encounterID = tonumber(encounter.encounterID)
    local encounterName = TrimText(encounter.name)
    if encounterID then
      if type(EJ_SelectEncounter) == "function" then
        pcall(EJ_SelectEncounter, encounterID)
      end
      local lootIndex = 1
      while true do
        local lootInfo
        if getLootInfo then
          local infoOk, raw = pcall(getLootInfo, lootIndex)
          if infoOk then
            lootInfo = NormalizeLootInfo(raw)
          end
          if not lootInfo then
            infoOk, raw = pcall(getLootInfo, lootIndex, encounterID)
            if infoOk then
              lootInfo = NormalizeLootInfo(raw)
            end
          end
        elseif type(EJ_GetLootInfoByIndex) == "function" then
          local infoOk, l1, l2, l3, l4, l5, l6, l7, l8 = pcall(EJ_GetLootInfoByIndex, lootIndex)
          if infoOk then
            lootInfo = NormalizeLootInfo(l1, l2, l3, l4, l5, l6, l7, l8)
          end
          if not lootInfo then
            infoOk, l1, l2, l3, l4, l5, l6, l7, l8 = pcall(EJ_GetLootInfoByIndex, lootIndex, encounterID)
            if infoOk then
              lootInfo = NormalizeLootInfo(l1, l2, l3, l4, l5, l6, l7, l8)
            end
          end
        end
        if not lootInfo then
          break
        end

        local dedupeKey = lootInfo.itemID and ("id:" .. tostring(lootInfo.itemID)) or string.lower(tostring(lootInfo.name or lootInfo.link or ""))
        if dedupeKey ~= "" and not seen[dedupeKey] then
          lootInfo.encounterID = encounterID
          lootInfo.encounterName = encounterName
          lootInfo.difficultyID = effectiveDifficultyID
          lootInfo.difficultyName = difficultyName
          items[#items + 1] = lootInfo
          seen[dedupeKey] = true
        end
        lootIndex = lootIndex + 1
      end
    end
  end

  if currentInstance and type(EJ_SelectInstance) == "function" then
    pcall(EJ_SelectInstance, currentInstance)
  end
  RestoreEncounterJournalDifficulty(currentDifficulty)
  RestoreEncounterJournalLootFilter(currentClassID, currentSpecID)

  table.sort(items, function(a, b)
    local aName = string.lower(tostring(a.name or ""))
    local bName = string.lower(tostring(b.name or ""))
    if aName == bName then
      return (tonumber(a.itemID) or 0) < (tonumber(b.itemID) or 0)
    end
    return aName < bName
  end)

  raidLootEntriesCache[cacheKey] = items
  return items
end

local function FindRaidEntryByProfile(profile)
  local raid = profile and profile.settings and profile.settings.raid or nil
  local storedInstanceID = raid and tonumber(raid.instanceID) or nil
  local storedName = TrimText(raid and raid.name or "")
  local seasonSnapshot = profile
    and profile.settings
    and type(profile.settings.seasonSnapshot) == "table"
    and profile.settings.seasonSnapshot
    or nil

  if type(seasonSnapshot) == "table" and type(seasonSnapshot.raids) == "table" then
    local snapshotTier = tonumber(seasonSnapshot.tier)
    local snapshotTierName = TrimText(seasonSnapshot.tierName) ~= "" and tostring(seasonSnapshot.tierName) or "Profile Season"
    if storedInstanceID then
      for _, entry in ipairs(seasonSnapshot.raids) do
        if tonumber(entry.instanceID) == storedInstanceID then
          return {
            instanceID = tonumber(entry.instanceID),
            name = tostring(entry.name or storedInstanceID),
            tier = snapshotTier,
            tierName = snapshotTierName,
            frozen = true,
          }
        end
      end
    end
    if storedName ~= "" then
      local needle = string.lower(storedName)
      for _, entry in ipairs(seasonSnapshot.raids) do
        if string.lower(tostring(entry.name or "")) == needle then
          return {
            instanceID = tonumber(entry.instanceID),
            name = tostring(entry.name or storedName),
            tier = snapshotTier,
            tierName = snapshotTierName,
            frozen = true,
          }
        end
      end
    end
    if storedInstanceID or storedName ~= "" then
      return {
        instanceID = storedInstanceID,
        name = storedName ~= "" and storedName or ("Raid " .. tostring(storedInstanceID or "?")),
        tier = snapshotTier,
        tierName = snapshotTierName,
        frozen = true,
        unresolved = true,
      }
    end
  end

  local raids = CollectRaidEntries()

  if storedInstanceID then
    for _, entry in ipairs(raids) do
      if tonumber(entry.instanceID) == storedInstanceID then
        return entry
      end
    end
  end

  if storedName ~= "" then
    local needle = string.lower(storedName)
    for _, entry in ipairs(raids) do
      if string.lower(tostring(entry.name or "")) == needle then
        return entry
      end
    end
    return {
      instanceID = storedInstanceID,
      name = storedName,
      unresolved = true,
    }
  end

  return nil
end

local function FindRaidLootEntry(instanceID, itemID)
  local resolvedInstanceID = tonumber(instanceID)
  local resolvedItemID = ExtractItemID(itemID)
  if not resolvedInstanceID or not resolvedItemID then
    return nil
  end
  for _, entry in ipairs(CollectRaidLootEntries(resolvedInstanceID)) do
    if tonumber(entry.itemID) == resolvedItemID then
      return entry
    end
  end
  return nil
end

local function EnsureLootLiveBoardMode(root)
  root = root or RootDB()
  root.ui = root.ui or {}
  if root.ui.lootLiveBoardMode ~= "POPOUT" and root.ui.lootLiveBoardMode ~= "EMBEDDED" then
    root.ui.lootLiveBoardMode = "EMBEDDED"
  end
  return root.ui.lootLiveBoardMode
end

local function ClampMenuPage(page, totalPages)
  local total = math.max(1, tonumber(totalPages) or 1)
  local resolvedPage = tonumber(page) or 1
  if resolvedPage < 1 then resolvedPage = 1 end
  if resolvedPage > total then resolvedPage = total end
  return resolvedPage
end

local function GetSelectedValuePage(entries, selectedValue)
  local needle = tostring(selectedValue or "")
  if needle == "" then
    return 1
  end
  for index, entry in ipairs(entries or {}) do
    if tostring(entry.itemID or entry.instanceID or entry.value or "") == needle then
      return math.floor((index - 1) / LOOT_ITEM_MENU_PAGE_SIZE) + 1
    end
  end
  return 1
end

local function EnsureLootProfileGroups(root)
  root = root or RootDB()
  root.profileGroups = root.profileGroups or {}

  local normalized = {}
  local seenGroups = {}
  for _, group in ipairs(root.profileGroups) do
    if type(group) == "table" then
      local groupName = TrimText(group.name or "")
      local groupKey = string.lower(groupName)
      if groupName ~= "" and not seenGroups[groupKey] then
        seenGroups[groupKey] = true
        local clean = {
          name = groupName,
          collapsed = group.collapsed == true,
          profiles = {},
        }
        local seenProfiles = {}
        for _, profileName in ipairs(group.profiles or {}) do
          local resolvedName = TrimText(profileName)
          if resolvedName ~= "" and resolvedName ~= "Default" and root.profiles[resolvedName] and not seenProfiles[resolvedName] then
            clean.profiles[#clean.profiles + 1] = resolvedName
            seenProfiles[resolvedName] = true
          end
        end
        table.sort(clean.profiles, function(a, b)
          return string.lower(tostring(a or "")) < string.lower(tostring(b or ""))
        end)
        normalized[#normalized + 1] = clean
      end
    end
  end

  table.sort(normalized, function(a, b)
    return string.lower(tostring(a and a.name or "")) < string.lower(tostring(b and b.name or ""))
  end)

  root.profileGroups = normalized
  return normalized
end

local function FindLootProfileGroupIndex(groups, groupName)
  local needle = string.lower(TrimText(groupName))
  for index, group in ipairs(groups or {}) do
    if string.lower(tostring(group and group.name or "")) == needle then
      return index
    end
  end
  return nil
end

local function RemoveLootProfileFromGroups(root, profileName)
  root = root or RootDB()
  local resolvedName = TrimText(profileName)
  if resolvedName == "" then
    return
  end
  for _, group in ipairs(EnsureLootProfileGroups(root)) do
    for index = #group.profiles, 1, -1 do
      if group.profiles[index] == resolvedName then
        table.remove(group.profiles, index)
      end
    end
  end
end

local function ToggleLootProfileGroupCollapsed(root, groupName)
  root = root or RootDB()
  local groups = EnsureLootProfileGroups(root)
  local groupIndex = FindLootProfileGroupIndex(groups, groupName)
  if not groupIndex then
    return false
  end
  groups[groupIndex].collapsed = not groups[groupIndex].collapsed
  return true
end

local function GetLootProfileGroupName(root, profileName)
  root = root or RootDB()
  local resolvedName = TrimText(profileName)
  if resolvedName == "" then
    return nil
  end
  for _, group in ipairs(EnsureLootProfileGroups(root)) do
    for _, groupedName in ipairs(group.profiles or {}) do
      if groupedName == resolvedName then
        return group.name
      end
    end
  end
  return nil
end

local function IsSimulationProfileVisibleInEditor(root)
  root = root or RootDB()
  local ui = type(root.ui) == "table" and root.ui or nil
  return type(root.profiles) == "table"
    and type(root.profiles.Simulation) == "table"
    and type(ui) == "table"
    and ui.developerMode == true
    and ui.showSimulationProfile == true
end

local function IsLootProfileVisibleInEditor(root, profileName)
  root = root or RootDB()
  local resolvedName = TrimText(profileName)
  if resolvedName == "" then
    return false
  end
  if resolvedName == "Default" then
    return false
  end
  if type(root.profiles) ~= "table" or type(root.profiles[resolvedName]) ~= "table" then
    return false
  end
  if resolvedName == "Simulation" then
    return IsSimulationProfileVisibleInEditor(root)
  end
  return true
end

local function GetVisibleLootProfileNames(root)
  root = root or RootDB()
  local names = {}
  for _, profileName in ipairs(GetLootProfileNames()) do
    if IsLootProfileVisibleInEditor(root, profileName) then
      names[#names + 1] = profileName
    end
  end
  return names
end

local function GetPreferredVisibleLootProfileName(root, requestedName)
  root = root or RootDB()

  local function ResolveCandidate(name)
    local resolvedName = TrimText(name)
    if resolvedName ~= "" and IsLootProfileVisibleInEditor(root, resolvedName) then
      return resolvedName
    end
    return nil
  end

  local primaryName = ResolveCandidate(GetPrimaryLootProfileName(requestedName or root.currentProfile))
  if primaryName then
    return primaryName
  end

  local requestedVisible = ResolveCandidate(requestedName)
  if requestedVisible then
    return requestedVisible
  end

  local currentVisible = ResolveCandidate(root.currentProfile)
  if currentVisible then
    return currentVisible
  end

  local fallbackVisible = ResolveCandidate(type(root.ui) == "table" and root.ui.lastNonSimulationProfile or nil)
  if fallbackVisible then
    return fallbackVisible
  end

  local names = GetVisibleLootProfileNames(root)
  return names[1]
end

local function BuildLootProfileNavRows(root)
  root = root or RootDB()
  local rows = {}
  local grouped = {}

  for _, group in ipairs(EnsureLootProfileGroups(root)) do
    local visibleChildren = {}
    for _, profileName in ipairs(group.profiles or {}) do
      if root.profiles[profileName] and profileName ~= "Default" and IsLootProfileVisibleInEditor(root, profileName) then
        visibleChildren[#visibleChildren + 1] = profileName
        grouped[profileName] = true
      end
    end

    if #visibleChildren > 0 then
      rows[#rows + 1] = {
        type = "group",
        key = "group:" .. string.lower(group.name or ""),
        name = group.name,
        collapsed = group.collapsed == true,
        childCount = #visibleChildren,
      }

      if group.collapsed ~= true then
        for _, profileName in ipairs(visibleChildren) do
          rows[#rows + 1] = {
            type = "profile",
            key = "profile:" .. profileName,
            name = profileName,
            groupName = group.name,
          }
        end
      end
    end
  end

  for _, profileName in ipairs(GetVisibleLootProfileNames(root)) do
    if not grouped[profileName] then
      rows[#rows + 1] = {
        type = "profile",
        key = "profile:" .. profileName,
        name = profileName,
        groupName = nil,
      }
    end
  end

  return rows
end

local function BuildPointRulesText(profile)
  local rules = profile and profile.settings and profile.settings.pointRules or {}
  if type(rules) ~= "table" or #rules == 0 then
    return "No point rules configured yet."
  end

  local lines = {}
  for i = 1, #rules do
    local rule = rules[i]
    if rule.mode == "item" then
      lines[#lines + 1] = string.format("%d. Item: %s -> %d points", i, FormatPointRuleItemLabel(rule), tonumber(rule.points) or 0)
    else
      lines[#lines + 1] = string.format("%d. Item Level %s -> %d points", i, tostring(rule.itemLevel or "?"), tonumber(rule.points) or 0)
    end
  end
  return table.concat(lines, "\n")
end

local POINT_MODE_CHOICES = {
  { value = "LIDTABLE", text = "LidTable" },
}

local function ResolvePointMode(profile)
  return "LIDTABLE"
end

local function GetLidTableRows(profile)
  local settings = profile and profile.settings or nil
  local storedRows = settings and settings.lidTable and settings.lidTable.rows or nil
  local rows = {}
  for index = 1, 15 do
    local row = type(storedRows) == "table" and storedRows[index] or nil
    rows[index] = {
      points = tonumber(row and row.points) or index,
      itemLevel = tonumber(row and row.itemLevel),
      setPiecePoints = tonumber(row and row.setPiecePoints),
      offSpecPoints = tonumber(row and row.offSpecPoints),
      transmogPoints = tonumber(row and row.transmogPoints),
      disenchantPoints = tonumber(row and row.disenchantPoints),
    }
  end
  return rows
end

local function BuildLidTableSummaryText(profile)
  local rows = GetLidTableRows(profile)
  local configuredCount = 0
  local highestItemLevel = nil
  for _, row in ipairs(rows) do
    local itemLevel = tonumber(row.itemLevel)
    if itemLevel then
      configuredCount = configuredCount + 1
      if not highestItemLevel or itemLevel > highestItemLevel then
        highestItemLevel = itemLevel
      end
    end
  end

  return string.format(
    "LidTable is active.\nEach row maps a point value to an item level.\nSetpiece, Off-Spec, Transmog, and Disenchant values are stored per mapped item level.\nConfigured rows: %d/15%s",
    configuredCount,
    highestItemLevel and (" | Highest mapped item level: " .. tostring(highestItemLevel)) or ""
  )
end

local function GetSelectedProfileName(editor)
  local root = RootDB()
  local preferredName = GetPreferredVisibleLootProfileName(root)
  local selected = editor and editor.selectedLootProfileName or nil
  local resolvedSelected = TrimText(selected)
  if resolvedSelected ~= "" and IsLootProfileVisibleInEditor(root, resolvedSelected) then
    preferredName = resolvedSelected
  end
  return preferredName
end
INTERNAL_API.LIDCOUNCIL_LootEditorShared_Impl = {
  RequireApi = RequireApi,
  BindMouseWheelScroll = BindMouseWheelScroll,
  RootDB = RootDB,
  ApplyFrameSkin = ApplyFrameSkin,
  MakeYellowSubHeader = MakeYellowSubHeader,
  GetCharacterKey = GetCharacterKey,
  GetLootProfile = GetLootProfile,
  GetLootProfileNames = GetLootProfileNames,
  GetVisibleLootProfileNames = GetVisibleLootProfileNames,
  GetPreferredVisibleLootProfileName = GetPreferredVisibleLootProfileName,
  IsLootProfileVisibleInEditor = IsLootProfileVisibleInEditor,
  GetPrimaryLootProfileName = GetPrimaryLootProfileName,
  GetLootProfileRoleEntries = GetLootProfileRoleEntries,
  GetLootProfileAccessInfo = GetLootProfileAccessInfo,
  GetLootProfileAccessInfoForRole = GetLootProfileAccessInfoForRole,
  GetLootProfilePermissionPolicyRoleState = GetLootProfilePermissionPolicyRoleState,
  SetCurrentLootProfile = SetCurrentLootProfile,
  CreateLootProfile = CreateLootProfile,
  DeleteLootProfile = DeleteLootProfile,
  CopyLootProfile = CopyLootProfile,
  ResetSimulationProfileContent = ResetSimulationProfileContent,
  SetLootProfileMemberRole = SetLootProfileMemberRole,
  SetLootProfilePermissionPolicyValue = SetLootProfilePermissionPolicyValue,
  ResetLootProfilePermissionPolicy = ResetLootProfilePermissionPolicy,
  TransferLootProfileMaster = TransferLootProfileMaster,
  SetLootProfileEnabled = SetLootProfileEnabled,
  SetLootProfileRaid = SetLootProfileRaid,
  ApplyLootRollTemplate = ApplyLootRollTemplate,
  AddLootRollOption = AddLootRollOption,
  UpdateLootRollOption = UpdateLootRollOption,
  MoveLootRollOption = MoveLootRollOption,
  RemoveLootRollOption = RemoveLootRollOption,
  UpdateLootPointMode = UpdateLootPointMode,
  UpdateLootLidTableRow = UpdateLootLidTableRow,
  AddLootPointRule = AddLootPointRule,
  RemoveLootPointRule = RemoveLootPointRule,
  StartLootSession = StartLootSession,
  StopLootSession = StopLootSession,
  GetActiveLootSession = GetActiveLootSession,
  AddLootLedgerEntry = AddLootLedgerEntry,
  RemoveLootLedgerEntry = RemoveLootLedgerEntry,
  GetLootLedgerCharacters = GetLootLedgerCharacters,
  ResolveOptionalApiFunction = ResolveOptionalApiFunction,
  CreateCustomDropdownControl = CreateCustomDropdownControl,
  TrimText = TrimText,
  FormatDateTime = FormatDateTime,
  FormatDuration = FormatDuration,
  CreateSimpleEditBox = CreateSimpleEditBox,
  SetListButtonSelected = SetListButtonSelected,
  SetTextListButtonTextColor = SetTextListButtonTextColor,
  EnsureTextListButtons = EnsureTextListButtons,
  CreateSectionShell = CreateSectionShell,
  ClampLayoutValue = ClampLayoutValue,
  GetResponsiveSectionLayout = GetResponsiveSectionLayout,
  SetControlWidth = SetControlWidth,
  GetFontStringWidth = GetFontStringWidth,
  CreateSectionDivider = CreateSectionDivider,
  FlowLayoutControls = FlowLayoutControls,
  LayoutRollOptionRow = LayoutRollOptionRow,
  LayoutLidTableEditorRow = LayoutLidTableEditorRow,
  BuildRollOptionsText = BuildRollOptionsText,
  ROLL_TEMPLATE_CHOICES = ROLL_TEMPLATE_CHOICES,
  DEFAULT_ROLL_OPTION_ICON_CHOICES = DEFAULT_ROLL_OPTION_ICON_CHOICES,
  ResolveTextureAsset = ResolveTextureAsset,
  ResolveDefaultRollOptionIcon = ResolveDefaultRollOptionIcon,
  ResolveRollOptionIcon = ResolveRollOptionIcon,
  GetRollOptionIconLabel = GetRollOptionIconLabel,
  ResolveRollTemplate = ResolveRollTemplate,
  GetRollTemplateText = GetRollTemplateText,
  EnsureRollOptionRows = EnsureRollOptionRows,
  EnsureLidTableEditorRows = EnsureLidTableEditorRows,
  ExtractItemID = ExtractItemID,
  ExtractItemLinkName = ExtractItemLinkName,
  FormatPointRuleItemLabel = FormatPointRuleItemLabel,
  FormatRaidLootItemLabel = FormatRaidLootItemLabel,
  LOOT_ITEM_MENU_PAGE_SIZE = LOOT_ITEM_MENU_PAGE_SIZE,
  NormalizeInstanceTuple = NormalizeInstanceTuple,
  NormalizeEncounterTuple = NormalizeEncounterTuple,
  NormalizeLootInfo = NormalizeLootInfo,
  EnsureEncounterJournalLoaded = EnsureEncounterJournalLoaded,
  RAID_LOOT_DIFFICULTIES = RAID_LOOT_DIFFICULTIES,
  CollectRaidEntries = CollectRaidEntries,
  CollectRaidEncounterEntries = CollectRaidEncounterEntries,
  CollectRaidLootEntries = CollectRaidLootEntries,
  FindRaidEntryByProfile = FindRaidEntryByProfile,
  FindRaidLootEntry = FindRaidLootEntry,
  EnsureLootLiveBoardMode = EnsureLootLiveBoardMode,
  ClampMenuPage = ClampMenuPage,
  GetSelectedValuePage = GetSelectedValuePage,
  EnsureLootProfileGroups = EnsureLootProfileGroups,
  FindLootProfileGroupIndex = FindLootProfileGroupIndex,
  RemoveLootProfileFromGroups = RemoveLootProfileFromGroups,
  ToggleLootProfileGroupCollapsed = ToggleLootProfileGroupCollapsed,
  GetLootProfileGroupName = GetLootProfileGroupName,
  BuildLootProfileNavRows = BuildLootProfileNavRows,
  BuildPointRulesText = BuildPointRulesText,
  ResolvePointMode = ResolvePointMode,
  GetLidTableRows = GetLidTableRows,
  BuildLidTableSummaryText = BuildLidTableSummaryText,
  POINT_MODE_CHOICES = POINT_MODE_CHOICES,
  GetSelectedProfileName = GetSelectedProfileName,
}

function LIDCOUNCIL_BuildLootProfilesPage(editor, page)
  local buildLootProfilesPage = INTERNAL_API.LIDCOUNCIL_BuildLootProfilesPage_Impl
  if type(buildLootProfilesPage) ~= 'function' then
    error('LidCouncil missing loot profiles page builder implementation.')
  end
  return buildLootProfilesPage(editor, page)
end

function LIDCOUNCIL_BuildLootSessionPage(editor, page)
  local buildLootSessionPage = INTERNAL_API.LIDCOUNCIL_BuildLootSessionPage_Impl
  if type(buildLootSessionPage) ~= 'function' then
    error('LidCouncil missing loot session page builder implementation.')
  end
  return buildLootSessionPage(editor, page)
end

function LIDCOUNCIL_BuildLootLiveBoardPage(editor, page)
  local buildLootLiveBoardPage = INTERNAL_API.LIDCOUNCIL_BuildLootLiveBoardPage_Impl
  if type(buildLootLiveBoardPage) ~= 'function' then
    error('LidCouncil missing loot live board page builder implementation.')
  end
  return buildLootLiveBoardPage(editor, page)
end

function LIDCOUNCIL_BuildLootListPage(editor, page)
  local buildLootListPage = INTERNAL_API.LIDCOUNCIL_BuildLootListPage_Impl
  if type(buildLootListPage) ~= 'function' then
    error('LidCouncil missing loot list page builder implementation.')
  end
  return buildLootListPage(editor, page)
end
