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 createBoardFrame = INTERNAL_API.LIDCOUNCIL_CreateBoardFrame_Impl
local applyPopupBoardLayout = INTERNAL_API.LIDCOUNCIL_ApplyPopupBoardLayout_Impl
local setBoardCollapsed = INTERNAL_API.LIDCOUNCIL_SetBoardCollapsed_Impl
if type(createBoardFrame) ~= "function" or type(applyPopupBoardLayout) ~= "function" or type(setBoardCollapsed) ~= "function" then
  error("LidCouncil missing loot runtime board helpers.")
end

local GetLootProfile = shared.GetLootProfile
local GetActiveLootSession = shared.GetActiveLootSession
local ApplyFrameSkin = shared.ApplyFrameSkin
local SendSessionComm = shared.SendSessionComm
local SendChoicePacket = shared.SendChoicePacket
local BroadcastAuthoritativeChoice = shared.BroadcastAuthoritativeChoice
local ResolveOptionalFunction = shared.ResolveOptionalFunction
local Print = shared.Print
local DeepCopy = shared.DeepCopy
local DEFAULT_OPTIONS = shared.DEFAULT_OPTIONS
local MAX_SELECTION_NOTE_LENGTH = shared.MAX_SELECTION_NOTE_LENGTH
local REAL_SIMULATION_ITEM_COUNT = shared.REAL_SIMULATION_ITEM_COUNT
local RuntimeState = shared.RuntimeState
local UIState = shared.UIState
local TrimText = shared.TrimText
local IsRealSimulationItemKey = shared.IsRealSimulationItemKey
local ExtractItemID = shared.ExtractItemID
local NormalizeSelectionNote = shared.NormalizeSelectionNote
local ResolveOptionIconTexture = shared.ResolveOptionIconTexture
local ResolveRollOptions = shared.ResolveRollOptions
local GetSelfKey = shared.GetSelfKey
local IsSelfHost = shared.IsSelfHost
local GetJoinedSessionState = shared.GetJoinedSessionState
local GetBroadcastDistribution = shared.GetBroadcastDistribution
local EnsureRuntimeSession = shared.EnsureRuntimeSession
local PersistRuntimeBoardHistory = shared.PersistRuntimeBoardHistory
local RefreshBoardAndEditor = shared.RefreshBoardAndEditor
local RecordChoice = shared.RecordChoice
local BuildPendingSelection = shared.BuildPendingSelection
local BuildSimulationBoardState = shared.BuildSimulationBoardState
local BuildRealSimulationItemDefs = shared.BuildRealSimulationItemDefs
local SetBoardPreviewState = shared.SetBoardPreviewState
local PersistBoardState = shared.PersistBoardState
local ApplySimulationChoice = shared.ApplySimulationChoice
local RenderAuthoritativeItemTooltip = shared.RenderAuthoritativeItemTooltip
local ResolveRuntimeItemDisplayText = shared.ResolveRuntimeItemDisplayText
local RefreshChoicePopup
local ShowCurrentChoicePopup
local RefreshHeaderPendingRollIndicator
local UpdateChoicePopupCommentPresentation
local ProcessPendingChoiceReceipts
local ArePopupCharacterKeysSame

local function BeginRollPerformanceSample(metricName)
  local beginSample = INTERNAL_API.LIDCOUNCIL_BeginSyncPerformanceSample_Impl
  if type(beginSample) == "function" then
    return beginSample(metricName, 1)
  end
  return nil
end

local function RecordRollPerformanceSample(metricName, startedAt, metadata)
  local recordSample = INTERNAL_API.LIDCOUNCIL_RecordSyncPerformance_Impl
  if type(recordSample) == "function" then
    recordSample(metricName, startedAt, metadata)
  end
end

local function RecordRaidTraceEvent(eventName, detail, fields)
  local recorder = INTERNAL_API.LIDCOUNCIL_RecordRaidTestTraceEvent_Impl
  if type(recorder) == "function" then
    recorder(eventName, detail, fields)
  end
end

local REAL_SIMULATION_ITEM_LOAD_TIMEOUT_SECONDS = 5.0
local REAL_SIMULATION_ITEM_LOAD_RETRY_SECONDS = 0.25
local REAL_SIMULATION_DEFINITION_REBUILD_SECONDS = 0.75
local REAL_SIMULATION_REQUEST_DEBOUNCE_SECONDS = 0.75
local QUESTION_MARK_ICON = "interface\\icons\\inv_misc_questionmark"

local function GetSimulationItemLoadClock()
  if type(GetTimePreciseSec) == "function" then
    return math.max(0, tonumber(GetTimePreciseSec()) or 0)
  end
  return math.max(0, tonumber(type(GetTime) == "function" and GetTime() or 0) or 0)
end

local function IsQuestionMarkSimulationIcon(icon)
  return string.lower(TrimText(icon)) == QUESTION_MARK_ICON
end

local function IsSimulationItemDefReady(itemDef)
  if type(itemDef) ~= "table" then
    return false
  end
  return ExtractItemID(itemDef.itemID or itemDef.link) ~= nil
    and TrimText(itemDef.name) ~= ""
    and TrimText(itemDef.link) ~= ""
    and itemDef.icon ~= nil
    and not IsQuestionMarkSimulationIcon(itemDef.icon)
    and tonumber(itemDef.quality) ~= nil
    and (tonumber(itemDef.itemLevel) or 0) > 0
end

local function ApplySimulationItemInfo(itemDef, name, link, quality, itemLevel, icon)
  if type(itemDef) ~= "table" then
    return false
  end
  if TrimText(itemDef.name) == "" and TrimText(name) ~= "" then itemDef.name = name end
  if TrimText(itemDef.link) == "" and TrimText(link) ~= "" then itemDef.link = link end
  if tonumber(itemDef.quality) == nil and tonumber(quality) then itemDef.quality = tonumber(quality) end
  if (tonumber(itemDef.itemLevel) or 0) <= 0 and tonumber(itemLevel) and tonumber(itemLevel) > 0 then
    itemDef.itemLevel = tonumber(itemLevel)
  end
  if (itemDef.icon == nil or IsQuestionMarkSimulationIcon(itemDef.icon)) and icon ~= nil then
    itemDef.icon = icon
  end
  return true
end

local function HydrateSimulationItemDef(itemDef)
  if type(itemDef) ~= "table" then
    return false
  end
  local itemID = ExtractItemID(itemDef.itemID or itemDef.link)
  if itemID then
    itemDef.itemID = itemID
  end
  local itemInfoSource = TrimText(itemDef.link) ~= "" and itemDef.link or itemID
  if itemInfoSource and type(GetItemInfo) == "function" then
    local name, link, quality, itemLevel, _, _, _, _, _, icon = GetItemInfo(itemInfoSource)
    ApplySimulationItemInfo(itemDef, name, link, quality, itemLevel, icon)
  end
  if itemID and type(C_Item) == "table" and type(C_Item.RequestLoadItemDataByID) == "function" then
    pcall(C_Item.RequestLoadItemDataByID, itemID)
  end
  return IsSimulationItemDefReady(itemDef)
end

local function InspectSimulationItemDefs(itemDefs)
  local readyCount = 0
  local itemIds = {}
  for _, itemDef in ipairs(type(itemDefs) == "table" and itemDefs or {}) do
    if HydrateSimulationItemDef(itemDef) then
      readyCount = readyCount + 1
    end
    local itemID = ExtractItemID(type(itemDef) == "table" and (itemDef.itemID or itemDef.link) or nil)
    if itemID then
      itemIds[#itemIds + 1] = tostring(itemID)
    end
  end
  return readyCount, #(type(itemDefs) == "table" and itemDefs or {}), table.concat(itemIds, ","), #itemIds
end

local function RunRealSimulationItemPreparationValidation()
  local checks = {}
  local passedCount = 0
  local function AddCheck(name, expected, actual)
    local passed = expected == actual
    checks[#checks + 1] = {
      name = name,
      expected = tostring(expected),
      actual = tostring(actual),
      passed = passed,
    }
    if passed then
      passedCount = passedCount + 1
    end
  end
  AddCheck("item preload uses a bounded retry window", "5.00/0.25/20/0.75/0.75", string.format(
    "%.2f/%.2f/%d/%.2f/%.2f",
    REAL_SIMULATION_ITEM_LOAD_TIMEOUT_SECONDS,
    REAL_SIMULATION_ITEM_LOAD_RETRY_SECONDS,
    math.floor((REAL_SIMULATION_ITEM_LOAD_TIMEOUT_SECONDS / REAL_SIMULATION_ITEM_LOAD_RETRY_SECONDS) + 0.5),
    REAL_SIMULATION_DEFINITION_REBUILD_SECONDS,
    REAL_SIMULATION_REQUEST_DEBOUNCE_SECONDS
  ))
  AddCheck("fully hydrated simulation item is admitted", true, IsSimulationItemDefReady({
    itemID = 19019,
    name = "Validation Item",
    link = "item:19019",
    icon = 134153,
    quality = 5,
    itemLevel = 80,
  }))
  AddCheck("question-mark simulation icon is rejected", false, IsSimulationItemDefReady({
    itemID = 19019,
    name = "Validation Item",
    link = "item:19019",
    icon = "Interface\\Icons\\INV_Misc_QuestionMark",
    quality = 5,
    itemLevel = 80,
  }))
  AddCheck("simulation item without an item level is rejected", false, IsSimulationItemDefReady({
    itemID = 19019,
    name = "Validation Item",
    link = "item:19019",
    icon = 134153,
    quality = 5,
  }))
  local difficultyItem = {
    itemID = 19019,
    name = "Encounter Journal Item",
    link = "item:19019",
    icon = "Interface\\Icons\\INV_Misc_QuestionMark",
    quality = 5,
    itemLevel = 639,
  }
  ApplySimulationItemInfo(difficultyItem, "Cached Item", "item:19019", 5, 80, 134153)
  AddCheck("item hydration preserves Encounter Journal difficulty level", "639/134153",
    tostring(difficultyItem.itemLevel) .. "/" .. tostring(difficultyItem.icon))
  return passedCount == #checks, {
    suite = "test_raid_item_preparation",
    total = #checks,
    passed = passedCount,
    failed = #checks - passedCount,
    checks = checks,
  }
end

local function RefreshVisibleLiveBoardSummary()
  if type(InCombatLockdown) == "function" and InCombatLockdown() then
    return false
  end
  local editor = rawget(_G, "LIDCOUNCIL_Editor")
  if type(editor) ~= "table" or editor.currentPage ~= "LiveBoard" or type(editor.RefreshLootLiveBoardSummaryOnly) ~= "function" then
    return false
  end
  local ok, refreshed = pcall(editor.RefreshLootLiveBoardSummaryOnly, editor)
  return ok and refreshed == true
end

local choiceReceiptRecovery = {
  maxAttempts = 4,
  retryIntervals = { 1.5, 3, 6, 8 },
  liveBurstWindowSeconds = 30,
  graceSeconds = 12,
}
local choiceReceiptRetryFrame

function choiceReceiptRecovery.IsRecentMatchingReceiver(receipt, receiver, now, requireItem)
  if type(receipt) ~= "table" or type(receiver) ~= "table" then
    return false
  end
  if TrimText(receipt.sessionId) == "" or TrimText(receipt.sessionId) ~= TrimText(receiver.sessionId) then
    return false
  end
  local receiptProfile = TrimText(receipt.profileName)
  local receiverProfile = TrimText(receiver.profileName)
  if receiptProfile ~= "" and receiverProfile ~= "" and receiptProfile ~= receiverProfile then
    return false
  end
  local receiverSender = TrimText(receiver.sender)
  if receiverSender ~= "" and not ArePopupCharacterKeysSame(receipt.host, receiverSender) then
    return false
  end
  if requireItem == true and TrimText(receipt.itemKey) ~= TrimText(receiver.itemKey) then
    return false
  end
  local receiverState = string.lower(TrimText(receiver.state))
  if receiverState == "rejected" or receiverState == "failed" or receiverState == "superseded" then
    return false
  end
  local activityAt = tonumber(receiver.activityClockAt) or 0
  local age = (tonumber(now) or 0) - activityAt
  return activityAt > 0 and age >= -1 and age <= choiceReceiptRecovery.liveBurstWindowSeconds
end

function choiceReceiptRecovery.GetLocalLiveQueueReason(queueStatus)
  if type(queueStatus) ~= "table" then
    return ""
  end
  local byPriority = type(queueStatus.byPriority) == "table" and queueStatus.byPriority or {}
  if (tonumber(byPriority["1"]) or 0) > 0 or (tonumber(byPriority["2"]) or 0) > 0 then
    return "local_live_queue"
  end
  local nextPriority = math.max(0, tonumber(queueStatus.nextPriority) or 0)
  if nextPriority > 0 and nextPriority <= 2 then
    return "local_live_queue"
  end
  return ""
end

function choiceReceiptRecovery.GetPressureReason(receipt, context, now)
  context = type(context) == "table" and context or {}
  local queueReason = choiceReceiptRecovery.GetLocalLiveQueueReason(context.queueStatus)
  if queueReason ~= "" then
    return queueReason
  end
  if choiceReceiptRecovery.IsRecentMatchingReceiver(receipt, context.itemReceiver, now, true) then
    return "live_item_burst"
  end
  if choiceReceiptRecovery.IsRecentMatchingReceiver(receipt, context.boardReceiver, now, false) then
    return "live_board_burst"
  end
  return ""
end

function choiceReceiptRecovery.Evaluate(receipt, context, now)
  receipt = type(receipt) == "table" and receipt or {}
  local resolvedNow = math.max(0, tonumber(now) or 0)
  local attempts = math.max(0, tonumber(receipt.attempts) or 0)
  if attempts < choiceReceiptRecovery.maxAttempts then
    return "retry", "attempt_available", 0
  end
  local graceExpiresAt = math.max(0, tonumber(receipt.queueGraceExpiresAt) or 0)
  if graceExpiresAt > 0 then
    if resolvedNow < graceExpiresAt then
      return "grace_wait", TrimText(receipt.queuePressureReason), graceExpiresAt
    end
    return "unconfirmed", "queue_grace_expired", graceExpiresAt
  end
  local pressureReason = choiceReceiptRecovery.GetPressureReason(receipt, context, resolvedNow)
  if pressureReason ~= "" then
    return "begin_grace", pressureReason, resolvedNow + choiceReceiptRecovery.graceSeconds
  end
  return "unconfirmed", "receipt_attempts_exhausted", 0
end

function choiceReceiptRecovery.CanRearm(selection, receipt)
  local receiptState = string.lower(TrimText(type(selection) == "table" and selection.receiptState or ""))
  if receiptState ~= "waiting" and receiptState ~= "unconfirmed" then
    return false
  end
  local selectionCount = math.max(0, math.floor(tonumber(
    type(selection) == "table" and selection.receiptRearmCount or 0
  ) or 0))
  local receiptCount = math.max(0, math.floor(tonumber(
    type(receipt) == "table" and receipt.rearmCount or 0
  ) or 0))
  return math.max(selectionCount, receiptCount) < 1
end

function choiceReceiptRecovery.GetContext()
  local queueStatus
  local getQueueStatus = ResolveOptionalFunction("LIDCOUNCIL_GetSessionCommQueueStatus")
  if type(getQueueStatus) == "function" then
    local statusOk, status = pcall(getQueueStatus)
    if statusOk and type(status) == "table" then
      queueStatus = status
    end
  end
  return {
    queueStatus = queueStatus,
    itemReceiver = type(RuntimeState.lastItemOpenReceiver) == "table" and RuntimeState.lastItemOpenReceiver or nil,
    boardReceiver = type(RuntimeState.lastBoardSyncReceiver) == "table" and RuntimeState.lastBoardSyncReceiver or nil,
  }
end

function choiceReceiptRecovery.RunValidation()
  local checks = {}
  local passedCount = 0
  local function addCheck(name, expected, actual)
    local passed = expected == actual
    checks[#checks + 1] = {
      name = name,
      expected = tostring(expected),
      actual = tostring(actual),
      passed = passed,
    }
    if passed then
      passedCount = passedCount + 1
    end
  end
  local baseReceipt = {
    sessionId = "VALIDATION-SESSION",
    profileName = "Validation Profile",
    host = "Host-Realm",
    itemKey = "VALIDATION-ITEM",
    attempts = 4,
  }
  local action, reason, deadline = choiceReceiptRecovery.Evaluate({ attempts = 3 }, {}, 100)
  addCheck("choice receipt preserves the four-send retry bound", "retry/attempt_available/0",
    table.concat({ tostring(action), tostring(reason), tostring(deadline) }, "/"))
  action, reason = choiceReceiptRecovery.Evaluate(baseReceipt, {}, 100)
  addCheck("choice receipt without pressure fails closed", "unconfirmed/receipt_attempts_exhausted",
    tostring(action) .. "/" .. tostring(reason))
  action, reason, deadline = choiceReceiptRecovery.Evaluate(baseReceipt, {
    itemReceiver = {
      sessionId = "VALIDATION-SESSION",
      profileName = "Validation Profile",
      sender = "Host-Realm",
      itemKey = "VALIDATION-ITEM",
      state = "admitted",
      activityClockAt = 90,
    },
  }, 100)
  addCheck("recent matching ITEM_OPEN starts bounded receipt grace", "begin_grace/live_item_burst/112",
    table.concat({ tostring(action), tostring(reason), tostring(deadline) }, "/"))
  action, reason, deadline = choiceReceiptRecovery.Evaluate(baseReceipt, {
    boardReceiver = {
      sessionId = "VALIDATION-SESSION",
      profileName = "Validation Profile",
      sender = "Host-Realm",
      state = "complete",
      activityClockAt = 80,
    },
  }, 100)
  addCheck("recent matching BSYNC starts bounded receipt grace", "begin_grace/live_board_burst/112",
    table.concat({ tostring(action), tostring(reason), tostring(deadline) }, "/"))
  action = choiceReceiptRecovery.Evaluate(baseReceipt, {
    itemReceiver = {
      sessionId = "OTHER-SESSION",
      sender = "Host-Realm",
      itemKey = "VALIDATION-ITEM",
      state = "admitted",
      activityClockAt = 99,
    },
  }, 100)
  addCheck("mismatched receiver activity cannot extend a receipt", "unconfirmed", tostring(action))
  action, reason = choiceReceiptRecovery.Evaluate(baseReceipt, {
    queueStatus = { byPriority = { ["2"] = 3 }, nextPriority = 2 },
  }, 100)
  addCheck("local priority-two pressure starts receipt grace", "begin_grace/local_live_queue",
    tostring(action) .. "/" .. tostring(reason))
  action = choiceReceiptRecovery.Evaluate(baseReceipt, {
    queueStatus = { byPriority = { ["60"] = 300 }, nextPriority = 60 },
  }, 100)
  addCheck("archive-only pressure cannot extend a critical receipt", "unconfirmed", tostring(action))
  local graceReceipt = {
    attempts = 4,
    queueGraceExpiresAt = 112,
    queuePressureReason = "live_board_burst",
  }
  action, reason, deadline = choiceReceiptRecovery.Evaluate(graceReceipt, {}, 106)
  addCheck("active receipt grace waits without a fifth send", "grace_wait/live_board_burst/112/4",
    table.concat({ tostring(action), tostring(reason), tostring(deadline), tostring(graceReceipt.attempts) }, "/"))
  action, reason = choiceReceiptRecovery.Evaluate(graceReceipt, {}, 112)
  addCheck("receipt grace has a hard terminal deadline", "unconfirmed/queue_grace_expired",
    tostring(action) .. "/" .. tostring(reason))
  addCheck("choice receipt grace remains fixed at twelve seconds", 12, choiceReceiptRecovery.graceSeconds)
  addCheck("unconfirmed choice receives one reload rearm", true,
    choiceReceiptRecovery.CanRearm({ receiptState = "unconfirmed", receiptRearmCount = 0 }, nil))
  addCheck("waiting choice interrupted by reload receives one rearm", true,
    choiceReceiptRecovery.CanRearm({ receiptState = "waiting", receiptRearmCount = 0 }, nil))
  addCheck("persisted selection tombstone blocks repeated reload rearm", false,
    choiceReceiptRecovery.CanRearm({ receiptState = "unconfirmed", receiptRearmCount = 1 }, nil))
  addCheck("persisted receipt tombstone blocks repeated board-sync rearm", false,
    choiceReceiptRecovery.CanRearm(
      { receiptState = "unconfirmed", receiptRearmCount = 0 },
      { rearmCount = 1 }
    ))
  addCheck("confirmed choice cannot be recreated after reload", false,
    choiceReceiptRecovery.CanRearm({ receiptState = "confirmed", receiptRearmCount = 0 }, nil))
  addCheck("legacy choice without receipt state is not replayed", false,
    choiceReceiptRecovery.CanRearm({ receiptRearmCount = 0 }, nil))
  return passedCount == #checks, {
    suite = "choice_receipt_queue_recovery",
    total = #checks,
    passed = passedCount,
    failed = #checks - passedCount,
    checks = checks,
  }
end

local function RecordChoicePopupState(activeSession, profileName, state, reason, itemKey)
  local sessionId = TrimText(type(activeSession) == "table" and activeSession.id or "")
  local now = (type(GetServerTime) == "function" and GetServerTime()) or (time and time() or 0)
  local record = {
    sessionId = sessionId,
    profileName = TrimText(profileName),
    state = string.lower(TrimText(state)),
    reason = TrimText(reason),
    itemKey = TrimText(itemKey),
    queueCount = #(RuntimeState.popupQueue or {}),
    inCombat = type(InCombatLockdown) == "function" and InCombatLockdown() == true or false,
    updatedAt = now,
  }
  RuntimeState.lastChoicePopupState = record
  local receiver = type(RuntimeState.lastBoardSyncReceiver) == "table" and RuntimeState.lastBoardSyncReceiver or nil
  if type(receiver) == "table"
    and sessionId ~= ""
    and sessionId == TrimText(receiver.sessionId)
    and TrimText(profileName) == TrimText(receiver.profileName)
  then
    receiver.popupState = record.state
    receiver.popupReason = record.reason
    receiver.popupQueueCount = record.queueCount
    receiver.popupOpened = record.state == "opened"
    receiver.updatedAt = now
  end
  return record
end

local function SetChoicePopupPendingRollIndicator(frame, pendingCount)
  if frame and frame.pendingRollIndicator then
    local remaining = math.max(0, math.floor(tonumber(pendingCount) or 0))
    frame.__pendingRollCount = remaining
    frame.pendingRollIndicator.__pendingRollCount = remaining
    frame.pendingRollIndicator:SetShown(remaining > 0)
  end
end

local function FormatRollRemainingText(count)
  local remaining = math.max(0, math.floor(tonumber(count) or 0))
  return tostring(remaining) .. (remaining == 1 and " roll left" or " rolls left")
end

local function SetChoicePopupItemMetaText(frame, entry)
  if not frame or not frame.itemMeta then
    return
  end

  local parts = {}
  local itemLevel = tonumber(type(entry) == "table" and entry.itemLevel) or nil
  if itemLevel and itemLevel > 0 then
    parts[#parts + 1] = "Item level " .. tostring(math.floor(itemLevel + 0.5))
  end
  local difficultyName = TrimText(type(entry) == "table" and entry.difficultyName or "")
  if difficultyName ~= "" then
    parts[#parts + 1] = difficultyName
  end
  local encounterName = TrimText(type(entry) == "table" and entry.encounterName or "")
  if encounterName ~= "" and encounterName ~= "Raid Encounter" then
    parts[#parts + 1] = encounterName
  end

  if #parts == 0 then
    frame.itemMeta:SetText("")
    frame.itemMeta:Hide()
    return
  end
  frame.itemMeta:SetText(table.concat(parts, " | "))
  frame.itemMeta:Show()
end

local function SaveChoicePopupPosition(frame)
  if not frame or not frame.GetPoint then
    return
  end

  local point, _, relativePoint, xOfs, yOfs = frame:GetPoint(1)
  UIState.choicePopupPosition = {
    point = TrimText(point) ~= "" and point or "CENTER",
    relativePoint = TrimText(relativePoint) ~= "" and relativePoint or (TrimText(point) ~= "" and point or "CENTER"),
    x = tonumber(xOfs) or 0,
    y = tonumber(yOfs) or 0,
  }
end

local function ApplyChoicePopupPosition(frame)
  if not frame or not frame.ClearAllPoints then
    return
  end

  local position = type(UIState.choicePopupPosition) == "table" and UIState.choicePopupPosition or nil
  frame:ClearAllPoints()
  if position then
    frame:SetPoint(
      TrimText(position.point) ~= "" and position.point or "CENTER",
      UIParent,
      TrimText(position.relativePoint) ~= "" and position.relativePoint or "CENTER",
      tonumber(position.x) or 0,
      tonumber(position.y) or 0
    )
    return
  end

  frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
end

local function NormalizePopupCharacterKey(value)
  local text = TrimText(value)
  text = text:gsub("%s*%-%s*", "-")
  text = text:gsub("%s+", "")
  return string.lower(text)
end

local function SplitPopupCharacterKey(value)
  local normalized = NormalizePopupCharacterKey(value)
  if normalized == "" then
    return "", ""
  end
  local name, realm = normalized:match("^([^%-]+)%-(.+)$")
  if name and name ~= "" then
    return name, realm or ""
  end
  return normalized, ""
end

ArePopupCharacterKeysSame = function(left, right)
  local leftKey = NormalizePopupCharacterKey(left)
  local rightKey = NormalizePopupCharacterKey(right)
  if leftKey == "" or rightKey == "" then
    return false
  end
  if leftKey == rightKey then
    return true
  end
  local leftName, leftRealm = SplitPopupCharacterKey(leftKey)
  local rightName, rightRealm = SplitPopupCharacterKey(rightKey)
  return leftName ~= "" and leftName == rightName and (leftRealm == "" or rightRealm == "")
end

local function GetSessionCommProfileName(activeSession, fallbackProfileName)
  return TrimText(type(activeSession) == "table" and activeSession.remoteProfileName) ~= ""
    and TrimText(activeSession.remoteProfileName)
    or TrimText(fallbackProfileName)
end

local function GetChoiceReceiptClock()
  if type(GetTime) == "function" then
    return tonumber(GetTime()) or 0
  end
  return tonumber(time and time() or 0) or 0
end

local function BuildChoiceReceiptKey(sessionId, itemKey, memberKey, selectedSeq)
  return string.lower(TrimText(sessionId))
    .. "\031" .. string.lower(TrimText(itemKey))
    .. "\031" .. string.lower(TrimText(memberKey))
    .. "\031" .. tostring(math.max(0, math.floor(tonumber(selectedSeq) or 0)))
end

local function GetPendingChoiceReceipt(activeSession, itemKey, memberKey, selection)
  if type(activeSession) ~= "table" or type(RuntimeState.pendingChoiceReceipts) ~= "table" then
    return nil
  end
  local selectedSeq = tonumber(type(selection) == "table" and selection.selectedSeq) or 0
  if selectedSeq <= 0 then
    return nil
  end
  return RuntimeState.pendingChoiceReceipts[BuildChoiceReceiptKey(activeSession.id, itemKey, memberKey, selectedSeq)]
end

local function SetChoiceReceiptRetryActive(active)
  if not choiceReceiptRetryFrame and type(CreateFrame) == "function" then
    choiceReceiptRetryFrame = CreateFrame("Frame")
    choiceReceiptRetryFrame:Hide()
    choiceReceiptRetryFrame:SetScript("OnUpdate", function(frame, elapsed)
      frame.__choiceReceiptElapsed = (tonumber(frame.__choiceReceiptElapsed) or 0) + (tonumber(elapsed) or 0)
      if frame.__choiceReceiptElapsed < 0.25 then
        return
      end
      frame.__choiceReceiptElapsed = 0
      if type(ProcessPendingChoiceReceipts) == "function" then
        ProcessPendingChoiceReceipts()
      end
    end)
  end
  if not choiceReceiptRetryFrame then
    return
  end
  choiceReceiptRetryFrame.__choiceReceiptElapsed = 0
  choiceReceiptRetryFrame:SetShown(active == true)
end

local function RemoveQueuedPopupItem(itemKey)
  local targetKey = TrimText(itemKey)
  if targetKey == "" then
    return
  end
  local queue = {}
  local removed = false
  for _, queuedKey in ipairs(RuntimeState.popupQueue or {}) do
    if not removed and TrimText(queuedKey) == targetKey then
      removed = true
    else
      queue[#queue + 1] = queuedKey
    end
  end
  RuntimeState.popupQueue = queue
  if type(RefreshHeaderPendingRollIndicator) == "function" then
    RefreshHeaderPendingRollIndicator()
  end
end

local function ResolvePopupEntrySelection(entry, memberKey)
  if type(entry) ~= "table" or type(entry.selections) ~= "table" then
    return nil
  end
  local direct = entry.selections[memberKey]
  if type(direct) == "table" then
    return direct
  end
  for selectionKey, selection in pairs(entry.selections) do
    if ArePopupCharacterKeysSame(selectionKey, memberKey) and type(selection) == "table" then
      return selection
    end
  end
  return nil
end

local function ResolvePopupSelfMemberKey(activeSession)
  local selfKey = GetSelfKey()
  local normalizedSelf = NormalizePopupCharacterKey(selfKey)
  if normalizedSelf == "" then
    return ""
  end

  if type(activeSession) == "table" and type(activeSession.members) == "table" then
    for memberKey, member in pairs(activeSession.members) do
      if ArePopupCharacterKeysSame(memberKey, selfKey)
        and string.upper(TrimText(type(member) == "table" and member.status or "JOINED")) == "JOINED"
      then
        return tostring(memberKey)
      end
    end
  end

  if type(activeSession) == "table" and ArePopupCharacterKeysSame(activeSession.host, selfKey) then
    return tostring(activeSession.host or selfKey)
  end

  return selfKey
end

local function ResolveActivePopupSessionState()
  local joined, activeSession, profileName = GetJoinedSessionState()
  if joined and type(activeSession) == "table" and TrimText(profileName) ~= "" then
    return true, activeSession, profileName
  end

  activeSession, profileName = GetActiveLootSession()
  if type(activeSession) ~= "table" or TrimText(profileName) == "" then
    return false, nil, nil
  end
  if string.upper(TrimText(activeSession.status or "ACTIVE")) ~= "ACTIVE" then
    return false, nil, nil
  end

  local selfKey = GetSelfKey()
  local normalizedSelf = NormalizePopupCharacterKey(selfKey)
  if normalizedSelf == "" then
    return false, nil, nil
  end
  if ArePopupCharacterKeysSame(activeSession.host, selfKey) then
    return true, activeSession, profileName
  end
  if activeSession.acceptedBySelf == true then
    return true, activeSession, profileName
  end

  if type(activeSession.members) == "table" then
    for memberKey, member in pairs(activeSession.members) do
      if ArePopupCharacterKeysSame(memberKey, selfKey)
        and string.upper(TrimText(type(member) == "table" and member.status or "JOINED")) == "JOINED"
      then
        return true, activeSession, profileName
      end
    end
  end
  return false, nil, nil
end

local function RaisePopupFrameAboveEditor(frame)
  if not frame then
    return
  end
  if frame.SetFrameStrata then
    frame:SetFrameStrata("TOOLTIP")
  end
  if frame.SetToplevel then
    frame:SetToplevel(true)
  end
  local editor = rawget(_G, "LIDCOUNCIL_Editor")
  local targetFrameLevel = 0
  if editor and editor.GetFrameLevel then
    targetFrameLevel = math.max(targetFrameLevel, tonumber(editor:GetFrameLevel()) or 0)
  end
  if UIState and UIState.boardFrame and UIState.boardFrame.GetFrameLevel then
    targetFrameLevel = math.max(targetFrameLevel, tonumber(UIState.boardFrame:GetFrameLevel()) or 0)
  end
  if frame.SetFrameLevel then
    frame:SetFrameLevel(targetFrameLevel + 140)
  end
  if frame.SetClampedToScreen then
    frame:SetClampedToScreen(true)
  end
  if frame.Raise then
    frame:Raise()
  end
end

local function CountNonSimulationRuntimeItems()
  local count = 0
  for _, itemKey in ipairs(RuntimeState.itemOrder or {}) do
    if type(RuntimeState.items[itemKey]) == "table" and not IsRealSimulationItemKey(itemKey) then
      count = count + 1
    end
  end
  return count
end

local function ClearRealSimulationRuntimeItems()
  local removeKeys = {}
  for _, itemKey in ipairs(RuntimeState.itemOrder or {}) do
    if IsRealSimulationItemKey(itemKey) then
      removeKeys[itemKey] = true
      RuntimeState.items[itemKey] = nil
    end
  end

  local itemOrder = {}
  for _, itemKey in ipairs(RuntimeState.itemOrder or {}) do
    if not removeKeys[itemKey] and type(RuntimeState.items[itemKey]) == "table" then
      itemOrder[#itemOrder + 1] = itemKey
    end
  end
  RuntimeState.itemOrder = itemOrder

  local popupQueue = {}
  for _, itemKey in ipairs(RuntimeState.popupQueue or {}) do
    if not removeKeys[itemKey] and type(RuntimeState.items[itemKey]) == "table" then
      popupQueue[#popupQueue + 1] = itemKey
    end
  end
  RuntimeState.popupQueue = popupQueue

  if removeKeys[RuntimeState.selectedItemKey] then
    RuntimeState.selectedItemKey = RuntimeState.itemOrder[1]
  end
  if #RuntimeState.itemOrder == 0 then
    RuntimeState.selectedMemberKey = nil
  end
end

local function ResolveChoicePopupSectionValue(option)
  local section = string.upper(TrimText(option and option.popupSection))
  if section == "NEED" or section == "GREEDPASS" then
    return section
  end

  local pointBehavior = string.upper(TrimText(option and option.pointBehavior))
  if pointBehavior == "OFFSPEC" or pointBehavior == "TRANSMOG" or pointBehavior == "DISENCHANT" or pointBehavior == "NONE" then
    return "GREEDPASS"
  end

  local hint = string.lower(TrimText(option and ((option.key ~= "" and option.key) or option.label)))
  hint = hint:gsub("[%s%-%_]+", "")
  if hint == "greed" or hint == "pass" or hint == "offspec" or hint == "transmog" or hint == "mog" or hint == "disenchant" or hint == "de" then
    return "GREEDPASS"
  end
  return "NEED"
end

local function EnsureChoicePopupButton(frame, index)
  frame.optionButtons = frame.optionButtons or {}
  if frame.optionButtons[index] then
    return frame.optionButtons[index]
  end

  local button = CreateFrame("Button", nil, frame, "BackdropTemplate")
  ApplyFrameSkin(button)
  button:SetSize(188, 28)
  if button.SetBackdropColor then
    button.__normalBackdrop = { 0.09, 0.12, 0.16, 0.96 }
    button.__hoverBackdrop = { 0.15, 0.20, 0.28, 0.98 }
    button:SetBackdropColor(button.__normalBackdrop[1], button.__normalBackdrop[2], button.__normalBackdrop[3], button.__normalBackdrop[4])
  end
  button:SetScript("OnEnter", function(self)
    if self.SetBackdropColor and self.__hoverBackdrop then
      self:SetBackdropColor(self.__hoverBackdrop[1], self.__hoverBackdrop[2], self.__hoverBackdrop[3], self.__hoverBackdrop[4])
    end
  end)
  button:SetScript("OnLeave", function(self)
    if self.SetBackdropColor and self.__normalBackdrop then
      self:SetBackdropColor(self.__normalBackdrop[1], self.__normalBackdrop[2], self.__normalBackdrop[3], self.__normalBackdrop[4])
    end
  end)
  local icon = button:CreateTexture(nil, "ARTWORK")
  icon:SetSize(18, 18)
  icon:SetPoint("LEFT", 8, 0)
  button.optionIcon = icon
  local label = button:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  label:SetPoint("LEFT", button, "LEFT", 32, 0)
  label:SetPoint("RIGHT", button, "RIGHT", -8, 0)
  label:SetJustifyH("LEFT")
  if label.SetWordWrap then
    label:SetWordWrap(false)
  end
  button.label = label

  local recommendationGlow = button:CreateTexture(nil, "BACKGROUND")
  recommendationGlow:SetTexture("Interface\\Buttons\\WHITE8x8")
  recommendationGlow:SetAllPoints(button)
  recommendationGlow:SetVertexColor(1.00, 0.82, 0.10, 0.18)
  recommendationGlow:Hide()
  button.recommendationGlow = recommendationGlow

  local recommendationText = button:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
  recommendationText:SetPoint("RIGHT", button, "RIGHT", -8, 0)
  recommendationText:SetText("Recommended")
  recommendationText:SetTextColor(1.00, 0.86, 0.18, 1)
  recommendationText:Hide()
  button.recommendationText = recommendationText

  frame.optionButtons[index] = button
  return button
end

local function CollectOrderedChoiceOptions(options)
  local orderedOptions = {}
  if type(options) ~= "table" then
    return orderedOptions
  end

  for _, option in ipairs(options) do
    if type(option) == "table" then
      orderedOptions[#orderedOptions + 1] = option
    end
  end

  if #orderedOptions > 0 then
    return orderedOptions
  end

  local keyedOptions = {}
  for key, value in pairs(options) do
    if type(value) == "table" then
      keyedOptions[#keyedOptions + 1] = {
        sortNumber = tonumber(key),
        sortText = tostring(key or ""),
        option = value,
      }
    end
  end

  table.sort(keyedOptions, function(a, b)
    local aNumber = tonumber(a.sortNumber)
    local bNumber = tonumber(b.sortNumber)
    if aNumber and bNumber and aNumber ~= bNumber then
      return aNumber < bNumber
    end
    if aNumber and not bNumber then
      return true
    end
    if bNumber and not aNumber then
      return false
    end
    return string.lower(tostring(a.sortText or "")) < string.lower(tostring(b.sortText or ""))
  end)

  for _, entry in ipairs(keyedOptions) do
    orderedOptions[#orderedOptions + 1] = entry.option
  end
  return orderedOptions
end

local function ApplyChoicePopupButtonOption(button, option, recommended)
  if not button then
    return
  end

  local resolvedIcon = ResolveOptionIconTexture(option and option.icon, option and option.key, option and option.label)

  if button.label then
    button.label:ClearAllPoints()
    button.label:SetPoint("LEFT", button, "LEFT", 32, 0)
    button.label:SetPoint("RIGHT", button, "RIGHT", recommended and -104 or -8, 0)
    button.label:SetText(tostring(option and (option.label or option.key) or "Option"))
  end
  if button.optionIcon then
    if resolvedIcon ~= nil and resolvedIcon ~= "" then
      button.optionIcon:SetTexture(resolvedIcon)
      button.optionIcon:Show()
    else
      button.optionIcon:Hide()
    end
  end

  local fontString = button.label
  if fontString then
    fontString:SetTextColor(
      tonumber(option and option.color and option.color[1]) or 1,
      tonumber(option and option.color and option.color[2]) or 1,
      tonumber(option and option.color and option.color[3]) or 1,
      1
    )
  end

  if button.recommendationGlow then
    button.recommendationGlow:SetShown(recommended and true or false)
  end
  if button.recommendationText then
    button.recommendationText:SetShown(recommended and true or false)
  end
end

local function ResolvePersonalRecommendationForPopup(profileName, entry, options)
  local api = rawget(_G, "LIDCOUNCIL_API")
  local resolver = type(api) == "table" and api.LIDCOUNCIL_ResolvePersonalRecommendation or nil
  if type(resolver) ~= "function" or type(entry) ~= "table" then
    return nil, nil, "NONE"
  end
  local difficultyID = entry.difficultyID or entry.lootDifficultyID
  local difficultyName = entry.difficultyName
  if not difficultyID and type(GetInstanceInfo) == "function" then
    local _, _, currentDifficultyID, currentDifficultyName = GetInstanceInfo()
    difficultyID = currentDifficultyID
    difficultyName = difficultyName or currentDifficultyName
  end
  return resolver(profileName, entry.itemID, entry.link, entry.name, options, {
    difficultyID = difficultyID,
    difficultyName = difficultyName,
  })
end

local function DoesOptionMatchRecommendation(option, recommendedOption)
  if type(option) ~= "table" or type(recommendedOption) ~= "table" then
    return false
  end
  if option == recommendedOption then
    return true
  end
  local optionKey = string.lower(TrimText(option.key or option.optionKey or option.label))
  local recommendedKey = string.lower(TrimText(recommendedOption.key or recommendedOption.optionKey or recommendedOption.label))
  return optionKey ~= "" and optionKey == recommendedKey
end

local function DoesOptionMatchCommittedSelection(option, selection)
  if type(option) ~= "table" or type(selection) ~= "table" then
    return false
  end
  local optionKey = string.lower(TrimText(option.key or option.optionKey or option.label))
  local selectionKey = string.lower(TrimText(selection.optionKey or selection.key or selection.label))
  return optionKey ~= "" and selectionKey ~= "" and optionKey == selectionKey
end

local function HasCommittedPopupSelection(selection)
  if type(selection) ~= "table" then
    return false
  end
  local optionKey = string.lower(TrimText(selection.optionKey or selection.key or selection.label))
  return optionKey ~= "" and optionKey ~= "pending"
end

local function GetChoicePopupItemLabel(entry, itemKey)
  local itemLink = TrimText(type(entry) == "table" and entry.link or "")
  local bracketName = itemLink:match("%[(.-)%]")
  if TrimText(bracketName) ~= "" then
    return TrimText(bracketName)
  end
  local itemName = TrimText(type(entry) == "table" and entry.name or "")
  if itemName ~= "" then
    return itemName
  end
  return TrimText(itemKey)
end

local function GetChoicePopupLayout(frame)
  local navVisible = frame and frame.__itemNavigatorVisible == true
  local popupWidth = navVisible and 624 or 460
  local navWidth = navVisible and 146 or 0
  local navGap = navVisible and 14 or 0
  local contentLeft = 16 + navWidth + navGap
  local contentWidth = popupWidth - contentLeft - 16
  return {
    navVisible = navVisible,
    popupWidth = popupWidth,
    navWidth = navWidth,
    contentLeft = contentLeft,
    contentWidth = contentWidth,
    sectionWidth = contentWidth,
  }
end

local function EnsureChoicePopupNavigatorButton(frame, index)
  frame.itemNavigatorButtons = frame.itemNavigatorButtons or {}
  if frame.itemNavigatorButtons[index] then
    return frame.itemNavigatorButtons[index]
  end

  local parent = frame.itemNavigatorContent or frame
  local button = CreateFrame("Button", nil, parent, "BackdropTemplate")
  ApplyFrameSkin(button)
  button:SetHeight(24)
  if button.SetBackdropColor then
    button:SetBackdropColor(0.08, 0.10, 0.14, 0.96)
  end
  if button.SetBackdropBorderColor then
    button:SetBackdropBorderColor(0.22, 0.24, 0.30, 0.82)
  end

  local icon = button:CreateTexture(nil, "ARTWORK")
  icon:SetSize(16, 16)
  icon:SetPoint("LEFT", 14, 0)
  button.itemIcon = icon

  local currentMarker = button:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
  currentMarker:SetPoint("LEFT", 4, 0)
  currentMarker:SetTextColor(1.00, 0.82, 0.28, 1.00)
  currentMarker:SetText(">")
  currentMarker:Hide()
  button.currentMarker = currentMarker

  local label = button:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  label:SetPoint("LEFT", icon, "RIGHT", 6, 0)
  label:SetPoint("RIGHT", button, "RIGHT", -20, 0)
  label:SetJustifyH("LEFT")
  if label.SetWordWrap then
    label:SetWordWrap(false)
  end
  button.itemLabel = label

  local stateIcon = button:CreateTexture(nil, "OVERLAY")
  stateIcon:SetSize(12, 12)
  stateIcon:SetPoint("RIGHT", -5, 0)
  button.stateIcon = stateIcon

  frame.itemNavigatorButtons[index] = button
  return button
end

local function EnsureChoicePopupNavigator(frame)
  if not frame then
    return nil
  end
  if frame.itemNavigatorFrame then
    return frame.itemNavigatorFrame
  end

  local navFrame = CreateFrame("Frame", nil, frame, "BackdropTemplate")
  ApplyFrameSkin(navFrame)
  if navFrame.SetBackdropColor then
    navFrame:SetBackdropColor(0.05, 0.06, 0.09, 0.98)
  end
  frame.itemNavigatorFrame = navFrame

  local navTitle = navFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
  navTitle:SetPoint("TOPLEFT", 10, -8)
  navTitle:SetText("Roll Queue")
  frame.itemNavigatorTitle = navTitle

  local navSummary = navFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  navSummary:SetPoint("TOPLEFT", 10, -24)
  navSummary:SetWidth(112)
  navSummary:SetJustifyH("LEFT")
  frame.itemNavigatorSummary = navSummary

  local navScroll = CreateFrame("ScrollFrame", nil, navFrame, "UIPanelScrollFrameTemplate")
  navScroll:SetPoint("TOPLEFT", 8, -40)
  navScroll:SetPoint("BOTTOMRIGHT", -26, 8)
  frame.itemNavigatorScroll = navScroll

  local navContent = CreateFrame("Frame", nil, navScroll)
  navContent:SetSize(1, 1)
  navScroll:SetScrollChild(navContent)
  frame.itemNavigatorContent = navContent

  frame.itemNavigatorButtons = {}
  return navFrame
end

local function ApplyChoicePopupBaseLayout(frame)
  if not frame then
    return
  end

  local layout = GetChoicePopupLayout(frame)
  local pendingIndicatorReservedWidth = frame.__largePendingIndicator and 120 or 0
  if frame.title then
    frame.title:ClearAllPoints()
    frame.title:SetPoint("TOPLEFT", layout.contentLeft - 2, -14)
  end
  if frame.pendingRollIndicator then
    frame.pendingRollIndicator:ClearAllPoints()
    frame.pendingRollIndicator:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -18, -14)
  end
  if frame.queueText and frame.pendingRollIndicator then
    frame.queueText:ClearAllPoints()
    frame.queueText:SetPoint("BOTTOMLEFT", frame.pendingRollIndicator, "BOTTOMLEFT", 4, 14)
    frame.queueText:SetPoint("BOTTOMRIGHT", frame.pendingRollIndicator, "BOTTOMRIGHT", -4, 14)
  end
  if frame.modeText and frame.title then
    frame.modeText:ClearAllPoints()
    frame.modeText:SetPoint("LEFT", frame.title, "RIGHT", 8, 0)
  end
  if frame.icon then
    frame.icon:ClearAllPoints()
    frame.icon:SetPoint("TOPLEFT", layout.contentLeft, -44)
  end
  if frame.itemTooltipButton then
    frame.itemTooltipButton:ClearAllPoints()
    frame.itemTooltipButton:SetPoint("TOPLEFT", layout.contentLeft, -44)
  end
  if frame.itemText then
    local itemTextWidth = math.max(120, layout.contentWidth - 46 - pendingIndicatorReservedWidth)
    frame.itemText:ClearAllPoints()
    frame.itemText:SetPoint("TOPLEFT", frame.icon, "TOPRIGHT", 10, 0)
    frame.itemText:SetWidth(itemTextWidth)
  end
  if frame.itemMeta and frame.itemText then
    frame.itemMeta:ClearAllPoints()
    frame.itemMeta:SetPoint("TOPLEFT", frame.itemText, "BOTTOMLEFT", 0, -4)
    frame.itemMeta:SetWidth(frame.itemText:GetWidth() or math.max(120, layout.contentWidth - 46))
  end
  if frame.itemTextTooltipButton then
    local itemTooltipWidth = math.max(120, layout.contentWidth - 42 - pendingIndicatorReservedWidth)
    frame.itemTextTooltipButton:ClearAllPoints()
    frame.itemTextTooltipButton:SetPoint("TOPLEFT", frame.icon, "TOPRIGHT", 8, 2)
    frame.itemTextTooltipButton:SetSize(itemTooltipWidth, 36)
  end
  if frame.hint then
    frame.hint:ClearAllPoints()
    frame.hint:SetPoint("TOPLEFT", layout.contentLeft, -92)
    frame.hint:SetWidth(math.max(120, layout.contentWidth - pendingIndicatorReservedWidth))
  end
  if frame.commentToggle then
    frame.commentToggle:ClearAllPoints()
    frame.commentToggle:SetPoint("TOPLEFT", layout.contentLeft, -112)
  end
  if frame.commentLabel then
    frame.commentLabel:ClearAllPoints()
    frame.commentLabel:SetPoint("TOPLEFT", layout.contentLeft, -136)
  end
  if frame.commentEdit then
    frame.commentEdit:ClearAllPoints()
    frame.commentEdit:SetPoint("TOPLEFT", layout.contentLeft, -156)
    frame.commentEdit:SetWidth(layout.contentWidth)
  end
  if frame.closeBtn then
    frame.closeBtn:ClearAllPoints()
    frame.closeBtn:SetPoint("BOTTOMRIGHT", -16, 12)
  end

  local navFrame = EnsureChoicePopupNavigator(frame)
  if navFrame then
    if layout.navVisible then
      navFrame:ClearAllPoints()
      navFrame:SetPoint("TOPLEFT", 16, -44)
      navFrame:SetSize(layout.navWidth, math.max(160, (frame:GetHeight() or 320) - 56))
      navFrame:Show()
    else
      navFrame:Hide()
    end
  end
end

local function BuildChoicePopupNavigatorEntries(activeSession, selfKey)
  local entries = {}
  local pendingLookup = {}
  for _, queuedKey in ipairs(RuntimeState.popupQueue or {}) do
    pendingLookup[TrimText(queuedKey)] = true
  end

  for _, itemKey in ipairs(RuntimeState.itemOrder or {}) do
    local resolvedKey = TrimText(itemKey)
    local entry = RuntimeState.items[resolvedKey]
    if type(entry) == "table" then
      local selection = ResolvePopupEntrySelection(entry, selfKey)
      if pendingLookup[resolvedKey] or type(selection) == "table" then
        local receipt = GetPendingChoiceReceipt(activeSession, resolvedKey, selfKey, selection)
        entries[#entries + 1] = {
          itemKey = resolvedKey,
          entry = entry,
          selection = selection,
          pending = pendingLookup[resolvedKey] and not HasCommittedPopupSelection(selection),
          committed = HasCommittedPopupSelection(selection),
          awaitingReceipt = type(receipt) == "table",
          receiptFailed = type(receipt) == "table" and receipt.state == "unconfirmed",
          label = GetChoicePopupItemLabel(entry, resolvedKey),
        }
      end
    end
  end

  return entries
end

local function FindChoicePopupNavigatorEntry(entries, itemKey)
  local targetKey = TrimText(itemKey)
  if targetKey == "" then
    return nil
  end
  for _, entry in ipairs(entries or {}) do
    if TrimText(type(entry) == "table" and entry.itemKey or "") == targetKey then
      return entry
    end
  end
  return nil
end

local function RefreshChoicePopupNavigator(frame, activeSession, profileName, currentItemKey)
  local navFrame = EnsureChoicePopupNavigator(frame)
  if not navFrame or frame.__itemNavigatorVisible ~= true then
    if navFrame then
      navFrame:Hide()
    end
    return currentItemKey, {}
  end

  local selfKey = ResolvePopupSelfMemberKey(activeSession)
  local entries = BuildChoicePopupNavigatorEntries(activeSession, selfKey)
  if #entries == 0 then
    navFrame:Hide()
    return currentItemKey, entries
  end

  navFrame:Show()
  if frame.itemNavigatorSummary then
    local completedCount = 0
    local confirmingCount = 0
    for _, entry in ipairs(entries) do
      if entry.committed and not entry.awaitingReceipt then
        completedCount = completedCount + 1
      end
      if entry.awaitingReceipt then
        confirmingCount = confirmingCount + 1
      end
    end
    local summary = string.format("%d / %d complete", completedCount, #entries)
    if confirmingCount > 0 then
      summary = summary .. string.format(" | %d confirming", confirmingCount)
    end
    frame.itemNavigatorSummary:SetText(summary)
    frame.itemNavigatorSummary:Show()
  end

  local resolvedCurrentKey = TrimText(currentItemKey)
  if resolvedCurrentKey == "" or not FindChoicePopupNavigatorEntry(entries, resolvedCurrentKey) then
    resolvedCurrentKey = TrimText(type(entries[1]) == "table" and entries[1].itemKey or "")
  end

  local rowHeight = 28
  local contentWidth = math.max(88, (navFrame:GetWidth() or 146) - 34)
  local content = frame.itemNavigatorContent
  if content then
    content:SetWidth(contentWidth)
    content:SetHeight(math.max(1, #entries * rowHeight))
  end

  for index, entry in ipairs(entries) do
    local button = EnsureChoicePopupNavigatorButton(frame, index)
    local targetItemKey = entry.itemKey
    button:ClearAllPoints()
    button:SetPoint("TOPLEFT", frame.itemNavigatorContent, "TOPLEFT", 0, -((index - 1) * rowHeight))
    button:SetWidth(contentWidth)
    button:Show()
    if button.itemIcon then
      button.itemIcon:SetTexture(type(entry.entry) == "table" and entry.entry.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
    end
    if button.itemLabel then
      button.itemLabel:SetText(tostring(entry.label or entry.itemKey))
      local color = type(entry.selection) == "table" and type(entry.selection.color) == "table" and entry.selection.color or nil
      if entry.awaitingReceipt then
        button.itemLabel:SetTextColor(1.00, entry.receiptFailed and 0.42 or 0.74, 0.26, 1)
      elseif entry.committed and color then
        button.itemLabel:SetTextColor(
          tonumber(color[1]) or 0.82,
          tonumber(color[2]) or 0.82,
          tonumber(color[3]) or 0.86,
          1
        )
      elseif entry.pending then
        button.itemLabel:SetTextColor(0.92, 0.92, 0.96, 1)
      else
        button.itemLabel:SetTextColor(0.82, 0.82, 0.86, 1)
      end
    end
    local isCurrent = TrimText(entry.itemKey) == resolvedCurrentKey
    if button.currentMarker then
      button.currentMarker:SetShown(isCurrent)
    end
    if button.stateIcon then
      button.stateIcon:SetTexture(entry.awaitingReceipt and "Interface\\RaidFrame\\ReadyCheck-Waiting"
        or (entry.committed and "Interface\\RaidFrame\\ReadyCheck-Ready" or "Interface\\RaidFrame\\ReadyCheck-Waiting"))
      button.stateIcon:SetVertexColor(1, 1, 1, 1)
      button.stateIcon:Show()
    end
    if button.SetBackdropColor then
      if isCurrent then
        button:SetBackdropColor(0.14, 0.20, 0.30, 0.98)
      elseif entry.awaitingReceipt then
        button:SetBackdropColor(entry.receiptFailed and 0.17 or 0.15, entry.receiptFailed and 0.07 or 0.10, 0.04, 0.96)
      elseif entry.committed then
        button:SetBackdropColor(0.08, 0.10, 0.14, 0.96)
      else
        button:SetBackdropColor(0.06, 0.08, 0.12, 0.96)
      end
    end
    if button.SetBackdropBorderColor then
      if isCurrent then
        button:SetBackdropBorderColor(1.00, 0.82, 0.32, 0.94)
      elseif entry.awaitingReceipt then
        button:SetBackdropBorderColor(entry.receiptFailed and 0.88 or 1.00, entry.receiptFailed and 0.28 or 0.72, 0.18, 0.90)
      elseif entry.committed then
        button:SetBackdropBorderColor(0.34, 0.70, 0.42, 0.82)
      else
        button:SetBackdropBorderColor(0.22, 0.24, 0.30, 0.82)
      end
    end

    button:SetScript("OnClick", function()
      RuntimeState.selectedItemKey = targetItemKey
      ShowCurrentChoicePopup(activeSession, profileName, targetItemKey)
    end)
  end

  for index = #entries + 1, #(frame.itemNavigatorButtons or {}) do
    frame.itemNavigatorButtons[index]:Hide()
  end

  return resolvedCurrentKey, entries
end

local function LayoutChoicePopupButtons(frame, options, showCloseButton)
  local groupedOptions = {
    NEED = {},
    GREEDPASS = {},
  }
  local orderedOptions = {}
  for _, option in ipairs(CollectOrderedChoiceOptions(options)) do
    local section = ResolveChoicePopupSectionValue(option)
    groupedOptions[section][#groupedOptions[section] + 1] = option
  end
  for _, option in ipairs(groupedOptions.NEED) do
    orderedOptions[#orderedOptions + 1] = option
  end
  for _, option in ipairs(groupedOptions.GREEDPASS) do
    orderedOptions[#orderedOptions + 1] = option
  end

  local layout = GetChoicePopupLayout(frame)
  local popupWidth = layout.popupWidth
  local sectionWidth = layout.sectionWidth
  local needRows = math.max(1, #groupedOptions.NEED)
  local greedRows = math.max(1, #groupedOptions.GREEDPASS)
  local needSectionHeight = 44 + (needRows * 34)
  local greedSectionHeight = 44 + (greedRows * 34)
  local commentExpanded = frame.__commentExpanded == true
  local popupHeight = math.max(344, (commentExpanded and 216 or 162) + needSectionHeight + greedSectionHeight + (showCloseButton and 56 or 24))
  frame:SetWidth(popupWidth)
  frame:SetHeight(popupHeight)
  ApplyChoicePopupBaseLayout(frame)

  local function layoutSection(sectionFrame, titleText, bucket, startIndex, topOffset)
    if not sectionFrame then
      return startIndex, topOffset
    end
    sectionFrame:ClearAllPoints()
    local bucketRows = math.max(1, #bucket)
    local currentSectionHeight = 44 + (bucketRows * 34)
    sectionFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", layout.contentLeft, topOffset)
    sectionFrame:SetSize(sectionWidth, currentSectionHeight)
    if sectionFrame.title then
      sectionFrame.title:SetText(titleText)
    end
    if sectionFrame.emptyText then
      if #bucket == 0 then
        sectionFrame.emptyText:Show()
      else
        sectionFrame.emptyText:Hide()
      end
    end

    local nextIndex = startIndex
    for rowIndex, option in ipairs(bucket) do
      local button = EnsureChoicePopupButton(frame, nextIndex)
      button:SetParent(frame)
      if button.SetFrameStrata then
        button:SetFrameStrata("TOOLTIP")
      end
      if button.SetFrameLevel then
        button:SetFrameLevel((frame:GetFrameLevel() or 1) + 6)
      end
      button:ClearAllPoints()
      button:SetPoint("TOPLEFT", frame, "TOPLEFT", layout.contentLeft + 12, topOffset - 28 - ((rowIndex - 1) * 34))
      button:SetWidth(sectionWidth - 24)
      button:Show()
      nextIndex = nextIndex + 1
    end
    return nextIndex, topOffset - currentSectionHeight - 10
  end

  local nextIndex = 1
  local topOffset = commentExpanded and -200 or -146
  if frame.needSectionFrame then
    frame.needSectionFrame:Show()
    nextIndex, topOffset = layoutSection(frame.needSectionFrame, "Need", groupedOptions.NEED, nextIndex, topOffset)
  end
  if frame.greedPassSectionFrame then
    frame.greedPassSectionFrame:Show()
    nextIndex, topOffset = layoutSection(frame.greedPassSectionFrame, "Greed / Pass", groupedOptions.GREEDPASS, nextIndex, topOffset)
  end

  for i = nextIndex, #(frame.optionButtons or {}) do
    frame.optionButtons[i]:Hide()
  end
  frame.__layoutOptions = orderedOptions
  return orderedOptions
end

UpdateChoicePopupCommentPresentation = function(frame, selectionLocked)
  if not frame then
    return
  end
  local commentText = frame.commentEdit and frame.commentEdit.GetText and frame.commentEdit:GetText() or ""
  local hasComment = TrimText(commentText) ~= ""
  if selectionLocked and hasComment then
    frame.__commentExpanded = true
  end
  local expanded = frame.__commentExpanded == true
  local previousExpanded = frame.__commentEffectiveExpanded
  frame.__commentEffectiveExpanded = expanded

  if frame.commentToggle then
    if selectionLocked then
      frame.commentToggle:Hide()
    else
      if frame.commentToggleLabel then
        frame.commentToggleLabel:SetText(expanded and "Hide comment" or "+ Add comment")
      end
      frame.commentToggle:Show()
    end
  end
  if frame.commentLabel then
    frame.commentLabel:SetText(selectionLocked and "Comment sent to host:" or "Comment for host - included with your roll choice.")
    frame.commentLabel:SetShown(expanded)
  end
  if frame.commentEdit then
    frame.commentEdit:SetShown(expanded)
    if selectionLocked then
      if frame.commentEdit.Disable then
        frame.commentEdit:Disable()
      end
    elseif frame.commentEdit.Enable then
      frame.commentEdit:Enable()
    end
  end

  if previousExpanded ~= nil and previousExpanded ~= expanded and type(frame.__layoutOptions) == "table" then
    LayoutChoicePopupButtons(frame, frame.__layoutOptions, frame.closeBtn and frame.closeBtn:IsShown())
  end
end

local function CreateChoicePopup()
  if UIState.choicePopup then
    return UIState.choicePopup
  end

  local function HideChoicePopupItemTooltip()
    if type(GameTooltip) == "table" then
      GameTooltip:Hide()
    end
  end

  local function ShowChoicePopupItemTooltip(self)
    local frame = self and self.__choicePopup
    if not frame or type(GameTooltip) ~= "table" then
      return
    end

    local itemLink = TrimText(frame.__itemTooltipLink)
    local itemID = ExtractItemID(frame.__itemTooltipItemID or itemLink)
    local itemName = TrimText(frame.__itemTooltipName)
    local authoritativeItemLevel = tonumber(frame.__itemTooltipItemLevel)
    if itemLink == "" and not itemID and itemName == "" then
      return
    end

    if type(RenderAuthoritativeItemTooltip) == "function" then
      RenderAuthoritativeItemTooltip(GameTooltip, frame, "ANCHOR_NONE", {
        link = itemLink,
        itemID = itemID,
        name = itemName,
        itemLevel = authoritativeItemLevel,
      })
    else
      GameTooltip:SetOwner(frame, "ANCHOR_NONE")
      GameTooltip:SetText(itemName ~= "" and itemName or ("Item #" .. tostring(itemID or "?")), 1, 0.82, 0.28, 1, true)
    end
    GameTooltip:ClearAllPoints()
    GameTooltip:SetPoint("TOPLEFT", frame, "TOPRIGHT", 12, -10)
    GameTooltip:Show()
  end

  local function SetChoicePopupItemTooltipData(frame, itemLink, itemID, itemName, itemLevel)
    if not frame then
      return
    end

    frame.__itemTooltipLink = TrimText(itemLink)
    frame.__itemTooltipItemID = ExtractItemID(itemID or itemLink)
    frame.__itemTooltipName = TrimText(itemName)
    frame.__itemTooltipItemLevel = tonumber(itemLevel)

    local hasTooltip = frame.__itemTooltipLink ~= "" or frame.__itemTooltipItemID ~= nil or frame.__itemTooltipName ~= ""
    if frame.itemTooltipButton and frame.itemTooltipButton.EnableMouse then
      frame.itemTooltipButton:EnableMouse(hasTooltip)
    end
    if frame.itemTextTooltipButton and frame.itemTextTooltipButton.EnableMouse then
      frame.itemTextTooltipButton:EnableMouse(hasTooltip)
    end
    if not hasTooltip then
      HideChoicePopupItemTooltip()
    end
  end

  local frame = CreateFrame("Frame", "LIDCOUNCIL_LootChoicePopup", UIParent, "BackdropTemplate")
  frame:SetSize(460, 320)
  frame:SetPoint("CENTER", 0, -140)
  frame:SetFrameStrata("TOOLTIP")
  frame:SetMovable(true)
  frame:EnableMouse(true)
  frame:SetToplevel(true)
  if frame.SetClampedToScreen then
    frame:SetClampedToScreen(true)
  end
  frame:RegisterForDrag("LeftButton")
  frame:SetScript("OnDragStart", frame.StartMoving)
  frame:SetScript("OnDragStop", function(self)
    self:StopMovingOrSizing()
    SaveChoicePopupPosition(self)
  end)
  ApplyFrameSkin(frame)
  frame:HookScript("OnShow", function(self)
    RaisePopupFrameAboveEditor(self)
  end)
  frame:Hide()

  local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
  title:SetPoint("TOPLEFT", 14, -14)
  title:SetText("LidCouncil Roll Choice")
  frame.title = title

  local modeText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  modeText:SetPoint("LEFT", title, "RIGHT", 8, 0)
  modeText:SetTextColor(0.72, 0.76, 0.82, 1.00)
  modeText:SetText("Simulation")
  modeText:Hide()
  frame.modeText = modeText

  local pendingRollIndicator = CreateFrame("Button", nil, frame, "BackdropTemplate")
  pendingRollIndicator:SetSize(112, 108)
  pendingRollIndicator:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -18, -14)
  frame.__largePendingIndicator = true
  pendingRollIndicator:EnableMouse(true)
  pendingRollIndicator:SetScript("OnEnter", function(self)
    if type(GameTooltip) == "table" then
      local popup = self:GetParent()
      GameTooltip:SetOwner(popup or self, "ANCHOR_NONE")
      local pendingCount = math.max(0, math.floor(tonumber(self.__pendingRollCount) or 0))
      GameTooltip:SetText("Pending loot choices", 1.00, 0.72, 0.16, 1.00)
      GameTooltip:AddLine(
        tostring(pendingCount) .. (pendingCount == 1 and " unanswered choice remains." or " unanswered choices remain."),
        0.92,
        0.92,
        0.96,
        true
      )
      if popup then
        GameTooltip:ClearAllPoints()
        GameTooltip:SetPoint("TOPLEFT", popup, "TOPRIGHT", 12, -10)
      end
      GameTooltip:Show()
    end
  end)
  pendingRollIndicator:SetScript("OnLeave", function()
    if type(GameTooltip) == "table" then
      GameTooltip:Hide()
    end
  end)
  local pendingRollTexture = pendingRollIndicator:CreateTexture(nil, "OVERLAY")
  pendingRollTexture:SetSize(76, 76)
  pendingRollTexture:SetPoint("TOP", pendingRollIndicator, "TOP", 0, -3)
  pendingRollTexture:SetTexture("Interface\\AddOns\\LidCouncil\\Media\\LidCouncil\\Logos\\LidCouncil_Dice_Amber.tga")
  pendingRollIndicator.texture = pendingRollTexture

  local queueText = pendingRollIndicator:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  queueText:SetPoint("BOTTOMLEFT", pendingRollIndicator, "BOTTOMLEFT", 4, 14)
  queueText:SetPoint("BOTTOMRIGHT", pendingRollIndicator, "BOTTOMRIGHT", -4, 14)
  queueText:SetJustifyH("CENTER")
  queueText:SetTextColor(1.00, 0.88, 0.44, 1.00)
  queueText:SetShadowColor(0, 0, 0, 1)
  queueText:SetShadowOffset(1, -1)
  queueText:SetText("")
  frame.queueText = queueText
  pendingRollIndicator:Hide()
  frame.pendingRollIndicator = pendingRollIndicator

  local icon = frame:CreateTexture(nil, "ARTWORK")
  icon:SetSize(34, 34)
  icon:SetPoint("TOPLEFT", 16, -44)
  frame.icon = icon

  local itemTooltipButton = CreateFrame("Button", nil, frame)
  itemTooltipButton:SetPoint("TOPLEFT", 16, -44)
  itemTooltipButton:SetSize(34, 34)
  itemTooltipButton.__choicePopup = frame
  itemTooltipButton:EnableMouse(false)
  itemTooltipButton:SetScript("OnEnter", ShowChoicePopupItemTooltip)
  itemTooltipButton:SetScript("OnLeave", HideChoicePopupItemTooltip)
  frame.itemTooltipButton = itemTooltipButton

  local itemText = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  itemText:SetPoint("TOPLEFT", icon, "TOPRIGHT", 10, 0)
  itemText:SetWidth(364)
  itemText:SetJustifyH("LEFT")
  frame.itemText = itemText

  local itemMeta = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  itemMeta:SetPoint("TOPLEFT", itemText, "BOTTOMLEFT", 0, -4)
  itemMeta:SetWidth(364)
  itemMeta:SetJustifyH("LEFT")
  itemMeta:SetTextColor(0.70, 0.74, 0.82, 1.00)
  itemMeta:Hide()
  frame.itemMeta = itemMeta

  local itemTextTooltipButton = CreateFrame("Button", nil, frame)
  itemTextTooltipButton:SetPoint("TOPLEFT", icon, "TOPRIGHT", 8, 2)
  itemTextTooltipButton:SetSize(368, 36)
  itemTextTooltipButton.__choicePopup = frame
  itemTextTooltipButton:EnableMouse(false)
  itemTextTooltipButton:SetScript("OnEnter", ShowChoicePopupItemTooltip)
  itemTextTooltipButton:SetScript("OnLeave", HideChoicePopupItemTooltip)
  frame.itemTextTooltipButton = itemTextTooltipButton

  local hint = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  hint:SetPoint("TOPLEFT", 16, -92)
  hint:SetWidth(420)
  hint:SetJustifyH("LEFT")
  hint:SetText("Choose your LidCouncil preference for this item. Blizzard group loot is still active during this preview phase.")
  frame.hint = hint

  local commentLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  commentLabel:SetPoint("TOPLEFT", 16, -122)
  commentLabel:SetText("Comment for the host (optional):")
  frame.commentLabel = commentLabel

  local commentToggle = CreateFrame("Button", nil, frame, "BackdropTemplate")
  ApplyFrameSkin(commentToggle)
  commentToggle:SetSize(128, 20)
  commentToggle:SetPoint("TOPLEFT", 16, -112)
  commentToggle.__normalBackdrop = { 0.07, 0.10, 0.16, 0.82 }
  commentToggle.__hoverBackdrop = { 0.12, 0.18, 0.28, 0.94 }
  if commentToggle.SetBackdropColor then
    commentToggle:SetBackdropColor(
      commentToggle.__normalBackdrop[1],
      commentToggle.__normalBackdrop[2],
      commentToggle.__normalBackdrop[3],
      commentToggle.__normalBackdrop[4]
    )
  end
  if commentToggle.SetBackdropBorderColor then
    commentToggle:SetBackdropBorderColor(0.32, 0.52, 0.82, 0.78)
  end
  local commentToggleLabel = commentToggle:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  commentToggleLabel:SetAllPoints(commentToggle)
  commentToggleLabel:SetText("+ Add comment")
  commentToggleLabel:SetTextColor(0.72, 0.84, 1.00, 1.00)
  commentToggle.label = commentToggleLabel
  frame.commentToggleLabel = commentToggleLabel
  commentToggle:SetScript("OnEnter", function(self)
    if self.SetBackdropColor and self.__hoverBackdrop then
      self:SetBackdropColor(self.__hoverBackdrop[1], self.__hoverBackdrop[2], self.__hoverBackdrop[3], self.__hoverBackdrop[4])
    end
    if self.label then
      self.label:SetTextColor(0.92, 0.96, 1.00, 1.00)
    end
  end)
  commentToggle:SetScript("OnLeave", function(self)
    if self.SetBackdropColor and self.__normalBackdrop then
      self:SetBackdropColor(self.__normalBackdrop[1], self.__normalBackdrop[2], self.__normalBackdrop[3], self.__normalBackdrop[4])
    end
    if self.label then
      self.label:SetTextColor(0.72, 0.84, 1.00, 1.00)
    end
  end)
  commentToggle:SetScript("OnClick", function()
    frame.__commentExpanded = frame.__commentExpanded ~= true
    UpdateChoicePopupCommentPresentation(frame, false)
  end)
  frame.commentToggle = commentToggle

  local commentEdit = CreateFrame("EditBox", nil, frame, "InputBoxTemplate")
  commentEdit:SetSize(420, 22)
  commentEdit:SetPoint("TOPLEFT", 16, -142)
  commentEdit:SetAutoFocus(false)
  commentEdit:SetTextInsets(6, 6, 0, 0)
  commentEdit:SetFontObject("GameFontHighlightSmall")
  if commentEdit.SetMaxLetters then
    commentEdit:SetMaxLetters(MAX_SELECTION_NOTE_LENGTH)
  end
  commentEdit:SetScript("OnEscapePressed", function(self)
    self:ClearFocus()
  end)
  commentEdit:SetScript("OnEnterPressed", function(self)
    self:ClearFocus()
  end)
  frame.commentEdit = commentEdit
  frame.__commentExpanded = false
  frame.__commentEffectiveExpanded = false

  local needSectionFrame = CreateFrame("Frame", nil, frame, "BackdropTemplate")
  ApplyFrameSkin(needSectionFrame)
  if needSectionFrame.SetFrameLevel then
    -- The headings need to stay above the popup artwork while the option
    -- buttons remain above the section background.
    needSectionFrame:SetFrameLevel((frame:GetFrameLevel() or 1) + 2)
  end
  if needSectionFrame.SetBackdropColor then
    needSectionFrame:SetBackdropColor(0.06, 0.08, 0.12, 0.98)
  end
  local needSectionTitle = needSectionFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  needSectionTitle:SetPoint("TOPLEFT", 12, -10)
  needSectionTitle:SetTextColor(1.00, 0.82, 0.32, 1.00)
  needSectionTitle:SetShadowColor(0, 0, 0, 1)
  needSectionTitle:SetShadowOffset(1, -1)
  needSectionTitle:SetText("Need")
  needSectionFrame.title = needSectionTitle
  local needEmptyText = needSectionFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  needEmptyText:SetPoint("CENTER", 0, 0)
  needEmptyText:SetText("No options")
  needSectionFrame.emptyText = needEmptyText
  frame.needSectionFrame = needSectionFrame

  local greedPassSectionFrame = CreateFrame("Frame", nil, frame, "BackdropTemplate")
  ApplyFrameSkin(greedPassSectionFrame)
  if greedPassSectionFrame.SetFrameLevel then
    greedPassSectionFrame:SetFrameLevel((frame:GetFrameLevel() or 1) + 2)
  end
  if greedPassSectionFrame.SetBackdropColor then
    greedPassSectionFrame:SetBackdropColor(0.06, 0.08, 0.12, 0.98)
  end
  local greedPassSectionTitle = greedPassSectionFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  greedPassSectionTitle:SetPoint("TOPLEFT", 12, -10)
  greedPassSectionTitle:SetTextColor(0.78, 0.88, 1.00, 1.00)
  greedPassSectionTitle:SetShadowColor(0, 0, 0, 1)
  greedPassSectionTitle:SetShadowOffset(1, -1)
  greedPassSectionTitle:SetText("Greed / Pass")
  greedPassSectionFrame.title = greedPassSectionTitle
  local greedPassEmptyText = greedPassSectionFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  greedPassEmptyText:SetPoint("CENTER", 0, 0)
  greedPassEmptyText:SetText("No options")
  greedPassSectionFrame.emptyText = greedPassEmptyText
  frame.greedPassSectionFrame = greedPassSectionFrame

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

  frame.optionButtons = {}
  frame.SetItemTooltipData = SetChoicePopupItemTooltipData
  frame:HookScript("OnHide", HideChoicePopupItemTooltip)

  UIState.choicePopup = frame
  return frame
end

local function SetChoicePopupComment(frame, value)
  if not frame or not frame.commentEdit or not frame.commentEdit.SetText then
    return
  end
  frame.commentEdit:SetText(NormalizeSelectionNote(value))
  if frame.commentEdit.ClearFocus then
    frame.commentEdit:ClearFocus()
  end
end

local function SendPendingChoiceReceipt(receipt)
  if type(receipt) ~= "table" then
    return false, "missing_receipt"
  end
  local now = GetChoiceReceiptClock()
  local sent = false
  local reason = "send_unavailable"
  local sendMetadata
  if type(SendChoicePacket) == "function" then
    sent, reason, sendMetadata = SendChoicePacket(
      "CHOICE",
      "WHISPER",
      receipt.host,
      receipt.sessionId,
      receipt.profileName,
      receipt.itemKey,
      receipt.memberKey,
      receipt.optionKey,
      receipt.optionLabel,
      receipt.note or "",
      receipt.rollValue or "",
      receipt.selectedAt,
      receipt.selectedSeq
    )
  end
  receipt.attempts = math.max(0, tonumber(receipt.attempts) or 0) + 1
  receipt.lastAttemptAt = now
  receipt.lastError = sent and "" or TrimText(reason)
  if type(sendMetadata) == "table" then
    receipt.sentNote = tostring(sendMetadata.sentNote or receipt.note or "")
    receipt.noteTruncated = sendMetadata.noteTruncated == true
    receipt.labelOmitted = sendMetadata.labelOmitted == true
    if receipt.noteTruncated and not receipt.compactionNotified and type(Print) == "function" then
      Print("Roll choice sent, but its comment was shortened to fit the safe addon-message limit.")
      receipt.compactionNotified = true
    end
  end
  receipt.state = "waiting"
  local intervalIndex = math.min(#choiceReceiptRecovery.retryIntervals, receipt.attempts)
  receipt.nextRetryAt = now + (tonumber(choiceReceiptRecovery.retryIntervals[intervalIndex]) or 8)
  return sent == true, reason
end

local function UpdateLocalChoiceReceiptState(receipt, state)
  if type(receipt) ~= "table" then
    return false
  end
  local entry = type(RuntimeState.items) == "table" and RuntimeState.items[TrimText(receipt.itemKey)] or nil
  local selection = ResolvePopupEntrySelection(entry, receipt.memberKey)
  if type(selection) ~= "table"
    or math.floor(tonumber(selection.selectedSeq) or 0) ~= math.floor(tonumber(receipt.selectedSeq) or 0)
  then
    return false
  end
  selection.receiptState = TrimText(state)
  selection.receiptRearmCount = math.max(
    math.max(0, math.floor(tonumber(selection.receiptRearmCount) or 0)),
    math.max(0, math.floor(tonumber(receipt.rearmCount) or 0))
  )
  return true
end

ProcessPendingChoiceReceipts = function()
  local receipts = type(RuntimeState.pendingChoiceReceipts) == "table" and RuntimeState.pendingChoiceReceipts or nil
  if not receipts or not next(receipts) then
    SetChoiceReceiptRetryActive(false)
    return
  end

  local joined, activeSession = ResolveActivePopupSessionState()
  if not joined or type(activeSession) ~= "table" or IsSelfHost(activeSession) then
    RuntimeState.pendingChoiceReceipts = {}
    SetChoiceReceiptRetryActive(false)
    return
  end

  local now = GetChoiceReceiptClock()
  local hasWaitingReceipt = false
  local recoveryContext
  for _, receipt in pairs(receipts) do
    if type(receipt) == "table" then
      local sessionMatches = TrimText(receipt.sessionId) == TrimText(activeSession.id)
        and ArePopupCharacterKeysSame(receipt.host, activeSession.host)
      if not sessionMatches then
        receipt.state = "superseded"
        receipts[BuildChoiceReceiptKey(receipt.sessionId, receipt.itemKey, receipt.memberKey, receipt.selectedSeq)] = nil
      elseif (receipt.state == "waiting" or receipt.state == "queue_grace")
        and now >= (tonumber(receipt.nextRetryAt) or 0)
      then
        if (tonumber(receipt.attempts) or 0) >= choiceReceiptRecovery.maxAttempts then
          if not receipt.queueGraceExpiresAt then
            recoveryContext = recoveryContext or choiceReceiptRecovery.GetContext()
          end
          local recoveryAction, pressureReason, graceExpiresAt = choiceReceiptRecovery.Evaluate(
            receipt,
            recoveryContext,
            now
          )
          if recoveryAction == "begin_grace" or recoveryAction == "grace_wait" then
            receipt.state = "queue_grace"
            receipt.queueGraceStartedAt = tonumber(receipt.queueGraceStartedAt) or now
            receipt.queueGraceExpiresAt = math.max(0, tonumber(graceExpiresAt) or now)
            receipt.queuePressureReason = TrimText(pressureReason)
            receipt.nextRetryAt = receipt.queueGraceExpiresAt
            RuntimeState.lastChoiceReceiptRecovery = {
              state = "queue_grace",
              reason = receipt.queuePressureReason,
              sessionId = TrimText(receipt.sessionId),
              itemKey = TrimText(receipt.itemKey),
              host = TrimText(receipt.host),
              attempts = math.max(0, tonumber(receipt.attempts) or 0),
              graceStartedAt = receipt.queueGraceStartedAt,
              graceExpiresAt = receipt.queueGraceExpiresAt,
              updatedAt = now,
            }
          else
            receipt.state = "unconfirmed"
            if UpdateLocalChoiceReceiptState(receipt, "unconfirmed") and type(PersistRuntimeBoardHistory) == "function" then
              PersistRuntimeBoardHistory({
                itemKey = TrimText(receipt.itemKey),
                selectionMemberKey = TrimText(receipt.memberKey),
              })
            end
            RuntimeState.lastChoiceReceiptRecovery = {
              state = "unconfirmed",
              reason = TrimText(pressureReason),
              sessionId = TrimText(receipt.sessionId),
              itemKey = TrimText(receipt.itemKey),
              host = TrimText(receipt.host),
              attempts = math.max(0, tonumber(receipt.attempts) or 0),
              graceStartedAt = math.max(0, tonumber(receipt.queueGraceStartedAt) or 0),
              graceExpiresAt = math.max(0, tonumber(receipt.queueGraceExpiresAt) or 0),
              updatedAt = now,
            }
          end
          if receipt.state == "unconfirmed" and not receipt.failureNotified and type(Print) == "function" then
            Print("Roll choice was not confirmed by the host. It remains recorded locally for reconciliation.")
            receipt.failureNotified = true
          end
        else
          SendPendingChoiceReceipt(receipt)
        end
      end
      if receipt.state == "waiting" or receipt.state == "queue_grace" then
        hasWaitingReceipt = true
      end
    end
  end
  SetChoiceReceiptRetryActive(hasWaitingReceipt)
end

local function RearmUnconfirmedChoiceReceipts(opts)
  opts = type(opts) == "table" and opts or {}
  local joined, activeSession, profileName = ResolveActivePopupSessionState()
  if not joined or type(activeSession) ~= "table" or IsSelfHost(activeSession) then
    return false, 0
  end
  if not EnsureRuntimeSession(activeSession, profileName) then
    return false, 0
  end
  local selfKey = ResolvePopupSelfMemberKey(activeSession)
  if selfKey == "" then
    return false, 0
  end
  RuntimeState.pendingChoiceReceipts = type(RuntimeState.pendingChoiceReceipts) == "table"
    and RuntimeState.pendingChoiceReceipts
    or {}
  local commProfileName = GetSessionCommProfileName(activeSession, profileName)
  local now = GetChoiceReceiptClock()
  local rearmed = 0
  local hasWaiting = false
  local changed = false
  for _, itemKey in ipairs(RuntimeState.itemOrder or {}) do
    local entry = type(RuntimeState.items) == "table" and RuntimeState.items[itemKey] or nil
    local selection, storedMemberKey = ResolvePopupEntrySelection(entry, selfKey)
    local optionKey = string.lower(TrimText(type(selection) == "table" and selection.optionKey or ""))
    if type(selection) == "table"
      and string.lower(TrimText(selection.source)) == "local"
      and optionKey ~= ""
      and optionKey ~= "pending"
      and (tonumber(selection.selectedAt) or 0) > 0
      and (tonumber(selection.selectedSeq) or 0) > 0
    then
      local memberKey = TrimText(storedMemberKey) ~= "" and storedMemberKey or selfKey
      local receiptKey = BuildChoiceReceiptKey(activeSession.id, itemKey, memberKey, selection.selectedSeq)
      local receipt = RuntimeState.pendingChoiceReceipts[receiptKey]
      if type(receipt) == "table" and (receipt.state == "waiting" or receipt.state == "queue_grace") then
        hasWaiting = true
      else
        local rearmCount = math.max(
          math.max(0, math.floor(tonumber(selection.receiptRearmCount) or 0)),
          math.max(0, math.floor(tonumber(type(receipt) == "table" and receipt.rearmCount) or 0))
        )
        if choiceReceiptRecovery.CanRearm(selection, receipt) then
          receipt = type(receipt) == "table" and receipt or {}
          receipt.sessionId = TrimText(activeSession.id)
          receipt.profileName = commProfileName
          receipt.host = TrimText(activeSession.host)
          receipt.itemKey = TrimText(itemKey)
          receipt.memberKey = NormalizePopupCharacterKey(memberKey)
          receipt.optionKey = TrimText(selection.optionKey)
          receipt.optionLabel = TrimText(selection.label)
          receipt.note = NormalizeSelectionNote(selection.note or "")
          receipt.rollValue = selection.rollValue or ""
          receipt.selectedAt = tonumber(selection.selectedAt)
          receipt.selectedSeq = math.floor(tonumber(selection.selectedSeq) or 1)
          receipt.attempts = 0
          receipt.state = "waiting"
          receipt.createdAt = now
          receipt.nextRetryAt = now
          receipt.failureNotified = nil
          receipt.queueGraceStartedAt = nil
          receipt.queueGraceExpiresAt = nil
          receipt.queuePressureReason = nil
          receipt.rearmCount = rearmCount + 1
          RuntimeState.pendingChoiceReceipts[receiptKey] = receipt
          selection.receiptState = "waiting"
          selection.receiptRearmCount = receipt.rearmCount
          SendPendingChoiceReceipt(receipt)
          rearmed = rearmed + 1
          hasWaiting = true
          changed = true
        end
      end
    end
  end
  if changed and type(PersistRuntimeBoardHistory) == "function" then
    PersistRuntimeBoardHistory({ reason = TrimText(opts.reason) ~= "" and TrimText(opts.reason) or "choice_receipt_rearm" })
  end
  SetChoiceReceiptRetryActive(hasWaiting)
  return rearmed > 0, rearmed
end

local function SubmitCurrentPopupChoice(option, noteText, activeSessionOverride, profileNameOverride, itemKeyOverride)
  local totalStartedAt = BeginRollPerformanceSample("roll_choice_total")
  local joined, activeSession, profileName
  local function FinishSubmit(ok, reason)
    RecordRollPerformanceSample("roll_choice_total", totalStartedAt, {
      result = ok == true and "committed" or "blocked",
      reason = TrimText(reason),
      sessionId = TrimText(type(activeSession) == "table" and activeSession.id or ""),
      profileName = TrimText(profileName),
    })
    return ok == true
  end
  if type(activeSessionOverride) == "table" and TrimText(profileNameOverride) ~= "" then
    joined = true
    activeSession = activeSessionOverride
    profileName = TrimText(profileNameOverride)
  else
    joined, activeSession, profileName = ResolveActivePopupSessionState()
  end
  if not joined or not activeSession then
    return FinishSubmit(false, "session_unavailable")
  end
  if #RuntimeState.popupQueue == 0 and TrimText(itemKeyOverride) == "" then
    return FinishSubmit(false, "queue_empty")
  end

  local itemKey = TrimText(itemKeyOverride) ~= "" and TrimText(itemKeyOverride) or RuntimeState.popupQueue[1]
  local entry = RuntimeState.items[itemKey]
  local selfKey = ResolvePopupSelfMemberKey(activeSession)
  if not entry or selfKey == "" then
    RemoveQueuedPopupItem(itemKey)
    return FinishSubmit(false, "item_or_member_missing")
  end

  local recordStartedAt = BeginRollPerformanceSample("roll_choice_record")
  local selectionOption = DeepCopy(option or {})
  selectionOption.note = NormalizeSelectionNote(noteText)
  local selectionKey = string.lower(TrimText(selectionOption.key or selectionOption.optionKey or selectionOption.label))
  if selectionKey ~= "" and selectionKey ~= "pending" then
    selectionOption.rollValue = math.random(1, 100)
  else
    selectionOption.rollValue = nil
  end
  local existingSelection = ResolvePopupEntrySelection(entry, selfKey)
  selectionOption.selectedSeq = (tonumber(type(existingSelection) == "table" and existingSelection.selectedSeq) or 0) + 1
  if not IsSelfHost(activeSession) then
    selectionOption.receiptState = "waiting"
    selectionOption.receiptRearmCount = 0
  end
  RecordChoice(entry, selfKey, selectionOption, "LOCAL")
  local storedSelection = ResolvePopupEntrySelection(entry, selfKey)
  local selectedAt = tonumber(type(storedSelection) == "table" and storedSelection.selectedAt) or (time and time() or 0)
  local selectedSeq = tonumber(type(storedSelection) == "table" and storedSelection.selectedSeq) or 1
  local commProfileName = GetSessionCommProfileName(activeSession, profileName)
  RecordRollPerformanceSample("roll_choice_record", recordStartedAt, {
    itemKey = itemKey,
    memberKey = selfKey,
  })

  local dispatchStartedAt = BeginRollPerformanceSample("roll_choice_dispatch")
  if IsSelfHost(activeSession) then
    if type(BroadcastAuthoritativeChoice) == "function" then
      BroadcastAuthoritativeChoice(
        activeSession, commProfileName, itemKey, selfKey,
        selectionOption.key, selectionOption.label, selectionOption.note or "",
        selectionOption.rollValue or "", selectedAt, selectedSeq
      )
    elseif type(Print) == "function" then
      Print("Your roll was recorded locally, but the authoritative choice sender is unavailable.")
    end
  else
    RuntimeState.pendingChoiceReceipts = type(RuntimeState.pendingChoiceReceipts) == "table"
      and RuntimeState.pendingChoiceReceipts
      or {}
    local receipt = {
      sessionId = TrimText(activeSession.id),
      profileName = commProfileName,
      host = TrimText(activeSession.host),
      itemKey = itemKey,
      memberKey = selfKey,
      optionKey = TrimText(selectionOption.key),
      optionLabel = TrimText(selectionOption.label),
      note = selectionOption.note or "",
      rollValue = selectionOption.rollValue or "",
      selectedAt = selectedAt,
      selectedSeq = selectedSeq,
      attempts = 0,
      state = "waiting",
      createdAt = GetChoiceReceiptClock(),
      nextRetryAt = GetChoiceReceiptClock(),
    }
    RuntimeState.pendingChoiceReceipts[BuildChoiceReceiptKey(activeSession.id, itemKey, selfKey, selectedSeq)] = receipt
    SendPendingChoiceReceipt(receipt)
    SetChoiceReceiptRetryActive(true)
  end
  RecordRollPerformanceSample("roll_choice_dispatch", dispatchStartedAt, {
    itemKey = itemKey,
    mode = IsSelfHost(activeSession) and "host" or "member",
  })

  local refreshStartedAt = BeginRollPerformanceSample("roll_choice_ui_refresh")
  RemoveQueuedPopupItem(itemKey)
  RuntimeState.selectedItemKey = TrimText(RuntimeState.popupQueue[1]) ~= "" and RuntimeState.popupQueue[1] or itemKey
  if UIState.choicePopup and UIState.choicePopup.commentEdit then
    SetChoicePopupComment(UIState.choicePopup, "")
  end
  RefreshBoardAndEditor({
    reason = "choice",
    itemKey = itemKey,
    refreshEditor = false,
  })
  RefreshVisibleLiveBoardSummary()
  RecordRollPerformanceSample("roll_choice_ui_refresh", refreshStartedAt, {
    itemKey = itemKey,
  })
  return FinishSubmit(true, "")
end

local function PreviewRollChoicePopup(profileName)
  local frame = CreateChoicePopup()
  frame.__itemNavigatorVisible = false
  local _, resolvedProfileName = GetLootProfile(profileName)
  local options = ResolveRollOptions(nil, resolvedProfileName)
  if type(options) ~= "table" or #options == 0 then
    options = DEFAULT_OPTIONS
  end

  frame.icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
  frame.title:SetText("LidCouncil Popup Preview")
  if frame.modeText then
    frame.modeText:Hide()
  end
  frame.itemText:SetText("|cffd1b35fSample Raid Item|r")
  SetChoicePopupItemMetaText(frame, nil)
  if frame.SetItemTooltipData then
    frame:SetItemTooltipData(nil, nil, "Sample Raid Item")
  end
  frame.queueText:SetText("Preview")
  frame.hint:SetText("Preview of the joined-player LidCouncil popup for profile \"" .. tostring(resolvedProfileName or "Default") .. "\". This does not send any selection.")
  frame.__commentExpanded = false
  frame.__commentEffectiveExpanded = nil
  SetChoicePopupPendingRollIndicator(frame, false)
  SetChoicePopupComment(frame, "")
  UpdateChoicePopupCommentPresentation(frame, false)
  if frame.closeBtn then
    frame.closeBtn:Show()
  end
  local orderedOptions = LayoutChoicePopupButtons(frame, options, true)
  if type(orderedOptions) ~= "table" or #orderedOptions == 0 then
    orderedOptions = LayoutChoicePopupButtons(frame, DEFAULT_OPTIONS, true)
  end

  for i = 1, #orderedOptions do
    local button = EnsureChoicePopupButton(frame, i)
    local option = orderedOptions[i]
    ApplyChoicePopupButtonOption(button, option)
    if button.Enable then
      button:Enable()
    end
    button:SetAlpha(1.0)
    if button.SetBackdropBorderColor then
      button:SetBackdropBorderColor(0.22, 0.24, 0.30, 0.82)
    end
    button:SetScript("OnClick", function() end)
    button:Show()
  end

  frame:Show()
end

local function OpenSimulationChoicePopup(preview, itemKey)
  if type(preview) ~= "table" or type(preview.items) ~= "table" then
    return
  end

  local entry = preview.items[itemKey]
  if type(entry) ~= "table" then
    return
  end

  local options = ResolveRollOptions(nil, TrimText(preview.sourceProfileName ~= "" and preview.sourceProfileName or preview.profileName))
  if type(options) ~= "table" or #options == 0 then
    options = DEFAULT_OPTIONS
  end
  local frame = CreateChoicePopup()
  frame.__itemNavigatorVisible = false
  frame.__commentExpanded = false
  frame.__commentEffectiveExpanded = nil
  preview.selectedItemKey = itemKey
  frame.title:SetText("LidCouncil Roll Choice")
  frame.icon:SetTexture(entry.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
  frame.itemText:SetText(type(ResolveRuntimeItemDisplayText) == "function"
    and ResolveRuntimeItemDisplayText(entry, itemKey)
    or (entry.name or itemKey))
  SetChoicePopupItemMetaText(frame, entry)
  if frame.SetItemTooltipData then
    frame:SetItemTooltipData(entry.link, entry.itemID, entry.name or itemKey, entry.itemLevel)
  end
  local pendingCount = #(preview.popupQueue or {})
  local isPending = false
  for _, queuedKey in ipairs(preview.popupQueue or {}) do
    if queuedKey == itemKey then
      isPending = true
      break
    end
  end
  SetChoicePopupPendingRollIndicator(frame, pendingCount)
  frame.queueText:SetText(pendingCount > 0 and FormatRollRemainingText(pendingCount) or "Simulation review")
  frame.hint:SetText(isPending
    and "Choose a roll option as if you were a joined session member. The host-side Live Board will update immediately after the click."
    or "This item already has simulated roll results. You can inspect it in the Live Board or rerun the full simulation from the button.")
  if frame.commentLabel then
    frame.commentLabel:SetText("Comment for the host (optional):")
    frame.commentLabel:Show()
  end
  if frame.commentEdit then
    frame.commentEdit:Show()
    if frame.commentEdit.Enable then
      frame.commentEdit:Enable()
    end
  end
  local existingSelection = entry.selections and entry.selections[preview.selfKey or ""] or nil
  if frame.modeText then
    frame.modeText:SetText("Simulation")
    frame.modeText:Show()
  end
  SetChoicePopupComment(frame, existingSelection and existingSelection.note or "")
  UpdateChoicePopupCommentPresentation(frame, false)
  if frame.closeBtn then
    frame.closeBtn:Show()
  end
  local orderedOptions = LayoutChoicePopupButtons(frame, options, true)
  if type(orderedOptions) ~= "table" or #orderedOptions == 0 then
    frame.hint:SetText("No roll options were found for this simulation. LidCouncil is falling back to the default options.")
    orderedOptions = LayoutChoicePopupButtons(frame, DEFAULT_OPTIONS, true)
  end

  for i = 1, #orderedOptions do
    local button = EnsureChoicePopupButton(frame, i)
    local option = orderedOptions[i]
    ApplyChoicePopupButtonOption(button, option)
    if button.Enable then
      button:Enable()
    end
    button:SetAlpha(1.0)
    if button.SetBackdropBorderColor then
      button:SetBackdropBorderColor(0.22, 0.24, 0.30, 0.82)
    end
    button:SetScript("OnClick", function()
      local noteText = UIState.choicePopup and UIState.choicePopup.commentEdit and UIState.choicePopup.commentEdit:GetText() or ""
      local nextItemKey = ApplySimulationChoice(preview, itemKey, {
        key = TrimText(option.key or option.label),
        label = TrimText(option.label or option.key),
        color = DeepCopy(option.color or { 0.82, 0.82, 0.86 }),
        icon = ResolveOptionIconTexture(option.icon, option.key, option.label),
        pointBehavior = option.pointBehavior,
        popupSection = option.popupSection,
        note = noteText,
      }, options)
      PersistBoardState(UIState.boardFrame)
      frame:Hide()
      if UIState.boardFrame and UIState.boardFrame.RefreshBoard then
        UIState.boardFrame:RefreshBoard()
      end
      if nextItemKey and preview.items and preview.items[nextItemKey] then
        OpenSimulationChoicePopup(preview, nextItemKey)
      elseif type(Print) == "function" then
        Print("[Simulation] All simulated items have finished rolling. Use the Live Board to review and assign them.")
      end
    end)
    button:Show()
  end

  frame:Show()
end

ShowCurrentChoicePopup = function(activeSession, profileName, preferredItemKey)
  local totalStartedAt = BeginRollPerformanceSample("roll_popup_refresh_total")
  local frame = CreateChoicePopup()
  local function FinishPopup(ok, reason)
    RecordRollPerformanceSample("roll_popup_refresh_total", totalStartedAt, {
      result = ok == true and "opened" or "blocked",
      reason = TrimText(reason),
      sessionId = TrimText(type(activeSession) == "table" and activeSession.id or ""),
      profileName = TrimText(profileName),
    })
    return ok == true
  end
  if type(activeSession) ~= "table" or TrimText(profileName) == "" then
    RecordChoicePopupState(activeSession, profileName, "blocked", "missing_session_or_profile", preferredItemKey)
    if type(RefreshHeaderPendingRollIndicator) == "function" then
      RefreshHeaderPendingRollIndicator()
    end
    frame:Hide()
    return FinishPopup(false, "missing_session_or_profile")
  end

  if not EnsureRuntimeSession(activeSession, profileName) then
    RecordChoicePopupState(activeSession, profileName, "blocked", "runtime_session_unavailable", preferredItemKey)
    frame:Hide()
    return FinishPopup(false, "runtime_session_unavailable")
  end
  local selfKey = ResolvePopupSelfMemberKey(activeSession)

  while #RuntimeState.popupQueue > 0 do
    local headKey = RuntimeState.popupQueue[1]
    local entry = RuntimeState.items[headKey]
    local selection = ResolvePopupEntrySelection(entry, selfKey)
    local selectionKey = string.lower(TrimText(type(selection) == "table" and (selection.optionKey or selection.label) or ""))
    if not entry or (selection and selectionKey ~= "" and selectionKey ~= "pending") then
      table.remove(RuntimeState.popupQueue, 1)
    else
      break
    end
  end

  if type(RefreshHeaderPendingRollIndicator) == "function" then
    RefreshHeaderPendingRollIndicator()
  end

  if #RuntimeState.popupQueue == 0 then
    RecordChoicePopupState(activeSession, profileName, "blocked", "no_pending_choices", preferredItemKey)
    frame:Hide()
    return FinishPopup(false, "no_pending_choices")
  end

  frame.__itemNavigatorVisible = true
  local navigatorStartedAt = BeginRollPerformanceSample("roll_popup_navigator")
  local navigatorEntries = BuildChoicePopupNavigatorEntries(activeSession, selfKey)
  RecordRollPerformanceSample("roll_popup_navigator", navigatorStartedAt, {
    itemCount = #navigatorEntries,
  })

  local itemKey = TrimText(preferredItemKey)
  local selectedEntry = FindChoicePopupNavigatorEntry(navigatorEntries, RuntimeState.selectedItemKey)
  if itemKey == "" then
    local preferredSelectedKey = TrimText(type(selectedEntry) == "table" and selectedEntry.itemKey or "")
    if preferredSelectedKey ~= "" and type(selectedEntry) == "table" and not selectedEntry.committed then
      itemKey = preferredSelectedKey
    else
      itemKey = TrimText(RuntimeState.popupQueue[1])
    end
  end
  if itemKey == "" or not FindChoicePopupNavigatorEntry(navigatorEntries, itemKey) then
    itemKey = TrimText(RuntimeState.popupQueue[1])
  end

  local optionsStartedAt = BeginRollPerformanceSample("roll_popup_options")
  local options = ResolveRollOptions(activeSession, profileName)
  local orderedOptions = LayoutChoicePopupButtons(frame, options, false)
  RecordRollPerformanceSample("roll_popup_options", optionsStartedAt, {
    optionCount = type(orderedOptions) == "table" and #orderedOptions or 0,
  })
  local renderStartedAt = BeginRollPerformanceSample("roll_popup_render")
  itemKey, navigatorEntries = RefreshChoicePopupNavigator(frame, activeSession, profileName, itemKey)
  RuntimeState.selectedItemKey = itemKey
  local entry = RuntimeState.items[itemKey]
  if not entry then
    RecordChoicePopupState(activeSession, profileName, "blocked", "selected_item_missing", itemKey)
    frame:Hide()
    RecordRollPerformanceSample("roll_popup_render", renderStartedAt, {
      result = "selected_item_missing",
    })
    return FinishPopup(false, "selected_item_missing")
  end

  local isRealSimulation = IsRealSimulationItemKey(itemKey)
  local existingSelection = ResolvePopupEntrySelection(entry, selfKey)
  local pendingReceipt = GetPendingChoiceReceipt(activeSession, itemKey, selfKey, existingSelection)
  local selectionLocked = HasCommittedPopupSelection(existingSelection)
  frame.icon:SetTexture(entry.icon)
  frame.title:SetText("LidCouncil Roll Choice")
  if frame.modeText then
    frame.modeText:SetText("Simulation")
    frame.modeText:SetShown(isRealSimulation)
  end
  frame.itemText:SetText(type(ResolveRuntimeItemDisplayText) == "function"
    and ResolveRuntimeItemDisplayText(entry, itemKey)
    or (entry.name or itemKey))
  SetChoicePopupItemMetaText(frame, entry)
  if frame.SetItemTooltipData then
    frame:SetItemTooltipData(entry.link, entry.itemID, entry.name or itemKey, entry.itemLevel)
  end
  local completedCount = 0
  for _, navigatorEntry in ipairs(navigatorEntries) do
    if navigatorEntry.committed then
      completedCount = completedCount + 1
    end
  end
  local pendingCount = #navigatorEntries - completedCount
  SetChoicePopupPendingRollIndicator(frame, pendingCount)
  frame.queueText:SetText(FormatRollRemainingText(pendingCount))
  if type(pendingReceipt) == "table" then
    frame.hint:SetText(pendingReceipt.state == "unconfirmed"
      and "Your roll choice is still waiting for host confirmation. It will reconcile when the host state arrives."
      or (pendingReceipt.state == "queue_grace"
        and "Your roll choice is saved. Host live traffic is clearing before confirmation is finalized."
        or "Submitted " .. tostring(existingSelection.label or existingSelection.optionKey or "a roll choice") .. ". Waiting for host confirmation."))
  elseif selectionLocked then
    frame.hint:SetText(
      "Submitted "
      .. tostring(existingSelection.label or existingSelection.optionKey or "a roll choice")
      .. ". Changes must be made by the council in the Live Board."
    )
  else
    frame.hint:SetText(isRealSimulation
      and "Simulation mode - Loot List points will not be saved."
      or "Choose your LidCouncil preference. Your choice is sent to the session host.")
  end
  if frame.closeBtn then
    frame.closeBtn:Hide()
  end
  SetChoicePopupComment(frame, existingSelection and existingSelection.note or "")
  UpdateChoicePopupCommentPresentation(frame, selectionLocked)
  local _, recommendedOption, recommendationStatus = ResolvePersonalRecommendationForPopup(profileName, entry, options)
  if recommendationStatus == "MISSING" and frame.hint then
    frame.hint:SetText((frame.hint:GetText() or "") .. " Your personal recommendation points to a removed roll priority.")
  end

  for i = 1, #orderedOptions do
    local button = EnsureChoicePopupButton(frame, i)
    local option = orderedOptions[i]
    local isChosenOption = DoesOptionMatchCommittedSelection(option, existingSelection)
    ApplyChoicePopupButtonOption(button, option, not selectionLocked and DoesOptionMatchRecommendation(option, recommendedOption))
    if selectionLocked then
      button:SetScript("OnClick", nil)
      if button.Enable then
        button:Disable()
      end
      button:SetAlpha(isChosenOption and 1.0 or 0.45)
      if button.SetBackdropBorderColor then
        if isChosenOption then
          button:SetBackdropBorderColor(1.00, 0.82, 0.32, 0.94)
        else
          button:SetBackdropBorderColor(0.22, 0.24, 0.30, 0.82)
        end
      end
      if button.recommendationText then
        button.recommendationText:SetText(isChosenOption and "Chosen" or "")
        button.recommendationText:SetShown(isChosenOption)
      end
      if button.recommendationGlow then
        button.recommendationGlow:SetShown(isChosenOption)
      end
    else
      if button.Enable then
        button:Enable()
      end
      button:SetAlpha(1.0)
      if button.SetBackdropBorderColor then
        button:SetBackdropBorderColor(0.22, 0.24, 0.30, 0.82)
      end
      button:SetScript("OnClick", function()
        local noteText = UIState.choicePopup and UIState.choicePopup.commentEdit and UIState.choicePopup.commentEdit:GetText() or ""
        local committed = SubmitCurrentPopupChoice({
          key = TrimText(option.key or option.label),
          label = TrimText(option.label or option.key),
          color = DeepCopy(option.color or { 0.82, 0.82, 0.86 }),
          icon = ResolveOptionIconTexture(option.icon, option.key, option.label),
          pointBehavior = option.pointBehavior,
          popupSection = option.popupSection,
        }, noteText, activeSession, profileName, itemKey)
        if committed then
          ShowCurrentChoicePopup(activeSession, profileName)
        else
          RefreshChoicePopup()
        end
      end)
    end
    button:Show()
  end

  ApplyChoicePopupPosition(frame)
  RaisePopupFrameAboveEditor(frame)
  frame:Show()
  if frame.Raise then
    frame:Raise()
  end
  RecordChoicePopupState(activeSession, profileName, "opened", "", itemKey)
  RecordRollPerformanceSample("roll_popup_render", renderStartedAt, {
    itemCount = #navigatorEntries,
    optionCount = #orderedOptions,
  })
  return FinishPopup(true, "")
end

local function RunLiveLootSimulation(profileName)
  local frame = createBoardFrame()
  local previewState, previewError = BuildSimulationBoardState(profileName)
  if type(previewState) ~= "table" then
    if type(Print) == "function" then
      Print(tostring(previewError or "Simulation profile unavailable."))
    end
    return
  end
  SetBoardPreviewState(frame, previewState)
  PersistBoardState(frame)
  if type(Print) == "function" and type(frame.__previewState) == "table" then
    Print(
      string.format(
        "[Simulation] Using the settings from profile \"%s\" for this run. Awarded simulated items will be added to the Simulation loot list.",
        tostring(frame.__previewState.sourceProfileName or profileName or "Default")
      )
    )
  end
  if not frame.__embeddedParent then
    applyPopupBoardLayout(frame)
  end
  frame:Show()
  frame:RefreshBoard()
  setBoardCollapsed(frame, false)
  OpenSimulationChoicePopup(frame.__previewState, frame.__previewState.selectedItemKey)
end

local RunLiveLootRealSimulation

local function ScheduleRealSimulationItemPreparation(pending)
  if type(pending) ~= "table" then
    return false
  end
  if type(C_Timer) ~= "table" or type(C_Timer.After) ~= "function" then
    RuntimeState.pendingRealSimulationItemLoad = nil
    RunLiveLootRealSimulation(pending.profileName, {
      itemDefs = pending.itemDefs,
      sourceLabel = pending.sourceLabel,
      itemLoadTimedOut = true,
      itemLoadWaitMs = 0,
      testRunId = pending.testRunId,
    })
    return false
  end

  C_Timer.After(REAL_SIMULATION_ITEM_LOAD_RETRY_SECONDS, function()
    local current = RuntimeState.pendingRealSimulationItemLoad
    if type(current) ~= "table" or TrimText(current.testRunId) ~= TrimText(pending.testRunId) then
      return
    end

    current.attempts = math.max(0, tonumber(current.attempts) or 0) + 1
    local readyCount, totalCount, itemIds, itemIdCount = InspectSimulationItemDefs(current.itemDefs)
    local now = GetSimulationItemLoadClock()
    if readyCount < totalCount
      and itemIdCount < totalCount
      and now >= math.max(0, tonumber(current.nextDefinitionRebuildAt) or 0)
    then
      current.nextDefinitionRebuildAt = now + REAL_SIMULATION_DEFINITION_REBUILD_SECONDS
      local rebuiltDefs, rebuiltSourceLabel = BuildRealSimulationItemDefs(current.profileName, REAL_SIMULATION_ITEM_COUNT)
      local rebuiltReadyCount, rebuiltTotalCount, rebuiltItemIds, rebuiltItemIdCount = InspectSimulationItemDefs(rebuiltDefs)
      if rebuiltReadyCount > readyCount or rebuiltItemIdCount > itemIdCount then
        current.itemDefs = rebuiltDefs
        current.sourceLabel = rebuiltSourceLabel
        current.definitionRebuilds = math.max(0, tonumber(current.definitionRebuilds) or 0) + 1
        readyCount = rebuiltReadyCount
        totalCount = rebuiltTotalCount
        itemIds = rebuiltItemIds
        itemIdCount = rebuiltItemIdCount
      end
    end
    if totalCount > 0 and readyCount >= totalCount then
      RuntimeState.pendingRealSimulationItemLoad = nil
      local waitMs = math.max(0, math.floor(((now - (tonumber(current.startedAt) or now)) * 1000) + 0.5))
      RecordRaidTraceEvent("TEST_ITEMS_PRELOAD_READY", "Simulation item data is cached.", {
        run = TrimText(current.testRunId),
        items = totalCount,
        attempts = current.attempts,
        waitMs = waitMs,
        itemIds = itemIds,
        definitionRebuilds = math.max(0, tonumber(current.definitionRebuilds) or 0),
      })
      RunLiveLootRealSimulation(current.profileName, {
        itemDefs = current.itemDefs,
        sourceLabel = current.sourceLabel,
        itemLoadWaitMs = waitMs,
        testRunId = current.testRunId,
      })
      return
    end

    if now >= (tonumber(current.deadline) or now) then
      RuntimeState.pendingRealSimulationItemLoad = nil
      local waitMs = math.max(0, math.floor(((now - (tonumber(current.startedAt) or now)) * 1000) + 0.5))
      RecordRaidTraceEvent("TEST_ITEMS_PRELOAD_TIMEOUT", "Simulation item data did not become ready.", {
        run = TrimText(current.testRunId),
        ready = readyCount,
        items = totalCount,
        attempts = current.attempts,
        waitMs = waitMs,
        itemIds = itemIds,
        definitionRebuilds = math.max(0, tonumber(current.definitionRebuilds) or 0),
      })
      RunLiveLootRealSimulation(current.profileName, {
        itemDefs = current.itemDefs,
        sourceLabel = current.sourceLabel,
        itemLoadTimedOut = true,
        itemLoadReadyCount = readyCount,
        itemLoadItemCount = totalCount,
        itemLoadWaitMs = waitMs,
        testRunId = current.testRunId,
      })
      return
    end

    ScheduleRealSimulationItemPreparation(current)
  end)
  return true
end

RunLiveLootRealSimulation = function(profileName, preparedRequest)
  preparedRequest = type(preparedRequest) == "table" and preparedRequest or nil
  local totalStartedAt = BeginRollPerformanceSample("roll_test_total")
  local activeSession, activeProfileName = GetActiveLootSession()
  local function FinishRealSimulationResult(ok, code, message, detail)
    local result = type(detail) == "table" and detail or {}
    local preserveExistingReport = result.preserveExistingReport == true
    result.preserveExistingReport = nil
    result.ok = ok == true
    result.code = TrimText(code)
    result.message = TrimText(message)
    result.sessionId = TrimText(result.sessionId ~= nil and result.sessionId or (type(activeSession) == "table" and activeSession.id or ""))
    result.profileName = TrimText(result.profileName ~= nil and result.profileName or activeProfileName)
    result.boardId = TrimText(result.boardId ~= nil and result.boardId or RuntimeState.boardId)
    result.updatedAt = (type(GetServerTime) == "function" and GetServerTime()) or (time and time() or 0)
    if not preserveExistingReport then
      RuntimeState.lastRealSimulationRun = DeepCopy(result)
    end
    RecordRollPerformanceSample("roll_test_total", totalStartedAt, {
      result = result.ok and "complete" or "failed",
      code = result.code,
      itemCount = math.max(0, tonumber(result.itemCount) or 0),
      targetCount = math.max(0, tonumber(result.attemptedTargetCount) or 0),
    })
    if type(Print) == "function" and result.message ~= "" then
      Print(result.message)
    end
    if preparedRequest then
      RefreshVisibleLiveBoardSummary()
    end
    return result
  end

  if not activeSession or TrimText(activeProfileName) == "" then
    return FinishRealSimulationResult(false, "NO_ACTIVE_SESSION", "Start a session before running Test Raid Items.")
  end
  if string.upper(TrimText(activeSession.status or "ACTIVE")) ~= "ACTIVE" then
    return FinishRealSimulationResult(false, "SESSION_NOT_ACTIVE", "Start the prepared session before running Test Raid Items.")
  end

  local resolvedProfileName = TrimText(profileName ~= "" and profileName or activeProfileName)
  if resolvedProfileName ~= "" and resolvedProfileName ~= TrimText(activeProfileName) then
    return FinishRealSimulationResult(
      false,
      "PROFILE_MISMATCH",
      "Test Raid Items only works for the active session profile \"" .. tostring(activeProfileName) .. "\"."
    )
  end

  if not IsSelfHost(activeSession) then
    return FinishRealSimulationResult(false, "NOT_SESSION_HOST", "Only the active session host can run Test Raid Items.")
  end

  if type(InCombatLockdown) == "function" and InCombatLockdown() then
    return FinishRealSimulationResult(false, "COMBAT_LOCKDOWN", "Test Raid Items is unavailable during combat.")
  end

  local prepareStartedAt = BeginRollPerformanceSample("roll_test_prepare")
  local runtimeReady = EnsureRuntimeSession(activeSession, activeProfileName)
  RecordRollPerformanceSample("roll_test_prepare", prepareStartedAt, {
    runtimeReady = runtimeReady == true,
  })
  if not runtimeReady then
    return FinishRealSimulationResult(
      false,
      "RUNTIME_UNAVAILABLE",
      "Could not prepare the active session runtime for Test Raid Items."
    )
  end

  if CountNonSimulationRuntimeItems() > 0 then
    return FinishRealSimulationResult(
      false,
      "LIVE_ITEMS_PRESENT",
      "Finish or clear the current Live Board items before running Test Raid Items."
    )
  end

  local sessionId = TrimText(activeSession.id)
  local pendingItemLoad = type(RuntimeState.pendingRealSimulationItemLoad) == "table"
    and RuntimeState.pendingRealSimulationItemLoad or nil
  if not preparedRequest
    and pendingItemLoad
    and TrimText(pendingItemLoad.sessionId) == sessionId
    and TrimText(pendingItemLoad.profileName) == TrimText(activeProfileName)
  then
    pendingItemLoad.requestCount = math.max(1, tonumber(pendingItemLoad.requestCount) or 1) + 1
    RecordRaidTraceEvent("TEST_ITEMS_REQUEST_COALESCED", "Item preparation is already in progress.", {
      run = TrimText(pendingItemLoad.testRunId),
      requests = pendingItemLoad.requestCount,
      session = sessionId,
    })
    return FinishRealSimulationResult(true, "ITEM_DATA_LOADING", "Test Raid Items is already preparing item data.", {
      testRunId = TrimText(pendingItemLoad.testRunId),
      dispatchState = "preparing_items",
      statusText = "Preparing Test Raid Items; repeated request coalesced.",
      boardId = "",
    })
  end

  local currentSimulationReport = type(RuntimeState.lastRealSimulationRun) == "table"
    and RuntimeState.lastRealSimulationRun or nil
  local currentDispatchState = string.lower(TrimText(
    type(currentSimulationReport) == "table" and currentSimulationReport.dispatchState or ""
  ))
  if not preparedRequest
    and type(currentSimulationReport) == "table"
    and TrimText(currentSimulationReport.sessionId) == sessionId
    and (currentDispatchState == "awaiting_receipts" or currentDispatchState == "coalesced_latest")
  then
    RecordRaidTraceEvent("TEST_ITEMS_REQUEST_COALESCED", "The previous Test Raid Items board is still in flight.", {
      coalescing = "inflight",
      run = TrimText(currentSimulationReport.testRunId),
      boardId = TrimText(currentSimulationReport.boardId),
      dispatch = currentDispatchState,
      session = sessionId,
    })
    return FinishRealSimulationResult(true, "TEST_ITEMS_IN_FLIGHT", "The previous Test Raid Items board is still syncing; no duplicate board was created.", {
      preserveExistingReport = true,
      boardId = "",
      testRunId = TrimText(currentSimulationReport.testRunId),
      dispatchState = currentDispatchState,
      statusText = "Test Raid Items request coalesced; the previous board is still syncing.",
    })
  end

  if not preparedRequest then
    local requestAt = GetSimulationItemLoadClock()
    local previousRequestAt = math.max(0, tonumber(RuntimeState.lastRealSimulationRequestAt) or 0)
    if previousRequestAt > 0 and (requestAt - previousRequestAt) < REAL_SIMULATION_REQUEST_DEBOUNCE_SECONDS then
      RuntimeState.realSimulationDebouncedRequests =
        math.max(0, tonumber(RuntimeState.realSimulationDebouncedRequests) or 0) + 1
      RecordRaidTraceEvent("TEST_ITEMS_REQUEST_COALESCED", "Rapid repeated Test Raid Items request ignored.", {
        coalescing = "debounce",
        total = RuntimeState.realSimulationDebouncedRequests,
        elapsedMs = math.max(0, math.floor(((requestAt - previousRequestAt) * 1000) + 0.5)),
        session = sessionId,
      })
      return FinishRealSimulationResult(true, "REQUEST_COALESCED", "Rapid repeated Test Raid Items request coalesced safely.", {
        boardId = "",
        dispatchState = "request_coalesced",
        statusText = "Test Raid Items request coalesced; the current board operation continues.",
      })
    end
    RuntimeState.lastRealSimulationRequestAt = requestAt
  end

  RuntimeState.realSimulationRequestSequence = math.max(0, tonumber(RuntimeState.realSimulationRequestSequence) or 0)
  local testRunId = TrimText(preparedRequest and preparedRequest.testRunId or "")
  if testRunId == "" then
    RuntimeState.realSimulationRequestSequence = RuntimeState.realSimulationRequestSequence + 1
    testRunId = string.format("%s-%d", sessionId ~= "" and sessionId or "SESSION", RuntimeState.realSimulationRequestSequence)
    RecordRaidTraceEvent("TEST_ITEMS_REQUEST", "Test Raid Items requested.", {
      run = testRunId,
      session = sessionId,
      profile = TrimText(activeProfileName),
    })
  end

  local itemDefsStartedAt = BeginRollPerformanceSample("roll_test_item_defs")
  local itemDefs = preparedRequest and preparedRequest.itemDefs or nil
  local sourceLabel = preparedRequest and preparedRequest.sourceLabel or nil
  if type(itemDefs) ~= "table" then
    itemDefs, sourceLabel = BuildRealSimulationItemDefs(activeProfileName, REAL_SIMULATION_ITEM_COUNT)
  end
  RecordRollPerformanceSample("roll_test_item_defs", itemDefsStartedAt, {
    itemCount = type(itemDefs) == "table" and #itemDefs or 0,
  })
  if type(itemDefs) ~= "table" or #itemDefs == 0 then
    RecordRaidTraceEvent("TEST_ITEMS_NO_ITEMS", "No simulation item definitions were available.", {
      run = testRunId,
      session = sessionId,
    })
    return FinishRealSimulationResult(
      false,
      "NO_SIMULATION_ITEMS",
      "No Test Raid Items are available for the active profile.",
      { testRunId = testRunId, dispatchState = "failed" }
    )
  end

  if preparedRequest and preparedRequest.itemLoadTimedOut == true then
    local readyCount = math.max(0, tonumber(preparedRequest.itemLoadReadyCount) or 0)
    return FinishRealSimulationResult(
      false,
      "ITEM_DATA_TIMEOUT",
      string.format(
        "Test Raid Items stopped safely because WoW item data was not ready after %.1f seconds (%d/%d ready). Try again once the item cache has loaded.",
        math.max(0, tonumber(preparedRequest.itemLoadWaitMs) or 0) / 1000,
        readyCount,
        #itemDefs
      ),
      {
        boardId = "",
        testRunId = testRunId,
        dispatchState = "failed",
        itemCount = #itemDefs,
        itemDataReadyCount = readyCount,
        itemLoadWaitMs = math.max(0, tonumber(preparedRequest.itemLoadWaitMs) or 0),
      }
    )
  end

  if not preparedRequest then
    local readyCount, totalCount, itemIds = InspectSimulationItemDefs(itemDefs)
    if readyCount < totalCount then
      local now = GetSimulationItemLoadClock()
      local pending = {
        sessionId = sessionId,
        profileName = TrimText(activeProfileName),
        itemDefs = itemDefs,
        sourceLabel = sourceLabel,
        startedAt = now,
        deadline = now + REAL_SIMULATION_ITEM_LOAD_TIMEOUT_SECONDS,
        attempts = 0,
        requestCount = 1,
        definitionRebuilds = 0,
        nextDefinitionRebuildAt = now + REAL_SIMULATION_DEFINITION_REBUILD_SECONDS,
        testRunId = testRunId,
      }
      RuntimeState.pendingRealSimulationItemLoad = pending
      RecordRaidTraceEvent("TEST_ITEMS_PRELOAD_BEGIN", "Waiting for WoW item data.", {
        run = testRunId,
        ready = readyCount,
        items = totalCount,
        timeoutMs = math.floor(REAL_SIMULATION_ITEM_LOAD_TIMEOUT_SECONDS * 1000),
        itemIds = itemIds,
      })
      ScheduleRealSimulationItemPreparation(pending)
      return FinishRealSimulationResult(true, "ITEM_DATA_LOADING", "Preparing Test Raid Items while WoW loads their names, levels, and icons.", {
        testRunId = testRunId,
        dispatchState = "preparing_items",
        itemCount = totalCount,
        itemDataReadyCount = readyCount,
        statusText = "Preparing Test Raid Items; waiting for WoW item data.",
        boardId = "",
      })
    end
    RecordRaidTraceEvent("TEST_ITEMS_PRELOAD_READY", "Simulation item data was already cached.", {
      run = testRunId,
      items = totalCount,
      attempts = 0,
      waitMs = 0,
      itemIds = itemIds,
    })
  end

  ClearRealSimulationRuntimeItems()

  local runtimeBuildStartedAt = BeginRollPerformanceSample("roll_test_runtime_build")
  local selfKey = ResolvePopupSelfMemberKey(activeSession)
  local startedAt = (type(GetServerTime) == "function" and GetServerTime()) or (time and time() or 0)
  for index, itemDef in ipairs(itemDefs) do
    local itemKey = string.format("TESTSIM-%d-%d", startedAt, index)
    local displayName = TrimText(type(itemDef) == "table" and itemDef.name or "")
    if displayName == "" then
      displayName = "Simulation Item " .. tostring(index)
    end
    local entry = {
      key = itemKey,
      sessionId = tostring(activeSession.id or ""),
      profileName = tostring(activeProfileName or ""),
      name = displayName,
      link = type(itemDef) == "table" and itemDef.link or nil,
      icon = type(itemDef) == "table" and itemDef.icon or "Interface\\Icons\\INV_Misc_QuestionMark",
      quality = tonumber(type(itemDef) == "table" and itemDef.quality) or nil,
      itemID = ExtractItemID(type(itemDef) == "table" and (itemDef.itemID or itemDef.link) or nil),
      itemLevel = tonumber(type(itemDef) == "table" and itemDef.itemLevel) or nil,
      difficultyID = tonumber(type(itemDef) == "table" and itemDef.difficultyID) or nil,
      difficultyName = type(itemDef) == "table" and itemDef.difficultyName or nil,
      encounterName = TrimText(type(itemDef) == "table" and itemDef.encounterName or "") ~= ""
        and tostring(itemDef.encounterName)
        or "Raid Encounter",
      instanceName = TrimText(type(itemDef) == "table" and itemDef.instanceName or "") ~= ""
        and tostring(itemDef.instanceName)
        or "Raid Simulation",
      createdAt = startedAt + index,
      isSimulation = true,
      selections = {},
      votesByVoter = {},
      voteUpdatedAtByVoter = {},
      voteSequenceByVoter = {},
    }

    for memberKey, member in pairs(activeSession.members or {}) do
      if string.upper(TrimText(type(member) == "table" and member.status or "JOINED")) == "JOINED" then
        entry.selections[memberKey] = BuildPendingSelection()
      end
    end
    if selfKey ~= "" and type(ResolvePopupEntrySelection(entry, selfKey)) ~= "table" then
      entry.selections[selfKey] = BuildPendingSelection()
    end

    RuntimeState.items[itemKey] = entry
    RuntimeState.itemOrder[#RuntimeState.itemOrder + 1] = itemKey
    RuntimeState.popupQueue[#RuntimeState.popupQueue + 1] = itemKey
  end

  RuntimeState.selectedItemKey = RuntimeState.itemOrder[1]
  RuntimeState.selectedMemberKey = nil
  RuntimeState.boardUpdatedAt = math.max(tonumber(RuntimeState.boardUpdatedAt) or 0, startedAt)
  RecordRollPerformanceSample("roll_test_runtime_build", runtimeBuildStartedAt, {
    itemCount = #itemDefs,
  })

  local persistStartedAt = BeginRollPerformanceSample("roll_test_persist")
  PersistBoardState()
  RecordRollPerformanceSample("roll_test_persist", persistStartedAt, {
    itemCount = #itemDefs,
  })

  local popupStartedAt = BeginRollPerformanceSample("roll_test_popup")
  local popupOpened = ShowCurrentChoicePopup(activeSession, activeProfileName)
  RecordRollPerformanceSample("roll_test_popup", popupStartedAt, {
    opened = popupOpened == true,
  })

  local boardStartedAt = BeginRollPerformanceSample("roll_test_board_render")
  local frame = createBoardFrame()
  SetBoardPreviewState(frame, nil)
  if not frame.__embeddedParent then
    applyPopupBoardLayout(frame)
  end
  frame:Show()
  frame:RefreshBoard()
  setBoardCollapsed(frame, false)
  RecordRollPerformanceSample("roll_test_board_render", boardStartedAt, {
    itemCount = #itemDefs,
  })

  local dispatchStartedAt = BeginRollPerformanceSample("roll_test_dispatch")
  local sendBoardSync = ResolveOptionalFunction("LIDCOUNCIL_SendActiveLiveBoardSyncFlow")
  local dispatchOk = false
  local dispatchCount = 0
  local dispatchDetail = {
    attemptedCount = 0,
    sentCount = 0,
    failedCount = 0,
    receiptPendingCount = 0,
    targets = {},
  }
  if type(sendBoardSync) == "function" then
    dispatchOk, dispatchCount, dispatchDetail = sendBoardSync({
      performancePrefix = "roll_test_dispatch",
      reuseCurrentLocalBoard = true,
    })
    dispatchDetail = type(dispatchDetail) == "table" and dispatchDetail or {}
  else
    dispatchDetail.reason = "board_sync_api_unavailable"
  end
  RecordRollPerformanceSample("roll_test_dispatch", dispatchStartedAt, {
    attemptedCount = math.max(0, tonumber(dispatchDetail.attemptedCount) or 0),
    sentCount = math.max(0, tonumber(dispatchDetail.sentCount) or tonumber(dispatchCount) or 0),
  })
  local editorRefreshStartedAt = BeginRollPerformanceSample("roll_test_editor_refresh")
  RefreshVisibleLiveBoardSummary()
  RecordRollPerformanceSample("roll_test_editor_refresh", editorRefreshStartedAt, {
    itemCount = #itemDefs,
  })

  if not popupOpened and type(RefreshChoicePopup) == "function" then
    if not RefreshChoicePopup() then
      ShowCurrentChoicePopup(activeSession, activeProfileName)
    end
  elseif not popupOpened then
    ShowCurrentChoicePopup(activeSession, activeProfileName)
  end

  local attemptedCount = math.max(0, tonumber(dispatchDetail.attemptedCount) or 0)
  local sentCount = math.max(0, tonumber(dispatchDetail.sentCount) or tonumber(dispatchCount) or 0)
  local failedCount = math.max(0, tonumber(dispatchDetail.failedCount) or 0)
  local dispatchTargets = {}
  for _, target in ipairs(type(dispatchDetail.targets) == "table" and dispatchDetail.targets or {}) do
    local targetKey = string.lower(TrimText(type(target) == "table" and target.target or ""))
    if targetKey ~= "" then
      dispatchTargets[targetKey] = DeepCopy(target)
    end
  end
  local sourceText = TrimText(sourceLabel or "")
  local resultCode
  local resultMessage
  local resultOk = true
  local dispatchState
  if type(sendBoardSync) ~= "function" then
    resultCode = "BOARD_SYNC_UNAVAILABLE"
    resultMessage = string.format("Created %d Test Raid Items locally, but Live Board sync is unavailable.", #itemDefs)
    resultOk = false
    dispatchState = "failed"
  elseif dispatchDetail.coalescedLatest == true then
    resultCode = "COALESCED_LATEST"
    resultMessage = string.format(
      "Created %d Test Raid Items locally. The newest board is retained until the active board sync finishes.",
      #itemDefs
    )
    dispatchState = "coalesced_latest"
  elseif attemptedCount == 0 then
    resultCode = "LOCAL_ONLY"
    resultMessage = string.format("Created %d Test Raid Items locally. No joined members required a board dispatch.", #itemDefs)
    dispatchState = "local_only"
  elseif sentCount == 0 then
    resultCode = "DISPATCH_FAILED"
    resultMessage = string.format("Created %d Test Raid Items, but dispatch failed for all %d joined members.", #itemDefs, attemptedCount)
    resultOk = false
    dispatchState = "failed"
  elseif failedCount > 0 or sentCount < attemptedCount then
    resultCode = "DISPATCH_PARTIAL"
    resultMessage = string.format(
      "Created %d Test Raid Items. Sent to %d of %d joined members; %d dispatches failed.",
      #itemDefs,
      sentCount,
      attemptedCount,
      math.max(failedCount, attemptedCount - sentCount)
    )
    resultOk = false
    dispatchState = "partial"
  else
    resultCode = "DISPATCHED"
    resultMessage = string.format(
      "Created %d Test Raid Items and dispatched them to %d joined member%s. Awaiting receipts.",
      #itemDefs,
      sentCount,
      sentCount == 1 and "" or "s"
    )
    dispatchState = "awaiting_receipts"
  end

  RecordRaidTraceEvent("TEST_ITEMS_BOARD_READY", resultCode, {
    run = testRunId,
    boardId = TrimText(dispatchDetail.boardId ~= nil and dispatchDetail.boardId or RuntimeState.boardId),
    items = #itemDefs,
    targets = attemptedCount,
    sent = sentCount,
    failed = failedCount,
    dispatch = TrimText(dispatchState),
    itemWaitMs = math.max(0, tonumber(preparedRequest and preparedRequest.itemLoadWaitMs) or 0),
  })
  return FinishRealSimulationResult(resultOk, resultCode, resultMessage, {
    sessionId = TrimText(activeSession.id),
    profileName = TrimText(activeProfileName),
    boardId = TrimText(dispatchDetail.boardId ~= nil and dispatchDetail.boardId or RuntimeState.boardId),
    itemCount = #itemDefs,
    source = sourceText,
    popupOpened = popupOpened == true,
    dispatchAttempted = attemptedCount > 0,
    dispatchOk = dispatchOk == true,
    dispatchState = dispatchState,
    attemptedTargetCount = attemptedCount,
    dispatchedTargetCount = sentCount,
    failedTargetCount = failedCount,
    pendingReceiptCount = sentCount,
    acknowledgedCount = 0,
    droppedRows = math.max(0, tonumber(dispatchDetail.droppedRows) or 0),
    hadTruncation = dispatchDetail.hadTruncation == true,
    dispatchTargets = dispatchTargets,
    statusText = resultMessage,
    testRunId = testRunId,
    itemLoadWaitMs = math.max(0, tonumber(preparedRequest and preparedRequest.itemLoadWaitMs) or 0),
  })
end

local function RunLiveLootTransportTest(profileName)
  local activeSession, activeProfileName = GetActiveLootSession()
  local function Finish(ok, code, message, detail)
    local result = type(detail) == "table" and detail or {}
    result.ok = ok == true
    result.code = TrimText(code)
    result.message = TrimText(message)
    result.statusText = result.message
    result.sessionId = TrimText(result.sessionId ~= nil and result.sessionId or (type(activeSession) == "table" and activeSession.id or ""))
    result.profileName = TrimText(result.profileName ~= nil and result.profileName or activeProfileName)
    result.updatedAt = (type(GetServerTime) == "function" and GetServerTime()) or (time and time() or 0)
    RuntimeState.lastLiveTransportTest = DeepCopy(result)
    if type(Print) == "function" and result.message ~= "" then
      Print(result.message)
    end
    return result
  end

  if type(activeSession) ~= "table" or TrimText(activeProfileName) == "" then
    return Finish(false, "NO_ACTIVE_SESSION", "Start a session before running the live transport test.")
  end
  if string.upper(TrimText(activeSession.status or "")) ~= "ACTIVE" then
    return Finish(false, "SESSION_NOT_ACTIVE", "Start the prepared session before running the live transport test.")
  end
  if TrimText(profileName) ~= "" and TrimText(profileName) ~= TrimText(activeProfileName) then
    return Finish(false, "PROFILE_MISMATCH", "The live transport test only works for the active session profile.")
  end
  if not IsSelfHost(activeSession) then
    return Finish(false, "NOT_SESSION_HOST", "Only the active session host can run the live transport test.")
  end
  if type(InCombatLockdown) == "function" and InCombatLockdown() then
    return Finish(false, "COMBAT_LOCKDOWN", "The live transport test is unavailable during combat.")
  end
  if not EnsureRuntimeSession(activeSession, activeProfileName) then
    return Finish(false, "RUNTIME_UNAVAILABLE", "Could not prepare the active session runtime.")
  end
  if CountNonSimulationRuntimeItems() > 0 then
    return Finish(false, "LIVE_ITEMS_PRESENT", "Finish or clear current live items before running the live transport test.")
  end

  ClearRealSimulationRuntimeItems()
  local itemDefs, sourceLabel = BuildRealSimulationItemDefs(activeProfileName, 1)
  local itemDef = type(itemDefs) == "table" and itemDefs[1] or nil
  if type(itemDef) ~= "table" then
    return Finish(false, "NO_SIMULATION_ITEM", "No simulation item is available for the live transport test.")
  end
  local openTransportItem = ResolveOptionalFunction("LIDCOUNCIL_OpenTransportSimulationItemFlow")
  if type(openTransportItem) ~= "function" then
    return Finish(false, "TRANSPORT_API_UNAVAILABLE", "The live ITEM_OPEN transport test is unavailable.")
  end

  local ok, itemKey, detail = openTransportItem(activeSession, activeProfileName, itemDef)
  detail = type(detail) == "table" and detail or {}
  detail.source = TrimText(sourceLabel)
  detail.itemKey = TrimText(detail.itemKey ~= nil and detail.itemKey or itemKey)
  if ok ~= true then
    return Finish(false, detail.code or "DISPATCH_FAILED", detail.message or "The live ITEM_OPEN transport test failed.", detail)
  end

  local frame = createBoardFrame()
  SetBoardPreviewState(frame, nil)
  if not frame.__embeddedParent then
    applyPopupBoardLayout(frame)
  end
  frame:Show()
  frame:RefreshBoard()
  setBoardCollapsed(frame, false)
  return Finish(true, detail.code or "DISPATCHED", detail.message or "Live ITEM_OPEN transport test dispatched.", detail)
end

RefreshChoicePopup = function()
  if type(RefreshHeaderPendingRollIndicator) == "function" then
    RefreshHeaderPendingRollIndicator()
  end
  local joined, activeSession, profileName = ResolveActivePopupSessionState()
  if not joined or not activeSession then
    RecordChoicePopupState(activeSession, profileName, "blocked", "not_joined_or_session_missing", "")
    local frame = CreateChoicePopup()
    frame:Hide()
    return false
  end
  if activeSession.sharedProfileReady == false then
    RecordChoicePopupState(activeSession, profileName, "blocked", "p1_profile_not_ready", "")
    if not activeSession.__sharedProfilePendingNoticeShown and type(Print) == "function" then
      Print(
        "Waiting for the session host's bootstrap copy before LidCouncil roll choices can open."
        .. " Remaining shared history can keep syncing afterward."
      )
      activeSession.__sharedProfilePendingNoticeShown = true
    end
    local frame = CreateChoicePopup()
    frame:Hide()
    return false
  end
  activeSession.__sharedProfilePendingNoticeShown = nil
  return ShowCurrentChoicePopup(activeSession, profileName)
end

-- The header only needs the local player's unanswered choices. Keep this
-- derived from the popup queue so it neither creates traffic nor changes the
-- host-authoritative choice flow.
local function GetPendingRollChoiceStatus()
  local joined, activeSession = ResolveActivePopupSessionState()
  if not joined or type(activeSession) ~= "table" then
    return { pendingCount = 0 }
  end

  local selfKey = ResolvePopupSelfMemberKey(activeSession)
  if selfKey == "" then
    return { pendingCount = 0 }
  end

  local pendingCount = 0
  for _, itemKey in ipairs(RuntimeState.popupQueue or {}) do
    local entry = RuntimeState.items[itemKey]
    local selection = ResolvePopupEntrySelection(entry, selfKey)
    if type(entry) == "table"
      and (type(selection) ~= "table" or string.lower(tostring(selection.optionKey or "pending")) == "pending")
    then
      pendingCount = pendingCount + 1
    end
  end

  return {
    pendingCount = pendingCount,
    hasPendingChoice = pendingCount > 0,
  }
end

RefreshHeaderPendingRollIndicator = function()
  -- The editor's normal Live Board refresh does not necessarily repaint the
  -- header. Update this small local-only cue immediately, except in combat
  -- where the existing deferred editor refresh applies it after combat.
  if type(InCombatLockdown) == "function" and InCombatLockdown() then
    return
  end
  local editor = rawget(_G, "LIDCOUNCIL_Editor")
  if not (editor and editor.__lidCouncilEditor and type(editor.SetHeaderRollPendingIndicator) == "function") then
    return
  end
  local status = GetPendingRollChoiceStatus()
  editor:SetHeaderRollPendingIndicator(tonumber(status.pendingCount) or 0)
end

INTERNAL_API.LIDCOUNCIL_OpenSimulationChoicePopup_Impl = OpenSimulationChoicePopup
INTERNAL_API.LIDCOUNCIL_PreviewRollChoicePopup_Impl = PreviewRollChoicePopup
INTERNAL_API.LIDCOUNCIL_RunLiveLootSimulation_Impl = RunLiveLootSimulation
INTERNAL_API.LIDCOUNCIL_RunLiveLootRealSimulation_Impl = RunLiveLootRealSimulation
INTERNAL_API.LIDCOUNCIL_RunLiveLootTransportTest_Impl = RunLiveLootTransportTest
INTERNAL_API.LIDCOUNCIL_RefreshChoicePopup_Impl = RefreshChoicePopup
INTERNAL_API.LIDCOUNCIL_RearmUnconfirmedChoiceReceipts_Impl = RearmUnconfirmedChoiceReceipts
INTERNAL_API.LIDCOUNCIL_GetPendingRollChoiceStatus_Impl = GetPendingRollChoiceStatus
INTERNAL_API.LIDCOUNCIL_RunChoiceReceiptQueueRecoveryValidation_Impl = choiceReceiptRecovery.RunValidation
INTERNAL_API.LIDCOUNCIL_RunRealSimulationItemPreparationValidation_Impl = RunRealSimulationItemPreparationValidation
