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

INTERNAL_API.LIDCOUNCIL_CreateLootRuntimeStateHelpers_Impl = function(deps)
  deps = type(deps) == "table" and deps or {}

  local RootDB = deps.RootDB
  local GetCharacterKey = deps.GetCharacterKey
  local GetLootProfile = deps.GetLootProfile
  local GetActiveLootSession = deps.GetActiveLootSession
  local HasJoinedActiveLootSession = deps.HasJoinedActiveLootSession
  local NormalizeCharacterKey = deps.NormalizeCharacterKey
  local DEFAULT_OPTIONS = deps.DEFAULT_OPTIONS
  local CLASS_ICON_TEXTURE = deps.CLASS_ICON_TEXTURE
  local runtime = deps.runtime
  local uiState = deps.uiState
  local TrimText = deps.TrimText
  local ExtractItemID = deps.ExtractItemID
  local RefreshEditor = deps.RefreshEditor
  local ResolveOptionIconTexture = deps.ResolveOptionIconTexture
  local NormalizeRollOptionsList = deps.NormalizeRollOptionsList
  local EnsureRuntimeBoardContext = deps.EnsureRuntimeBoardContext
  local GetLatestRuntimeBoardSnapshot = deps.GetLatestRuntimeBoardSnapshot
  local RestoreRuntimeBoardSnapshot = deps.RestoreRuntimeBoardSnapshot
  local PersistRuntimeBoardHistory = deps.PersistRuntimeBoardHistory
  local NormalizeEntryVotes = deps.NormalizeEntryVotes
  local ResolveDefaultOptionIcon = deps.ResolveDefaultOptionIcon

  local function ClearRuntimeState()
    runtime.sessionId = nil
    runtime.profileName = nil
    runtime.host = nil
    runtime.boardId = nil
    runtime.historyProfileName = nil
    runtime.itemCounts = {}
    runtime.itemOrder = {}
    runtime.items = {}
    runtime.popupQueue = {}
    runtime.localRollItemKeys = {}
    runtime.hostRollItemKeys = {}
    runtime.hostReportItemKeys = {}
    runtime.selectedItemKey = nil
    runtime.selectedMemberKey = nil
    if uiState.boardFrame then
      uiState.boardFrame.__previewState = nil
    end
    if uiState.choicePopup then
      uiState.choicePopup.__previewSelectHandler = nil
      uiState.choicePopup:Hide()
    end
  end

  local function ClearRuntimeAndRefresh()
    ClearRuntimeState()
    if uiState.boardFrame and uiState.boardFrame.RefreshBoard then
      uiState.boardFrame:RefreshBoard()
    end
    RefreshEditor()
  end

  local function EnsureRuntimeSession(session, profileName)
    local resolvedId = type(session) == "table" and tostring(session.id or "") or ""
    if resolvedId == "" then
      ClearRuntimeState()
      return false
    end
    if runtime.sessionId ~= resolvedId or runtime.profileName ~= tostring(profileName or "") then
      ClearRuntimeState()
      runtime.sessionId = resolvedId
      runtime.profileName = tostring(profileName or "")
      runtime.host = tostring(session.host or "")
      EnsureRuntimeBoardContext(session, profileName)
      RestoreRuntimeBoardSnapshot(session, profileName, GetLatestRuntimeBoardSnapshot(profileName, resolvedId))
    end
    runtime.host = tostring(session.host or runtime.host or "")
    return true
  end

  local function ResolveRollOptions(session, profileName)
    local options = type(session) == "table" and session.rollOptions or nil
    options = NormalizeRollOptionsList(options)
    if type(options) == "table" and #options > 0 then
      return options
    end
    local profile = GetLootProfile(profileName)
    options = profile and profile.settings and profile.settings.rollOptions or nil
    options = NormalizeRollOptionsList(options)
    if type(options) == "table" and #options > 0 then
      return options
    end
    return DEFAULT_OPTIONS
  end

  local function ResolveRollOption(session, profileName, optionKey, fallbackLabel)
    local options = ResolveRollOptions(session, profileName)
    local needleKey = string.lower(TrimText(optionKey))
    local fallbackNeedle = string.lower(TrimText(fallbackLabel))
    for _, option in ipairs(options) do
      local key = string.lower(TrimText(option.key))
      local label = string.lower(TrimText(option.label))
      if (needleKey ~= "" and key == needleKey) or (fallbackNeedle ~= "" and label == fallbackNeedle) then
        return {
          key = TrimText(option.key or option.label or optionKey),
          label = TrimText(option.label or option.key or fallbackLabel),
          color = {
            tonumber(option.color and option.color[1]) or 0.82,
            tonumber(option.color and option.color[2]) or 0.82,
            tonumber(option.color and option.color[3]) or 0.86,
          },
          icon = ResolveOptionIconTexture(option.icon, option.key, option.label or fallbackLabel),
          pointBehavior = TrimText(option.pointBehavior or ""),
        }
      end
    end
    return {
      key = TrimText(optionKey ~= "" and optionKey or fallbackLabel),
      label = TrimText(fallbackLabel ~= "" and fallbackLabel or optionKey),
      color = { 0.82, 0.82, 0.86 },
      icon = ResolveDefaultOptionIcon(optionKey, fallbackLabel),
      pointBehavior = "",
    }
  end

  local function GetSelfKey()
    return NormalizeCharacterKey(GetCharacterKey() or "")
  end

  local function NormalizeComparableCharacterKey(value)
    return string.lower(NormalizeCharacterKey(value))
  end

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

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

  local function HasSelectionForMember(entry, memberKey)
    if type(entry) ~= "table" or type(entry.selections) ~= "table" then
      return false
    end
    if type(entry.selections[memberKey]) == "table" then
      return true
    end
    for selectionKey, selection in pairs(entry.selections) do
      if AreCharacterKeysSame(selectionKey, memberKey) and type(selection) == "table" then
        return true
      end
    end
    return false
  end

  local function NormalizeRealmName(value)
    return tostring(value or ""):gsub("%s+", "")
  end

  local function GetLocalRealmName()
    if type(GetNormalizedRealmName) == "function" then
      local normalizedRealm = NormalizeRealmName(GetNormalizedRealmName())
      if normalizedRealm ~= "" then
        return normalizedRealm
      end
    end
    if type(GetRealmName) == "function" then
      local realmName = NormalizeRealmName(GetRealmName())
      if realmName ~= "" then
        return realmName
      end
    end
    return ""
  end

  local function BuildNormalizedCharacterKey(name, realm)
    local resolvedName = TrimText(name)
    if resolvedName == "" then
      return ""
    end

    if string.find(resolvedName, "-", 1, true) then
      return NormalizeCharacterKey(resolvedName)
    end

    local resolvedRealm = NormalizeRealmName(realm)
    if resolvedRealm == "" then
      resolvedRealm = GetLocalRealmName()
    end
    if resolvedRealm == "" then
      return NormalizeCharacterKey(resolvedName)
    end
    return NormalizeCharacterKey(resolvedName .. "-" .. resolvedRealm)
  end

  local function ResolveSessionUnit(memberKey)
    local targetKey = NormalizeCharacterKey(memberKey)
    if targetKey == "" or type(UnitExists) ~= "function" or type(UnitFullName) ~= "function" then
      return nil
    end

    local function MatchUnit(unit)
      if not UnitExists(unit) then
        return false
      end
      local unitName, unitRealm = UnitFullName(unit)
      return BuildNormalizedCharacterKey(unitName, unitRealm) == targetKey
    end

    if MatchUnit("player") then
      return "player"
    end

    if type(IsInRaid) == "function" and IsInRaid() then
      local count = (type(GetNumGroupMembers) == "function" and GetNumGroupMembers()) or 0
      for i = 1, count do
        local unit = "raid" .. tostring(i)
        if MatchUnit(unit) then
          return unit
        end
      end
    elseif type(IsInGroup) == "function" and IsInGroup() then
      local count = (type(GetNumSubgroupMembers) == "function" and GetNumSubgroupMembers()) or 0
      for i = 1, count do
        local unit = "party" .. tostring(i)
        if MatchUnit(unit) then
          return unit
        end
      end
    end

    return nil
  end

  local function ResolveStoredMemberIconData(row)
    if type(row) ~= "table" then
      return nil
    end

    local specIcon = TrimText(row.specIcon)
    if specIcon ~= "" then
      return { texture = specIcon }
    end

    local classFile = TrimText(row.classFile)
    local coords = classFile ~= "" and type(CLASS_ICON_TCOORDS) == "table" and CLASS_ICON_TCOORDS[classFile] or nil
    if type(coords) == "table" then
      return {
        texture = CLASS_ICON_TEXTURE,
        fileWidth = 256,
        fileHeight = 256,
        left = coords[1],
        right = coords[2],
        top = coords[3],
        bottom = coords[4],
      }
    end

    return nil
  end

  local function ResolveMemberIconData(rowOrKey)
    local row = type(rowOrKey) == "table" and rowOrKey or nil
    local memberKey = type(rowOrKey) == "table" and rowOrKey.key or rowOrKey
    local storedIcon = ResolveStoredMemberIconData(row)
    if storedIcon then
      return storedIcon
    end

    local unit = ResolveSessionUnit(memberKey)
    if not unit then
      return nil
    end

    if unit == "player" and type(GetSpecialization) == "function" and type(GetSpecializationInfo) == "function" then
      local specIndex = GetSpecialization()
      if tonumber(specIndex) and specIndex > 0 then
        local _, _, _, specIcon = GetSpecializationInfo(specIndex)
        if specIcon then
          return { texture = specIcon }
        end
      end
    elseif type(GetInspectSpecialization) == "function" and type(GetSpecializationInfoByID) == "function" then
      local specID = GetInspectSpecialization(unit)
      if tonumber(specID) and specID > 0 then
        local _, _, _, specIcon = GetSpecializationInfoByID(specID)
        if specIcon then
          return { texture = specIcon }
        end
      end
    end

    if type(UnitClass) == "function" and type(CLASS_ICON_TCOORDS) == "table" then
      local _, classFile = UnitClass(unit)
      local coords = classFile and CLASS_ICON_TCOORDS[classFile] or nil
      if type(coords) == "table" then
        return {
          texture = CLASS_ICON_TEXTURE,
          fileWidth = 256,
          fileHeight = 256,
          left = coords[1],
          right = coords[2],
          top = coords[3],
          bottom = coords[4],
        }
      end
    end

    return nil
  end

  local function BuildInlineIconTag(iconData, size)
    if type(iconData) ~= "table" or TrimText(iconData.texture) == "" then
      return ""
    end

    local iconSize = math.max(10, tonumber(size) or 14)
    local left = tonumber(iconData.left)
    local right = tonumber(iconData.right)
    local top = tonumber(iconData.top)
    local bottom = tonumber(iconData.bottom)
    if left and right and top and bottom then
      local fileWidth = tonumber(iconData.fileWidth) or 256
      local fileHeight = tonumber(iconData.fileHeight) or 256
      return string.format(
        "|T%s:%d:%d:0:0:%d:%d:%d:%d:%d:%d|t",
        tostring(iconData.texture),
        iconSize,
        iconSize,
        fileWidth,
        fileHeight,
        math.floor((left * fileWidth) + 0.5),
        math.floor((right * fileWidth) + 0.5),
        math.floor((top * fileHeight) + 0.5),
        math.floor((bottom * fileHeight) + 0.5)
      )
    end

    return string.format("|T%s:%d:%d:0:0|t", tostring(iconData.texture), iconSize, iconSize)
  end

  local function BuildMemberDisplayText(row, opts)
    opts = opts or {}
    if type(row) ~= "table" then
      return ""
    end

    local name = tostring(row.name or row.key or "?")
    local specName = TrimText(row.specName)
    if specName ~= "" and not string.find(name, " | ", 1, true) then
      name = name .. " | " .. specName
    end
    if row.isHost and not opts.hideHostTag then
      name = name .. " [Host]"
    end
    if opts.selected then
      name = name .. " [Selected]"
    end

    local iconTag = BuildInlineIconTag(ResolveMemberIconData(row), tonumber(opts.iconSize) or 14)
    if iconTag ~= "" then
      name = iconTag .. " " .. name
    end
    return name
  end

  local function IsSelfHost(session)
    return AreCharacterKeysSame(type(session) == "table" and session.host or "", GetSelfKey())
  end

  local function ResolveDirectJoinedSessionState()
    if type(GetActiveLootSession) ~= "function" then
      return false, nil, nil
    end

    local 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 = NormalizeCharacterKey(GetCharacterKey() or "")
    if selfKey == "" then
      return false, nil, nil
    end

    if AreCharacterKeysSame(activeSession.host or "", selfKey) then
      return true, activeSession, profileName
    end

    local directMember = type(activeSession.members) == "table" and activeSession.members[selfKey] or nil
    if type(directMember) == "table" and tostring(directMember.status or "JOINED") == "JOINED" then
      return true, activeSession, profileName
    end

    if type(activeSession.members) == "table" then
      for memberKey, member in pairs(activeSession.members) do
        if AreCharacterKeysSame(memberKey, selfKey) and tostring(type(member) == "table" and member.status or "JOINED") == "JOINED" then
          return true, activeSession, profileName
        end
      end
    end

    return false, nil, nil
  end

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

  local function GetBroadcastDistribution()
    local instanceCategory = rawget(_G, "LE_PARTY_CATEGORY_INSTANCE") or 2
    if type(IsInGroup) == "function" and IsInGroup(instanceCategory) then
      return "INSTANCE_CHAT"
    end
    if type(IsInRaid) == "function" and IsInRaid() then
      return "RAID"
    end
    if type(IsInGroup) == "function" and IsInGroup() then
      return "PARTY"
    end
    return nil
  end

  local function ParseItemSequence(itemKey)
    local text = tostring(itemKey or "")
    local base, index = text:match("^(.*)#(%d+)$")
    if base and index then
      return base, tonumber(index)
    end
    return text, nil
  end

  local function PrimeSequence(itemKey)
    local base, index = ParseItemSequence(itemKey)
    if base ~= "" and tonumber(index) then
      local current = tonumber(runtime.itemCounts[base]) or 0
      if index > current then
        runtime.itemCounts[base] = index
      end
    end
  end

  local function NextItemKey(baseKey)
    local current = tonumber(runtime.itemCounts[baseKey]) or 0
    current = current + 1
    runtime.itemCounts[baseKey] = current
    return baseKey .. "#" .. tostring(current)
  end

  local function QueueContains(itemKey)
    for _, queuedKey in ipairs(runtime.popupQueue) do
      if queuedKey == itemKey then
        return true
      end
    end
    return false
  end

  local function ResolveDetailedItemLevel(link)
    local itemLink = TrimText(link)
    if itemLink == "" then
      return nil
    end
    if type(C_Item) == "table" and type(C_Item.GetDetailedItemLevelInfo) == "function" then
      local ok, itemLevel = pcall(C_Item.GetDetailedItemLevelInfo, itemLink)
      if ok and tonumber(itemLevel) and tonumber(itemLevel) > 0 then
        return tonumber(itemLevel)
      end
    end
    if type(GetDetailedItemLevelInfo) == "function" then
      local ok, itemLevel = pcall(GetDetailedItemLevelInfo, itemLink)
      if ok and tonumber(itemLevel) and tonumber(itemLevel) > 0 then
        return tonumber(itemLevel)
      end
    end
    return nil
  end

  local function EnsureLiveEntry(session, profileName, itemKey, payload)
    local entry = runtime.items[itemKey]
    if type(entry) ~= "table" then
      entry = {
        key = itemKey,
        sessionId = runtime.sessionId,
        profileName = profileName,
        name = tostring(payload and payload.name or "Awaiting item info"),
        link = payload and payload.link or nil,
        icon = payload and payload.icon or nil,
        quality = tonumber(payload and payload.quality) or nil,
        itemID = ExtractItemID(payload and (payload.itemID or payload.link)),
        itemLevel = tonumber(payload and payload.itemLevel) or nil,
        difficultyID = tonumber(payload and (payload.difficultyID or payload.lootDifficultyID or payload.difficulty)) or nil,
        difficultyName = payload and payload.difficultyName or nil,
        createdAt = time and time() or 0,
        localRollId = tonumber(payload and payload.rollID) or nil,
        selections = {},
        votesByVoter = {},
      }
      runtime.items[itemKey] = entry
      runtime.itemOrder[#runtime.itemOrder + 1] = itemKey
      if not runtime.selectedItemKey then
        runtime.selectedItemKey = itemKey
      end
    end

    if payload then
      if payload.name and payload.name ~= "" then
        entry.name = payload.name
      end
      if payload.link and payload.link ~= "" then
        entry.link = payload.link
      end
      if payload.icon then
        entry.icon = payload.icon
      end
      if payload.quality then
        entry.quality = tonumber(payload.quality)
      end
      if payload.itemID or payload.link then
        entry.itemID = ExtractItemID(payload.itemID or payload.link)
      end
      if payload.itemLevel then
        entry.itemLevel = tonumber(payload.itemLevel)
      end
      if entry.link and (not tonumber(entry.itemLevel) or tonumber(entry.itemLevel) < 100) then
        local detailedItemLevel = ResolveDetailedItemLevel(entry.link)
        if detailedItemLevel and detailedItemLevel >= 100 then
          entry.itemLevel = detailedItemLevel
        elseif tonumber(entry.itemLevel) and tonumber(entry.itemLevel) < 100 then
          entry.itemLevel = nil
        end
      end
      if tonumber(entry.itemLevel) and tonumber(entry.itemLevel) < 100 and tonumber(entry.itemID) and tonumber(entry.itemID) > 100000 then
        entry.itemLevel = nil
      end
      if payload.difficultyID or payload.lootDifficultyID or payload.difficulty then
        entry.difficultyID = tonumber(payload.difficultyID or payload.lootDifficultyID or payload.difficulty)
      end
      if payload.difficultyName then
        entry.difficultyName = payload.difficultyName
      end
      if payload.rollID then
        entry.localRollId = tonumber(payload.rollID)
      end
    end

    if type(session) == "table" and type(session.members) == "table" then
      for memberKey, member in pairs(session.members) do
        if tostring(member.status or "JOINED") == "JOINED" and not HasSelectionForMember(entry, memberKey) then
          entry.selections[memberKey] = {
            optionKey = "pending",
            label = "Pending",
            color = { 0.72, 0.72, 0.76 },
            selectedAt = nil,
            source = "RUNTIME",
          }
        end
      end
    end
    NormalizeEntryVotes(entry)
    PersistRuntimeBoardHistory({
      itemKey = TrimText(type(entry) == "table" and entry.key or ""),
      includeItemOrder = true,
    })
    return entry
  end

  local function GetTrackedItemCount()
    local count = 0
    for _, itemKey in ipairs(runtime.itemOrder) do
      if runtime.items[itemKey] then
        count = count + 1
      end
    end
    return count
  end

  local function BuildPreviewSummary(preview)
    if type(preview) ~= "table" then
      return nil
    end

    local itemCount = 0
    local pending = 0
    local unassigned = 0
    local latestItem = nil
    for _, itemKey in ipairs(preview.itemOrder or {}) do
      local entry = preview.items and preview.items[itemKey] or nil
      if type(entry) == "table" then
        itemCount = itemCount + 1
        latestItem = entry.link or entry.name or latestItem
        if not entry.awardedAt then
          unassigned = unassigned + 1
          for _, selection in pairs(entry.selections or {}) do
            if string.lower(TrimText(selection and (selection.optionKey or selection.label) or "pending")) == "pending" then
              pending = pending + 1
            end
          end
        end
      end
    end

    return {
      itemCount = itemCount,
      pendingChoices = pending,
      unassignedItems = unassigned,
      latestItem = latestItem,
      isHost = true,
      isPreview = true,
      previewLabel = tostring(preview.summaryLabel or "Preview"),
    }
  end

  local function GetLiveLootBoardSummary()
    local previewSummary = BuildPreviewSummary(uiState.previewState or (uiState.boardFrame and uiState.boardFrame.__previewState))
    if previewSummary then
      return previewSummary
    end

    local activeSession, profileName = GetActiveLootSession()
    if not activeSession or not EnsureRuntimeSession(activeSession, profileName) then
      if uiState.choicePopup then
        uiState.choicePopup:Hide()
      end
      return {
        itemCount = 0,
        pendingChoices = 0,
        unassignedItems = 0,
        latestItem = nil,
        isHost = false,
      }
    end

    local pending = 0
    local unassigned = 0
    for _, itemKey in ipairs(runtime.itemOrder) do
      local entry = runtime.items[itemKey]
      if type(entry) == "table" and not entry.awardedAt then
        unassigned = unassigned + 1
        for _, selection in pairs(entry.selections or {}) do
          if tostring(selection.optionKey or "") == "pending" then
            pending = pending + 1
          end
        end
      end
    end

    local latestKey = runtime.itemOrder[#runtime.itemOrder]
    local latestItem = latestKey and runtime.items[latestKey] or nil
    return {
      itemCount = GetTrackedItemCount(),
      pendingChoices = pending,
      unassignedItems = unassigned,
      latestItem = latestItem and (latestItem.link or latestItem.name) or nil,
      isHost = IsSelfHost(activeSession),
    }
  end

  return {
    ClearRuntimeState = ClearRuntimeState,
    ClearRuntimeAndRefresh = ClearRuntimeAndRefresh,
    EnsureRuntimeSession = EnsureRuntimeSession,
    ResolveRollOptions = ResolveRollOptions,
    ResolveRollOption = ResolveRollOption,
    GetSelfKey = GetSelfKey,
    NormalizeRealmName = NormalizeRealmName,
    GetLocalRealmName = GetLocalRealmName,
    BuildNormalizedCharacterKey = BuildNormalizedCharacterKey,
    ResolveSessionUnit = ResolveSessionUnit,
    ResolveStoredMemberIconData = ResolveStoredMemberIconData,
    ResolveMemberIconData = ResolveMemberIconData,
    BuildInlineIconTag = BuildInlineIconTag,
    BuildMemberDisplayText = BuildMemberDisplayText,
    IsSelfHost = IsSelfHost,
    GetJoinedSessionState = GetJoinedSessionState,
    GetBroadcastDistribution = GetBroadcastDistribution,
    ParseItemSequence = ParseItemSequence,
    PrimeSequence = PrimeSequence,
    NextItemKey = NextItemKey,
    QueueContains = QueueContains,
    EnsureLiveEntry = EnsureLiveEntry,
    GetTrackedItemCount = GetTrackedItemCount,
    BuildPreviewSummary = BuildPreviewSummary,
    GetLiveLootBoardSummary = GetLiveLootBoardSummary,
  }
end
