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_LootRuntimeShared_Impl
if type(shared) ~= "table" then
  error("LidCouncil missing loot runtime shared helpers.")
end

local ResolveOptionalFunction = shared.ResolveOptionalFunction
local Print = shared.Print
local DeepCopy = shared.DeepCopy
local GetLootProfile = shared.GetLootProfile
local AddLootLedgerEntry = shared.AddLootLedgerEntry
local ResolveLootPointValue = shared.ResolveLootPointValue
local ResolveLootAwardOutcome = shared.ResolveLootAwardOutcome
local DEFAULT_OPTIONS = shared.DEFAULT_OPTIONS
local pendingColor = shared.pendingColor
local SIMULATION_ITEM_COUNT = shared.SIMULATION_ITEM_COUNT
local SIMULATION_SAMPLE_MEMBERS = shared.SIMULATION_SAMPLE_MEMBERS
local TrimText = shared.TrimText
local ExtractItemID = shared.ExtractItemID
local ExtractItemLinkName = shared.ExtractItemLinkName
local GetFirstDetailedItemLevel = shared.GetFirstDetailedItemLevel
local ResolveOptionIconTexture = shared.ResolveOptionIconTexture
local BuildSelectionState = shared.BuildSelectionState
local BuildPendingSelection = shared.BuildPendingSelection
local EnsureSimulationProfile = shared.EnsureSimulationProfile
local ResolveRollOptions = shared.ResolveRollOptions
local GetSelfKey = shared.GetSelfKey
local BuildNormalizedCharacterKey = shared.BuildNormalizedCharacterKey
local BuildInlineIconTag = shared.BuildInlineIconTag
local BuildMemberDisplayText = shared.BuildMemberDisplayText
local NormalizeCharacterKey = shared.NormalizeCharacterKey
local MarkAwardOnEntry = shared.MarkAwardOnEntry

local function BuildPreviewBoardState(profileName)
  local profile, resolvedProfileName = GetLootProfile(profileName)
  local options = ResolveRollOptions(nil, resolvedProfileName)
  local hostKey = GetSelfKey()
  if hostKey == "" then
    hostKey = "You-PreviewRealm"
  end

  if type(options) ~= "table" or #options == 0 then
    options = DEFAULT_OPTIONS
  end

  local function ResolvePreviewRollValue(option)
    local key = string.lower(TrimText(type(option) == "table" and (option.key or option.label) or ""))
    local behavior = string.upper(TrimText(type(option) == "table" and option.pointBehavior or ""))
    if key == "pass" or behavior == "NONE" then
      return nil
    end
    if type(math) == "table" and type(math.random) == "function" then
      return math.random(1, 100)
    end
    return 50
  end

  local function BuildPreviewSelection(option)
    option = type(option) == "table" and option or DEFAULT_OPTIONS[1]
    return BuildSelectionState(
      option.key,
      option.label,
      option.color,
      ResolvePreviewRollValue(option),
      option.icon,
      nil,
      option.pointBehavior
    )
  end

  local previewPointsA = tonumber((ResolveLootPointValue(resolvedProfileName, "Sample Raid Item", 308, nil))) or 2
  local previewPointsB = tonumber((ResolveLootPointValue(resolvedProfileName, "Sample Raid Item", 305, nil))) or 1
  local memberNames = {
    "RaiderOne-PreviewRealm",
    "RaiderTwo-PreviewRealm",
    "RaiderThree-PreviewRealm",
    "RaiderFour-PreviewRealm",
    "RaiderFive-PreviewRealm",
    "RaiderSix-PreviewRealm",
    "RaiderSeven-PreviewRealm",
    "RaiderEight-PreviewRealm",
    "RaiderNine-PreviewRealm",
    "RaiderTen-PreviewRealm",
  }
  local memberCount = math.max(4, #options)
  local members = {
    { key = hostKey, name = hostKey, isHost = true, label = "Pending", color = pendingColor },
  }
  local memberOptionByKey = {}
  for index = 1, memberCount do
    local option = options[((index - 1) % #options) + 1] or DEFAULT_OPTIONS[1]
    local memberKey = memberNames[index] or ("Raider" .. tostring(index) .. "-PreviewRealm")
    memberOptionByKey[memberKey] = option
    members[#members + 1] = {
      key = memberKey,
      name = memberKey,
      label = tostring(option.label or option.key or "Roll"),
      color = DeepCopy(option.color or { 0.82, 0.82, 0.86 }),
    }
  end

  local function BuildPreviewSelections(offset)
    local selections = {
      [hostKey] = BuildPendingSelection(),
    }
    for index = 1, memberCount do
      local memberKey = memberNames[index] or ("Raider" .. tostring(index) .. "-PreviewRealm")
      local option = options[((index - 1 + (tonumber(offset) or 0)) % #options) + 1] or memberOptionByKey[memberKey] or DEFAULT_OPTIONS[1]
      selections[memberKey] = BuildPreviewSelection(option)
    end
    return selections
  end

  local preview = {
    profileName = tostring(resolvedProfileName or "Default"),
    summaryLabel = "Preview",
    itemOrder = { "PREVIEW-1", "PREVIEW-2" },
    items = {},
    members = members,
    selectedItemKey = "PREVIEW-1",
    selectedMemberKey = "RaiderOne-PreviewRealm",
  }

  preview.items["PREVIEW-1"] = {
    key = "PREVIEW-1",
    name = "Sample Raid Item",
    icon = "Interface\\Icons\\INV_Misc_QuestionMark",
    itemLevel = 308,
    projectedPoints = previewPointsA,
    selections = BuildPreviewSelections(0),
  }

  preview.items["PREVIEW-2"] = {
    key = "PREVIEW-2",
    name = "Already Awarded Example",
    icon = "Interface\\Icons\\INV_Misc_QuestionMark",
    itemLevel = 305,
    projectedPoints = previewPointsB,
    awardedTo = "RaiderTwo-PreviewRealm",
    awardedPoints = previewPointsB,
    awardedAt = time and time() or 0,
    selections = BuildPreviewSelections(1),
  }

  return preview
end

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

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

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

  if name == "" and type(link) == "string" then
    name = ExtractItemLinkName(link)
  end

  local itemInfoSource = link or itemID
  if itemInfoSource and type(GetItemInfo) == "function" then
    local itemName, itemLink, itemQuality, resolvedItemLevel, _, _, _, _, _, itemIcon = GetItemInfo(itemInfoSource)
    if name == "" then
      name = TrimText(itemName or "")
    end
    if not link or link == "" then
      link = itemLink
    end
    if not icon then
      icon = itemIcon
    end
    if not quality then
      quality = tonumber(itemQuality)
    end
    if not itemLevel then
      itemLevel = tonumber(resolvedItemLevel)
    end
  end

  if not itemLevel and type(link) == "string" and link ~= "" then
    itemLevel = GetFirstDetailedItemLevel(link)
  end

  if not itemID and name == "" then
    return nil
  end

  return {
    itemID = itemID,
    name = name ~= "" and name or ("Item #" .. tostring(itemID or "?")),
    icon = icon or "Interface\\Icons\\INV_Misc_QuestionMark",
    slot = slot,
    link = link,
    quality = quality,
    itemLevel = itemLevel,
  }
end

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

local function CollectSimulationRaidEntriesForTier(tierIndex)
  local raids = {}
  if not EnsureSimulationEncounterJournalLoaded() then
    return raids
  end

  local resolvedTier = tonumber(tierIndex)
  if not resolvedTier then
    return raids
  end

  pcall(EJ_SelectTier, resolvedTier)
  local instanceIndex = 1
  while true do
    local ok, r1, r2, r3 = pcall(EJ_GetInstanceByIndex, instanceIndex, true)
    if not ok or r1 == nil then
      break
    end
    local instanceID, instanceName = NormalizeSimulationInstanceTuple(r1, r2, r3)
    if instanceID then
      raids[#raids + 1] = {
        instanceID = instanceID,
        name = instanceName ~= "" and instanceName or ("Raid " .. tostring(instanceID)),
      }
    end
    instanceIndex = instanceIndex + 1
  end
  return raids
end

local function CollectSimulationRaidLootEntries(instanceID)
  local resolvedInstanceID = tonumber(instanceID)
  if not resolvedInstanceID or not EnsureSimulationEncounterJournalLoaded() then
    return {}
  end

  local items = {}
  local seen = {}
  local getLootInfo = type(C_EncounterJournal) == "table" and type(C_EncounterJournal.GetLootInfoByIndex) == "function"
    and C_EncounterJournal.GetLootInfoByIndex
    or nil
  local currentInstance = type(EJ_GetCurrentInstance) == "function" and tonumber(EJ_GetCurrentInstance()) or nil

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

  local encounterIndex = 1
  while true do
    local ok, e1, e2, e3, e4, e5, e6, e7 = pcall(EJ_GetEncounterInfoByIndex, encounterIndex, resolvedInstanceID)
    if not ok or e1 == nil then
      break
    end

    local encounterID, encounterName = NormalizeSimulationEncounterTuple(e1, e2, e3, e4, e5, e6, e7)
    if encounterID then
      if type(EJ_SelectEncounter) == "function" then
        pcall(EJ_SelectEncounter, encounterID)
      end

      local lootIndex = 1
      while true do
        local lootInfo
        if getLootInfo then
          local infoOk, raw = pcall(getLootInfo, lootIndex)
          if infoOk then
            lootInfo = NormalizeSimulationLootInfo(raw)
          end
          if not lootInfo then
            infoOk, raw = pcall(getLootInfo, lootIndex, encounterID)
            if infoOk then
              lootInfo = NormalizeSimulationLootInfo(raw)
            end
          end
        elseif type(EJ_GetLootInfoByIndex) == "function" then
          local infoOk, l1, l2, l3, l4, l5, l6, l7, l8 = pcall(EJ_GetLootInfoByIndex, lootIndex)
          if infoOk then
            lootInfo = NormalizeSimulationLootInfo(l1, l2, l3, l4, l5, l6, l7, l8)
          end
          if not lootInfo then
            infoOk, l1, l2, l3, l4, l5, l6, l7, l8 = pcall(EJ_GetLootInfoByIndex, lootIndex, encounterID)
            if infoOk then
              lootInfo = NormalizeSimulationLootInfo(l1, l2, l3, l4, l5, l6, l7, l8)
            end
          end
        end

        if not lootInfo then
          break
        end

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

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

  return items
end

local function GetProfileSeasonSnapshot(profileName)
  local sourceProfile = GetLootProfile(profileName)
  local snapshot = type(sourceProfile) == "table"
    and type(sourceProfile.settings) == "table"
    and type(sourceProfile.settings.seasonSnapshot) == "table"
    and sourceProfile.settings.seasonSnapshot
    or nil
  if type(snapshot) ~= "table" or not tonumber(snapshot.tier) or type(snapshot.raids) ~= "table" or #snapshot.raids == 0 then
    return nil
  end
  return snapshot
end

local function CollectProfileSeasonRaidEntries(profileName)
  local snapshot = GetProfileSeasonSnapshot(profileName)
  if not snapshot then
    return nil
  end

  local raids = {}
  local tier = tonumber(snapshot.tier)
  local tierName = TrimText(snapshot.tierName) ~= "" and tostring(snapshot.tierName) or "Profile Season"
  for _, raid in ipairs(snapshot.raids or {}) do
    local instanceID = tonumber(raid.instanceID)
    if instanceID then
      raids[#raids + 1] = {
        instanceID = instanceID,
        name = tostring(raid.name or instanceID),
        tier = tier,
        tierName = tierName,
      }
    end
  end
  table.sort(raids, function(a, b)
    return string.lower(tostring(a.name or "")) < string.lower(tostring(b.name or ""))
  end)
  return raids
end

local function IsRaidInProfileSeason(profileName, instanceID)
  local snapshot = GetProfileSeasonSnapshot(profileName)
  if not snapshot then
    return true
  end
  local resolvedInstanceID = tonumber(instanceID)
  if not resolvedInstanceID then
    return false
  end
  for _, raid in ipairs(snapshot.raids or {}) do
    if tonumber(raid.instanceID) == resolvedInstanceID then
      return true
    end
  end
  return false
end

local function CollectSimulationSeasonLootEntries(profileName)
  if not EnsureSimulationEncounterJournalLoaded() then
    return {}, nil
  end

  local originalTier = type(EJ_GetCurrentTier) == "function" and tonumber(EJ_GetCurrentTier()) or nil
  local raids = CollectProfileSeasonRaidEntries(profileName)
  if not raids then
    local currentTier = type(EJ_GetCurrentTier) == "function" and tonumber(EJ_GetCurrentTier()) or nil
    if not currentTier then
      currentTier = tonumber(EJ_GetNumTiers()) or nil
    end
    if not currentTier then
      return {}, nil
    end
    raids = CollectSimulationRaidEntriesForTier(currentTier)
  end
  local selectedRaidInstanceID
  local sourceProfile = GetLootProfile(profileName)
  if type(sourceProfile) == "table" and type(sourceProfile.settings) == "table" and type(sourceProfile.settings.raid) == "table" then
    selectedRaidInstanceID = tonumber(sourceProfile.settings.raid.instanceID)
  end

  local lootPool = {}
  local seen = {}
  local selectedPool = {}
  local selectedSeen = {}
  for _, raid in ipairs(raids) do
    for _, item in ipairs(CollectSimulationRaidLootEntries(raid.instanceID)) do
      local dedupeKey = item.itemID and ("id:" .. tostring(item.itemID)) or string.lower(tostring(item.name or item.link or ""))
      if dedupeKey ~= "" and not seen[dedupeKey] then
        local cloned = DeepCopy(item)
        cloned.instanceID = tonumber(raid.instanceID)
        cloned.instanceName = tostring(raid.name or "")
        lootPool[#lootPool + 1] = cloned
        seen[dedupeKey] = true
      end
      if selectedRaidInstanceID and tonumber(raid.instanceID) == selectedRaidInstanceID and dedupeKey ~= "" and not selectedSeen[dedupeKey] then
        local selectedClone = DeepCopy(item)
        selectedClone.instanceID = tonumber(raid.instanceID)
        selectedClone.instanceName = tostring(raid.name or "")
        selectedPool[#selectedPool + 1] = selectedClone
        selectedSeen[dedupeKey] = true
      end
    end
  end

  if originalTier and type(EJ_SelectTier) == "function" then
    pcall(EJ_SelectTier, originalTier)
  end

  if #selectedPool > 0 then
    return selectedPool, selectedRaidInstanceID
  end
  return lootPool, nil
end

local function CollectConfiguredProfileRaidLootEntries(profileName)
  local sourceProfile = GetLootProfile(profileName)
  local configuredRaid = type(sourceProfile) == "table"
    and type(sourceProfile.settings) == "table"
    and type(sourceProfile.settings.raid) == "table"
    and sourceProfile.settings.raid
    or nil
  local configuredInstanceID = tonumber(type(configuredRaid) == "table" and configuredRaid.instanceID)
  if not configuredInstanceID then
    return {}, nil, nil
  end
  if not IsRaidInProfileSeason(profileName, configuredInstanceID) then
    return {}, nil, nil
  end

  local configuredRaidName = TrimText(configuredRaid.name or "")
  local items = {}
  for _, item in ipairs(CollectSimulationRaidLootEntries(configuredInstanceID)) do
    local cloned = DeepCopy(item)
    cloned.instanceID = configuredInstanceID
    cloned.instanceName = configuredRaidName ~= "" and configuredRaidName or TrimText(item.instanceName or "")
    items[#items + 1] = cloned
  end
  return items, configuredRaidName, configuredInstanceID
end

local function FindSimulationRaidEntryByName(raidName)
  local needle = string.lower(TrimText(raidName))
  if needle == "" or not EnsureSimulationEncounterJournalLoaded() then
    return nil
  end

  local originalTier = type(EJ_GetCurrentTier) == "function" and tonumber(EJ_GetCurrentTier()) or nil
  local tierCount = tonumber(type(EJ_GetNumTiers) == "function" and EJ_GetNumTiers()) or 0
  for tierIndex = 1, tierCount do
    for _, raid in ipairs(CollectSimulationRaidEntriesForTier(tierIndex)) do
      if string.lower(TrimText(raid.name or "")) == needle then
        if originalTier and type(EJ_SelectTier) == "function" then
          pcall(EJ_SelectTier, originalTier)
        end
        return raid
      end
    end
  end

  if originalTier and type(EJ_SelectTier) == "function" then
    pcall(EJ_SelectTier, originalTier)
  end
  return nil
end

local function CollectCurrentRaidSimulationLootEntries(profileName)
  if type(GetInstanceInfo) ~= "function" then
    return {}, nil
  end

  local instanceName, instanceType = GetInstanceInfo()
  if tostring(instanceType or "") ~= "raid" then
    return {}, nil
  end

  local resolvedInstanceName = TrimText(instanceName)
  if resolvedInstanceName == "" then
    return {}, nil
  end

  local raidEntry = FindSimulationRaidEntryByName(resolvedInstanceName)
  if type(raidEntry) ~= "table" or not tonumber(raidEntry.instanceID) then
    return {}, resolvedInstanceName
  end
  if not IsRaidInProfileSeason(profileName, raidEntry.instanceID) then
    return {}, resolvedInstanceName
  end

  local items = {}
  for _, item in ipairs(CollectSimulationRaidLootEntries(raidEntry.instanceID)) do
    local cloned = DeepCopy(item)
    cloned.instanceID = tonumber(raidEntry.instanceID)
    cloned.instanceName = resolvedInstanceName
    items[#items + 1] = cloned
  end
  return items, resolvedInstanceName
end

local function ShuffleSimulationItems(sourceItems)
  for index = #sourceItems, 2, -1 do
    local swapIndex = type(math) == "table" and type(math.random) == "function" and math.random(1, index) or index
    sourceItems[index], sourceItems[swapIndex] = sourceItems[swapIndex], sourceItems[index]
  end
end

local function BuildSimulationItemDefsFromSource(sourceItems, maxItems, keyPrefix)
  local itemDefs = {}
  local resolvedMaxItems = math.max(1, math.floor(tonumber(maxItems) or #sourceItems))
  local total = math.min(resolvedMaxItems, #sourceItems)
  for index = 1, total do
    local item = sourceItems[index]
    itemDefs[#itemDefs + 1] = {
      key = tostring(keyPrefix or "SIM-") .. tostring(index),
      name = tostring(item.name or ("Simulation Drop " .. tostring(index))),
      link = item.link,
      icon = item.icon or "Interface\\Icons\\INV_Misc_QuestionMark",
      itemLevel = tonumber(item.itemLevel),
      itemID = ExtractItemID(item.itemID or item.link),
      quality = tonumber(item.quality),
      encounterName = TrimText(item.encounterName or ""),
      instanceName = TrimText(item.instanceName or ""),
      instanceID = tonumber(item.instanceID),
    }
  end
  return itemDefs
end

local function BuildRandomSimulationItemDefs(profileName)
  local sourceItems = {}
  local currentSeasonItems = CollectSimulationSeasonLootEntries(profileName)
  for _, item in ipairs(currentSeasonItems or {}) do
    sourceItems[#sourceItems + 1] = item
  end

  if #sourceItems == 0 then
    return {
      { key = "SIM-1", name = "Simulation Drop 250", itemLevel = 250, icon = "Interface\\Icons\\INV_Misc_QuestionMark" },
      { key = "SIM-2", name = "Simulation Drop 253", itemLevel = 253, icon = "Interface\\Icons\\INV_Misc_QuestionMark" },
      { key = "SIM-3", name = "Simulation Drop 256", itemLevel = 256, icon = "Interface\\Icons\\INV_Misc_QuestionMark" },
      { key = "SIM-4", name = "Simulation Drop 259", itemLevel = 259, icon = "Interface\\Icons\\INV_Misc_QuestionMark" },
      { key = "SIM-5", name = "Simulation Drop 263", itemLevel = 263, icon = "Interface\\Icons\\INV_Misc_QuestionMark" },
      { key = "SIM-6", name = "Simulation Drop 266", itemLevel = 266, icon = "Interface\\Icons\\INV_Misc_QuestionMark" },
      { key = "SIM-7", name = "Simulation Drop 269", itemLevel = 269, icon = "Interface\\Icons\\INV_Misc_QuestionMark" },
      { key = "SIM-8", name = "Simulation Drop 272", itemLevel = 272, icon = "Interface\\Icons\\INV_Misc_QuestionMark" },
      { key = "SIM-9", name = "Simulation Drop 276", itemLevel = 276, icon = "Interface\\Icons\\INV_Misc_QuestionMark" },
      { key = "SIM-10", name = "Simulation Drop 279", itemLevel = 279, icon = "Interface\\Icons\\INV_Misc_QuestionMark" },
    }
  end

  ShuffleSimulationItems(sourceItems)
  return BuildSimulationItemDefsFromSource(sourceItems, SIMULATION_ITEM_COUNT, "SIM-")
end

local function BuildRealSimulationItemDefs(profileName, count)
  local resolvedCount = math.max(1, math.floor(tonumber(count) or 5))
  local sourceItems, sourceLabel = CollectCurrentRaidSimulationLootEntries(profileName)
  if #sourceItems == 0 then
    local configuredRaidItems, configuredRaidName, configuredRaidInstanceID = CollectConfiguredProfileRaidLootEntries(profileName)
    for _, item in ipairs(configuredRaidItems or {}) do
      sourceItems[#sourceItems + 1] = item
    end
    if #sourceItems > 0 then
      if TrimText(configuredRaidName) ~= "" then
        sourceLabel = configuredRaidName
      elseif tonumber(configuredRaidInstanceID) then
        sourceLabel = "selected profile raid"
      end
    end
  end

  if #sourceItems == 0 then
    local currentSeasonItems, selectedRaidInstanceID = CollectSimulationSeasonLootEntries(profileName)
    for _, item in ipairs(currentSeasonItems or {}) do
      sourceItems[#sourceItems + 1] = item
    end

    local sourceProfile = GetLootProfile(profileName)
    local configuredRaid = type(sourceProfile) == "table"
      and type(sourceProfile.settings) == "table"
      and type(sourceProfile.settings.raid) == "table"
      and sourceProfile.settings.raid
      or nil
    local configuredRaidName = TrimText(type(configuredRaid) == "table" and configuredRaid.name or "")
    if #sourceItems > 0 then
      if configuredRaidName ~= "" then
        sourceLabel = configuredRaidName
      elseif tonumber(selectedRaidInstanceID) then
        sourceLabel = "selected profile raid"
      else
        sourceLabel = "profile season raid pool"
      end
    end
  end

  if #sourceItems > 0 then
    ShuffleSimulationItems(sourceItems)
  end

  local resolvedDefs = BuildSimulationItemDefsFromSource(sourceItems, resolvedCount, "SIM-REAL-")
  for _, itemDef in ipairs(resolvedDefs) do
    itemDef.instanceName = TrimText(itemDef.instanceName ~= "" and itemDef.instanceName or sourceLabel or "Raid Simulation")
    itemDef.encounterName = TrimText(itemDef.encounterName ~= "" and itemDef.encounterName or "Raid Encounter")
  end
  while #resolvedDefs < resolvedCount do
    local fallbackIndex = #resolvedDefs + 1
    resolvedDefs[#resolvedDefs + 1] = {
      key = "SIM-REAL-" .. tostring(fallbackIndex),
      name = "Simulation Item " .. tostring(240 + (fallbackIndex * 3)),
      icon = "Interface\\Icons\\INV_Misc_QuestionMark",
      itemLevel = 240 + (fallbackIndex * 3),
      instanceName = TrimText(sourceLabel or "") ~= "" and tostring(sourceLabel) or "Raid Simulation",
      encounterName = "Raid Encounter",
    }
  end
  return resolvedDefs, sourceLabel
end

local function BuildSimulationMembers(profileName, selfKey)
  local members = {}
  local memberInfoByKey = {}
  local seen = {}

  local function upsertMember(source, forceHost)
    if type(source) ~= "table" then
      return
    end
    local resolvedKey = BuildNormalizedCharacterKey(source.key or source.character or source.name, source.realm)
    if resolvedKey == "" then
      return
    end

    local existing = memberInfoByKey[resolvedKey]
    if not existing then
      existing = {
        key = resolvedKey,
        name = TrimText(source.name or source.character or resolvedKey),
        className = TrimText(source.className),
        classFile = TrimText(source.classFile),
        specName = TrimText(source.specName),
        specIcon = TrimText(source.specIcon),
        note = TrimText(source.note),
        isHost = false,
      }
      if existing.name == "" then
        existing.name = resolvedKey
      end
      members[#members + 1] = existing
      memberInfoByKey[resolvedKey] = existing
      seen[resolvedKey] = true
    else
      if existing.className == "" then existing.className = TrimText(source.className) end
      if existing.classFile == "" then existing.classFile = TrimText(source.classFile) end
      if existing.specName == "" then existing.specName = TrimText(source.specName) end
      if existing.specIcon == "" then existing.specIcon = TrimText(source.specIcon) end
      if existing.note == "" then existing.note = TrimText(source.note) end
    end

    if forceHost then
      existing.isHost = true
    end
  end

  local getLootLedgerCharacters = ResolveOptionalFunction("LIDCOUNCIL_GetLootLedgerCharacters")
  if type(getLootLedgerCharacters) == "function" then
    for _, row in ipairs(getLootLedgerCharacters(profileName) or {}) do
      upsertMember({
        key = row.character,
        name = row.character,
        className = row.className,
        classFile = row.classFile,
        specName = row.specName,
        specIcon = row.specIcon,
        note = row.note,
      }, false)
    end
  else
    local sourceProfile = GetLootProfile(profileName)
    for key, meta in pairs(type(sourceProfile) == "table" and (sourceProfile.lootLedgerMeta or {}) or {}) do
      upsertMember({
        key = key,
        name = key,
        className = type(meta) == "table" and meta.className or "",
        classFile = type(meta) == "table" and meta.classFile or "",
        specName = type(meta) == "table" and meta.specName or "",
        specIcon = type(meta) == "table" and meta.specIcon or "",
        note = type(meta) == "table" and meta.note or "",
      }, false)
    end
  end

  if selfKey ~= "" then
    upsertMember({
      key = selfKey,
      name = selfKey,
    }, true)
  end

  if #members == 0 then
    for index, sample in ipairs(SIMULATION_SAMPLE_MEMBERS) do
      upsertMember(sample, index == 1 and selfKey == "")
    end
  elseif #members < 4 then
    for _, sample in ipairs(SIMULATION_SAMPLE_MEMBERS) do
      upsertMember(sample, false)
      if #members >= 4 then
        break
      end
    end
  end

  if selfKey == "" and members[1] then
    selfKey = members[1].key
    members[1].isHost = true
  end

  table.sort(members, function(a, b)
    if (a.isHost and true or false) ~= (b.isHost and true or false) then
      return a.isHost and true or false
    end
    return string.lower(tostring(a.name or a.key or "")) < string.lower(tostring(b.name or b.key or ""))
  end)

  while #members > 10 do
    local removed = table.remove(members)
    if removed and removed.key and not (removed.isHost and selfKey == removed.key) then
      memberInfoByKey[removed.key] = nil
    end
  end

  return members, memberInfoByKey, selfKey
end

local function EnsureSimulationMemberMeta(preview, memberKey)
  if type(preview) ~= "table" then
    return
  end

  local resolvedKey = NormalizeCharacterKey(memberKey)
  if resolvedKey == "" then
    return
  end

  local profile = GetLootProfile(preview.profileName or "Simulation")
  profile.lootLedgerMeta = profile.lootLedgerMeta or {}
  local meta = type(profile.lootLedgerMeta[resolvedKey]) == "table" and profile.lootLedgerMeta[resolvedKey] or {}
  local member = type(preview.memberInfoByKey) == "table" and preview.memberInfoByKey[resolvedKey] or nil
  meta.penaltyPoints = tonumber(meta.penaltyPoints) or 0
  if type(member) == "table" then
    if TrimText(member.className) ~= "" then meta.className = TrimText(member.className) end
    if TrimText(member.classFile) ~= "" then meta.classFile = TrimText(member.classFile) end
    if TrimText(member.specName) ~= "" then meta.specName = TrimText(member.specName) end
    if TrimText(member.specIcon) ~= "" then meta.specIcon = TrimText(member.specIcon) end
    if TrimText(member.note) ~= "" then meta.note = TrimText(member.note) end
  end
  profile.lootLedgerMeta[resolvedKey] = meta
end

local function BuildSimulationBoardState(profileName)
  local _, simulationProfileName, resolvedSourceProfileName, simulationError = EnsureSimulationProfile(profileName)
  if not simulationProfileName then
    return nil, simulationError or "Simulation profile unavailable."
  end
  local selfKey = GetSelfKey()
  if selfKey == "" then
    selfKey = ""
  end
  local members, memberInfoByKey, resolvedSelfKey = BuildSimulationMembers(resolvedSourceProfileName, selfKey)
  selfKey = resolvedSelfKey ~= "" and resolvedSelfKey or (members[1] and members[1].key or "You-PreviewRealm")

  local function BuildPendingSelections()
    local selections = {}
    for _, member in ipairs(members) do
      selections[member.key] = BuildPendingSelection()
    end
    return selections
  end

  local itemDefs = BuildRandomSimulationItemDefs(resolvedSourceProfileName)

  local preview = {
    profileName = tostring(simulationProfileName or "Simulation"),
    sourceProfileName = tostring(resolvedSourceProfileName or profileName or "Default"),
    summaryLabel = "Simulation",
    isSimulation = true,
    selfKey = selfKey,
    members = members,
    memberInfoByKey = memberInfoByKey,
    itemOrder = {},
    popupQueue = {},
    totalItems = #itemDefs,
    items = {},
    selectedItemKey = itemDefs[1] and itemDefs[1].key or nil,
    selectedMemberKey = nil,
  }

  for _, itemDef in ipairs(itemDefs) do
    preview.itemOrder[#preview.itemOrder + 1] = itemDef.key
    preview.popupQueue[#preview.popupQueue + 1] = itemDef.key
    preview.items[itemDef.key] = {
      key = itemDef.key,
      name = itemDef.name,
      link = itemDef.link,
      icon = itemDef.icon or "Interface\\Icons\\INV_Misc_QuestionMark",
      quality = itemDef.quality,
      itemID = itemDef.itemID,
      itemLevel = tonumber(itemDef.itemLevel),
      encounterName = itemDef.encounterName,
      instanceName = itemDef.instanceName,
      projectedPoints = tonumber((ResolveLootPointValue(
        tostring(resolvedSourceProfileName or simulationProfileName or "Simulation"),
        itemDef.name,
        itemDef.itemLevel,
        itemDef.itemID,
        itemDef.link
      ))) or 0,
      selections = BuildPendingSelections(),
    }
  end

  return preview
end

local function GetSelectionConfiguredPriority(profileName, selection)
  if type(selection) ~= "table" then
    return nil
  end
  local key = string.lower(TrimText(selection.optionKey or selection.key or selection.label))
  local label = string.lower(TrimText(selection.label or selection.optionKey or selection.key))
  if key == "" or key == "pending" then
    return nil
  end
  local options = ResolveRollOptions(nil, profileName)
  for index, option in ipairs(options or {}) do
    if type(option) == "table" then
      local optionKey = string.lower(TrimText(option.key or option.label))
      local optionLabel = string.lower(TrimText(option.label or option.key))
      if (optionKey ~= "" and (optionKey == key or optionKey == label))
        or (optionLabel ~= "" and (optionLabel == key or optionLabel == label)) then
        return index
      end
    end
  end
  return nil
end

local function GetSelectionPriority(selection, profileName)
  local configuredPriority = GetSelectionConfiguredPriority(profileName, selection)
  if configuredPriority then
    return configuredPriority
  end
  local key = string.lower(TrimText(selection and (selection.optionKey or selection.label) or "pending"))
  if key == "need" then
    return 1
  elseif key == "greed" then
    return 2
  elseif key == "pass" then
    return 3
  elseif key == "pending" or key == "" then
    return 9999
  end
  return 9000
end

local function GetSelectionRollValue(selection)
  return tonumber(selection and selection.rollValue) or 0
end

local function FormatSelectionLabel(selection)
  local label = TrimText(selection and (selection.label or selection.optionKey) or "Pending")
  if label == "" then
    label = "Pending"
  end
  local lowerLabel = string.lower(label)
  local iconTexture = ResolveOptionIconTexture(selection and selection.icon, selection and selection.optionKey, label)
  local prefix = BuildInlineIconTag(iconTexture and { texture = iconTexture } or nil, 14)
  if prefix ~= "" then
    label = prefix .. " " .. label
  end
  local rollValue = tonumber(selection and selection.rollValue)
  if rollValue and lowerLabel ~= "pass" and lowerLabel ~= "pending" then
    return string.format("%s (%d)", label, rollValue)
  end
  return label
end

local function GetPreviewCandidateRows(preview, entry)
  local rows = {}
  if type(preview) ~= "table" or type(entry) ~= "table" then
    return rows
  end

  local priorityProfileName = TrimText(preview.sourceProfileName)
  if priorityProfileName == "" then
    priorityProfileName = TrimText(preview.profileName)
  end

  for _, member in ipairs(preview.members or {}) do
    local selection = entry.selections and entry.selections[member.key] or nil
    rows[#rows + 1] = {
      key = member.key,
      name = tostring(member.name or member.key or "?"),
      isHost = member.isHost and true or false,
      className = TrimText(member.className),
      classFile = TrimText(member.classFile),
      specName = TrimText(member.specName),
      specIcon = TrimText(member.specIcon),
      note = TrimText(member.note),
      selection = selection,
      sortPriority = GetSelectionPriority(selection, priorityProfileName),
    }
  end

  table.sort(rows, function(a, b)
    local pa = GetSelectionPriority(a.selection, priorityProfileName)
    local pb = GetSelectionPriority(b.selection, priorityProfileName)
    if pa == pb then
      local ra = GetSelectionRollValue(a.selection)
      local rb = GetSelectionRollValue(b.selection)
      if ra == rb then
        return string.lower(tostring(a.name or "")) < string.lower(tostring(b.name or ""))
      end
      return ra > rb
    end
    return pa < pb
  end)
  return rows
end

local function EnsurePreviewSelectedMember(preview, entry)
  local rows = GetPreviewCandidateRows(preview, entry)
  if #rows == 0 then
    if type(preview) == "table" then
      preview.selectedMemberKey = nil
    end
    return rows, nil
  end

  for _, row in ipairs(rows) do
    if row.key == preview.selectedMemberKey then
      return rows, row
    end
  end

  preview.selectedMemberKey = rows[1].key
  return rows, rows[1]
end

local function CyclePreviewSelectedMember(preview, entry, delta)
  local rows, current = EnsurePreviewSelectedMember(preview, entry)
  if #rows == 0 then
    preview.selectedMemberKey = nil
    return nil
  end
  local currentIndex = 1
  for i, row in ipairs(rows) do
    if current and row.key == current.key then
      currentIndex = i
      break
    end
  end
  local offset = tonumber(delta) or 0
  local nextIndex = currentIndex + offset
  while nextIndex < 1 do
    nextIndex = nextIndex + #rows
  end
  while nextIndex > #rows do
    nextIndex = nextIndex - #rows
  end
  preview.selectedMemberKey = rows[nextIndex].key
  return rows[nextIndex]
end

local function BuildSimulationRollOption(option)
  option = option or DEFAULT_OPTIONS[1]
  local selection = BuildSelectionState(option.key or option.label, option.label or option.key, option.color, nil, option.icon, option.note, option.pointBehavior)
  selection.icon = ResolveOptionIconTexture(option.icon, option.key, option.label)
  return selection
end

local function RollSimulationSelection(selection)
  local lower = string.lower(TrimText(selection and (selection.optionKey or selection.label) or "pending"))
  if lower == "pass" or lower == "pending" or lower == "" then
    selection.rollValue = nil
    return
  end
  if type(math) == "table" and type(math.random) == "function" then
    selection.rollValue = math.random(1, 100)
  else
    selection.rollValue = 50
  end
end

local function PrintSimulationRollResult(preview, entry)
  if type(Print) ~= "function" or type(entry) ~= "table" then
    return
  end

  local rows = GetPreviewCandidateRows(preview, entry)
  local parts = {}
  for _, row in ipairs(rows) do
    parts[#parts + 1] = string.format("%s %s", tostring(row.name), FormatSelectionLabel(row.selection))
  end
  Print(string.format("[Simulation] Rolls for \"%s\": %s", tostring(entry.name or entry.key), table.concat(parts, ", ")))
end

local function ApplySimulationAward(preview, entry, memberKey)
  if type(preview) ~= "table" or type(entry) ~= "table" then
    return false
  end
  if entry.awardedAt then
    return true
  end

  local rows, selectedRow = EnsurePreviewSelectedMember(preview, entry)
  local targetRow = selectedRow
  for _, row in ipairs(rows) do
    if row.key == memberKey then
      targetRow = row
      break
    end
  end
  if not targetRow then
    return false
  end

  local itemLevel = tonumber(entry.itemLevel)
  if not itemLevel and TrimText(entry.link) ~= "" and type(GetDetailedItemLevelInfo) == "function" then
    itemLevel = tonumber((GetDetailedItemLevelInfo(entry.link)))
  end

  local selection = type(entry.selections) == "table" and entry.selections[targetRow.key] or nil
  local points = tonumber(entry.projectedPoints) or 0
  local awardOutcome = nil
  local pointProfileName = tostring(preview.sourceProfileName or preview.profileName or "Simulation")
  if type(ResolveLootAwardOutcome) == "function" then
    points, awardOutcome = ResolveLootAwardOutcome(
      pointProfileName,
      tostring(entry.name or entry.link or entry.key or "?"),
      itemLevel,
      entry.itemID,
      entry.link,
      selection
    )
    entry.projectedPoints = tonumber(points) or 0
  elseif type(ResolveLootPointValue) == "function" then
    points = tonumber((ResolveLootPointValue(
      pointProfileName,
      tostring(entry.name or entry.link or entry.key or "?"),
      itemLevel,
      entry.itemID,
      entry.link
    ))) or points
    entry.projectedPoints = points
  end
  entry.itemLevel = itemLevel or entry.itemLevel
  preview.selectedMemberKey = targetRow.key
  EnsureSimulationMemberMeta(preview, targetRow.key)
  local note = string.format(
    "Simulation | Source Profile %s | Item %s",
    tostring(preview.sourceProfileName or "Default"),
    tostring(entry.key or "?")
  )
  local ok, ledgerEntry = AddLootLedgerEntry(
    tostring(preview.profileName or "Simulation"),
    tostring(targetRow.key),
    tostring(entry.name or entry.key or "?"),
    tonumber(entry.itemLevel),
    points,
    note,
    entry.itemID,
    entry.link,
    entry.icon,
    entry.quality,
    {
      pointBehavior = TrimText(type(awardOutcome) == "table" and awardOutcome.pointBehavior or (type(selection) == "table" and selection.pointBehavior) or ""),
      counterKey = TrimText(type(awardOutcome) == "table" and awardOutcome.counterKey or ""),
      optionKey = TrimText(type(selection) == "table" and (selection.optionKey or selection.key) or ""),
      optionLabel = TrimText(type(selection) == "table" and selection.label or ""),
    }
  )
  if not ok then
    if type(Print) == "function" then
      Print("[Simulation] Could not write the awarded item into the Simulation loot list.")
    end
    return false
  end

  MarkAwardOnEntry(entry, targetRow.key, points, note)
  entry.ledgerEntryId = TrimText(type(ledgerEntry) == "table" and ledgerEntry.entryId or "")
  if type(Print) == "function" then
    local optionLabel = TrimText(type(selection) == "table" and (selection.label ~= "" and selection.label or selection.optionKey) or "")
    local optionSuffix = optionLabel ~= "" and (" for \"" .. optionLabel .. "\"") or ""
    Print(
      string.format(
        "[Simulation] Assigned \"%s\" (iLvl %s) to %s for %d point(s)%s in profile \"%s\".",
        tostring(entry.name or entry.key),
        tostring(entry.itemLevel or "?"),
        tostring(targetRow.name or targetRow.key),
        points,
        optionSuffix,
        tostring(preview.profileName or "Simulation")
      )
    )
  end
  return true
end

local function ApplySimulationChoice(preview, itemKey, chosenOption, options)
  if type(preview) ~= "table" or type(preview.items) ~= "table" then
    return nil
  end
  local entry = preview.items[itemKey]
  if type(entry) ~= "table" then
    return nil
  end

  local selfKey = preview.selfKey or GetSelfKey() or "You-PreviewRealm"
  local resolvedOptions = (type(options) == "table" and #options > 0) and options or DEFAULT_OPTIONS
  entry.selections = entry.selections or {}

  for index, member in ipairs(preview.members or {}) do
    local sourceOption
    if member.key == selfKey then
      sourceOption = chosenOption
    else
      local optionIndex
      if type(math) == "table" and type(math.random) == "function" then
        optionIndex = math.random(1, #resolvedOptions)
      else
        optionIndex = ((index - 1) % #resolvedOptions) + 1
      end
      sourceOption = resolvedOptions[optionIndex] or chosenOption or DEFAULT_OPTIONS[1]
    end
    local selection = BuildSimulationRollOption(sourceOption)
    RollSimulationSelection(selection)
    entry.selections[member.key] = selection
  end

  preview.selectedItemKey = itemKey
  preview.selectedMemberKey = nil
  local _, topRow = EnsurePreviewSelectedMember(preview, entry)
  if topRow then
    preview.selectedMemberKey = topRow.key
  end
  PrintSimulationRollResult(preview, entry)

  local nextQueue = {}
  for _, queuedKey in ipairs(preview.popupQueue or {}) do
    if queuedKey ~= itemKey then
      nextQueue[#nextQueue + 1] = queuedKey
    end
  end
  preview.popupQueue = nextQueue
  return nextQueue[1]
end

INTERNAL_API.LIDCOUNCIL_LootRuntimeSimulation_Impl = {
  BuildPreviewBoardState = BuildPreviewBoardState,
  BuildSimulationBoardState = BuildSimulationBoardState,
  BuildRealSimulationItemDefs = BuildRealSimulationItemDefs,
  GetSelectionPriority = GetSelectionPriority,
  GetSelectionRollValue = GetSelectionRollValue,
  FormatSelectionLabel = FormatSelectionLabel,
  GetPreviewCandidateRows = GetPreviewCandidateRows,
  EnsurePreviewSelectedMember = EnsurePreviewSelectedMember,
  CyclePreviewSelectedMember = CyclePreviewSelectedMember,
  ApplySimulationAward = ApplySimulationAward,
  ApplySimulationChoice = ApplySimulationChoice,
}
