local function RequireApi(name, value)
  if value == nil then
    value = rawget(_G, name)
  end
  if value == nil then
    local api = rawget(_G, "LIDCOUNCIL_API")
    if type(api) == "table" then
      value = api[name]
    end
  end
  if value == nil then
    error("LidCouncil missing API: " .. tostring(name))
  end
  return value
end

local API = rawget(_G, "LIDCOUNCIL_API")
if type(API) ~= "table" then
  API = {}
  _G.LIDCOUNCIL_API = API
end
API.__internal = API.__internal or {}
local INTERNAL_API = API.__internal

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

local function PublishApi(name, value, opts)
  API[name] = value
  if type(opts) == "table" and opts.global == false then
    _G[name] = nil
    return
  end
  _G[name] = value
end

local commApi = {
  RootDB = RequireApi("LIDCOUNCIL_RootDB", _G.LIDCOUNCIL_RootDB),
  Print = RequireApi("LIDCOUNCIL_Print", _G.LIDCOUNCIL_Print),
  GetCharacterKey = RequireApi("LIDCOUNCIL_GetCharacterKey", _G.LIDCOUNCIL_GetCharacterKey),
  AddCharacterToList = RequireApi("LIDCOUNCIL_AddCharacterToList", _G.LIDCOUNCIL_AddCharacterToList),
  StartLootSession = RequireApi("LIDCOUNCIL_StartLootSession", _G.LIDCOUNCIL_StartLootSession),
  ActivateLootSession = RequireApi("LIDCOUNCIL_ActivateLootSession", _G.LIDCOUNCIL_ActivateLootSession),
  GetActiveLootSession = RequireApi("LIDCOUNCIL_GetActiveLootSession", _G.LIDCOUNCIL_GetActiveLootSession),
  StopLootSession = RequireApi("LIDCOUNCIL_StopLootSession", _G.LIDCOUNCIL_StopLootSession),
  JoinLootSession = RequireApi("LIDCOUNCIL_JoinLootSession", _G.LIDCOUNCIL_JoinLootSession),
  UpsertLootSessionMember = RequireApi("LIDCOUNCIL_UpsertLootSessionMember", _G.LIDCOUNCIL_UpsertLootSessionMember),
  RemoveLootSessionMember = RequireApi("LIDCOUNCIL_RemoveLootSessionMember", _G.LIDCOUNCIL_RemoveLootSessionMember),
  FinishLootSession = RequireApi("LIDCOUNCIL_FinishLootSession", _G.LIDCOUNCIL_FinishLootSession),
  GetLootProfileRole = RequireApi("LIDCOUNCIL_GetLootProfileRole", _G.LIDCOUNCIL_GetLootProfileRole),
  GetLootProfileMaster = RequireApi("LIDCOUNCIL_GetLootProfileMaster", _G.LIDCOUNCIL_GetLootProfileMaster),
  SyncLootProfilePermissionState = RequireApi("LIDCOUNCIL_SyncLootProfilePermissionState", _G.LIDCOUNCIL_SyncLootProfilePermissionState),
  EnsureLootProfileMemberRole = RequireApi("LIDCOUNCIL_EnsureLootProfileMemberRole", _G.LIDCOUNCIL_EnsureLootProfileMemberRole),
  GetLocalAutoPassEnabled = RequireApi("LIDCOUNCIL_GetLocalAutoPassEnabled", _G.LIDCOUNCIL_GetLocalAutoPassEnabled),
  SetLocalAutoPassEnabled = RequireApi("LIDCOUNCIL_SetLocalAutoPassEnabled", _G.LIDCOUNCIL_SetLocalAutoPassEnabled),
  GetLootProfileId = RequireApi("LIDCOUNCIL_GetLootProfileId", _G.LIDCOUNCIL_GetLootProfileId),
  FindLootProfileNameById = RequireApi("LIDCOUNCIL_FindLootProfileNameById", _G.LIDCOUNCIL_FindLootProfileNameById),
  ExportLootProfileSnapshot = RequireApi("LIDCOUNCIL_ExportLootProfileSnapshot", _G.LIDCOUNCIL_ExportLootProfileSnapshot),
  ImportLootProfileSnapshot = RequireApi("LIDCOUNCIL_ImportLootProfileSnapshot", _G.LIDCOUNCIL_ImportLootProfileSnapshot),
  PreviewLootProfileSnapshot = RequireApi("LIDCOUNCIL_PreviewLootProfileSnapshot", _G.LIDCOUNCIL_PreviewLootProfileSnapshot),
  BuildLootProfileSnapshotDiff = RequireApi("LIDCOUNCIL_BuildLootProfileSnapshotDiff", _G.LIDCOUNCIL_BuildLootProfileSnapshotDiff),
  ExportLootProfileDomainSnapshot = RequireApi("LIDCOUNCIL_ExportLootProfileDomainSnapshot", _G.LIDCOUNCIL_ExportLootProfileDomainSnapshot),
  ImportLootProfileDomainSnapshot = RequireApi("LIDCOUNCIL_ImportLootProfileDomainSnapshot", _G.LIDCOUNCIL_ImportLootProfileDomainSnapshot),
  PreviewLootProfileDomainSnapshot = RequireApi("LIDCOUNCIL_PreviewLootProfileDomainSnapshot", _G.LIDCOUNCIL_PreviewLootProfileDomainSnapshot),
  BuildLootProfileDomainSnapshotDiff = RequireApi("LIDCOUNCIL_BuildLootProfileDomainSnapshotDiff", _G.LIDCOUNCIL_BuildLootProfileDomainSnapshotDiff),
  ExportLootProfileSubdomainSnapshot = RequireApi("LIDCOUNCIL_ExportLootProfileSubdomainSnapshot", _G.LIDCOUNCIL_ExportLootProfileSubdomainSnapshot),
  ImportLootProfileSubdomainSnapshot = RequireApi("LIDCOUNCIL_ImportLootProfileSubdomainSnapshot", _G.LIDCOUNCIL_ImportLootProfileSubdomainSnapshot),
  PreviewLootProfileSubdomainSnapshot = RequireApi("LIDCOUNCIL_PreviewLootProfileSubdomainSnapshot", _G.LIDCOUNCIL_PreviewLootProfileSubdomainSnapshot),
  BuildLootProfileSubdomainSnapshotDiff = RequireApi("LIDCOUNCIL_BuildLootProfileSubdomainSnapshotDiff", _G.LIDCOUNCIL_BuildLootProfileSubdomainSnapshotDiff),
  ExportLootProfileSubdomainPatch = RequireApi("LIDCOUNCIL_ExportLootProfileSubdomainPatch", _G.LIDCOUNCIL_ExportLootProfileSubdomainPatch),
  ImportLootProfileSubdomainPatch = RequireApi("LIDCOUNCIL_ImportLootProfileSubdomainPatch", _G.LIDCOUNCIL_ImportLootProfileSubdomainPatch),
  PreviewLootProfileSubdomainPatch = RequireApi("LIDCOUNCIL_PreviewLootProfileSubdomainPatch", _G.LIDCOUNCIL_PreviewLootProfileSubdomainPatch),
  BuildLootProfileSubdomainPatchDiff = RequireApi("LIDCOUNCIL_BuildLootProfileSubdomainPatchDiff", _G.LIDCOUNCIL_BuildLootProfileSubdomainPatchDiff),
  ExportLootProfileConflictRowPatch = RequireApi("LIDCOUNCIL_ExportLootProfileConflictRowPatch", _G.LIDCOUNCIL_ExportLootProfileConflictRowPatch),
  ImportLootProfileConflictRowPatch = RequireApi("LIDCOUNCIL_ImportLootProfileConflictRowPatch", _G.LIDCOUNCIL_ImportLootProfileConflictRowPatch),
  PreviewLootProfileConflictRowPatch = RequireApi("LIDCOUNCIL_PreviewLootProfileConflictRowPatch", _G.LIDCOUNCIL_PreviewLootProfileConflictRowPatch),
  BuildLootProfileConflictRowPatchDiff = RequireApi("LIDCOUNCIL_BuildLootProfileConflictRowPatchDiff", _G.LIDCOUNCIL_BuildLootProfileConflictRowPatchDiff),
  BuildLootProfileTransferConflictPreview = RequireApi("LIDCOUNCIL_BuildLootProfileTransferConflictPreview", _G.LIDCOUNCIL_BuildLootProfileTransferConflictPreview),
  ApplyLootProfileTransferConflictResolution = RequireApi("LIDCOUNCIL_ApplyLootProfileTransferConflictResolution", _G.LIDCOUNCIL_ApplyLootProfileTransferConflictResolution),
  ExportLootProfileSubdomainPatchManifest = RequireApi("LIDCOUNCIL_ExportLootProfileSubdomainPatchManifest", _G.LIDCOUNCIL_ExportLootProfileSubdomainPatchManifest),
  PreviewLootProfileSubdomainPatchManifest = RequireApi("LIDCOUNCIL_PreviewLootProfileSubdomainPatchManifest", _G.LIDCOUNCIL_PreviewLootProfileSubdomainPatchManifest),
  GetLootProfileSubchecksumManifest = RequireApi("LIDCOUNCIL_GetLootProfileSubchecksumManifest", _G.LIDCOUNCIL_GetLootProfileSubchecksumManifest),
  NormalizeLootProfileSubdomainList = RequireApi("LIDCOUNCIL_NormalizeLootProfileSubdomainList", _G.LIDCOUNCIL_NormalizeLootProfileSubdomainList),
  CloneLootProfileSubdomainList = RequireApi("LIDCOUNCIL_CloneLootProfileSubdomainList", _G.LIDCOUNCIL_CloneLootProfileSubdomainList),
  FilterLootProfilePatchCapableSubdomains = RequireApi("LIDCOUNCIL_FilterLootProfilePatchCapableSubdomains", _G.LIDCOUNCIL_FilterLootProfilePatchCapableSubdomains),
  GetLootProfileSubdomainKeysForDomain = RequireApi("LIDCOUNCIL_GetLootProfileSubdomainKeysForDomain", _G.LIDCOUNCIL_GetLootProfileSubdomainKeysForDomain),
  GetLootProfileSubdomainLabel = RequireApi("LIDCOUNCIL_GetLootProfileSubdomainLabel", _G.LIDCOUNCIL_GetLootProfileSubdomainLabel),
  GetLootProfileSubdomainParentDomain = RequireApi("LIDCOUNCIL_GetLootProfileSubdomainParentDomain", _G.LIDCOUNCIL_GetLootProfileSubdomainParentDomain),
  ResolveLootProfileDomainsForSubdomains = RequireApi("LIDCOUNCIL_ResolveLootProfileDomainsForSubdomains", _G.LIDCOUNCIL_ResolveLootProfileDomainsForSubdomains),
  GetLootProfileRevisionSummary = RequireApi("LIDCOUNCIL_GetLootProfileRevisionSummary", _G.LIDCOUNCIL_GetLootProfileRevisionSummary),
  ApplyIncomingLootProfileDelta = RequireApi("LIDCOUNCIL_ApplyIncomingLootProfileDelta", _G.LIDCOUNCIL_ApplyIncomingLootProfileDelta),
}

local COMM_PREFIX = "LIDLOOT1"
local COMM_PROTOCOL_VERSION = 5
local prefixRegistered = false
local DEFAULT_PROFILE_SNAPSHOT_CHUNK_SIZE = 120
local MIN_PROFILE_SNAPSHOT_CHUNK_SIZE = 80
local MAX_PROFILE_SNAPSHOT_CHUNK_SIZE = 190
local COMM_SAFE_PAYLOAD_LIMIT = 240
local COMM_WARNING_THROTTLE_SECONDS = 5
local DEFAULT_COMM_OUTBOUND_INTERVAL_SECONDS = 0.12
local MIN_COMM_OUTBOUND_INTERVAL_SECONDS = 0.08
local MAX_COMM_OUTBOUND_INTERVAL_SECONDS = 0.25
local recentCommWarnings = {}
local outboundCommQueue = {}
local outboundCommQueueRunning = false
local outboundCommStats = {
  queuedTotal = 0,
  attemptedTotal = 0,
  sentTotal = 0,
  throttledTotal = 0,
  failedTotal = 0,
  bytesAttempted = 0,
  bytesSent = 0,
  lastQueuedAt = 0,
  lastAttemptAt = 0,
  lastSentAt = 0,
  lastThrottleAt = 0,
  lastFailureAt = 0,
  lastFailureReason = "",
}
local outboundCommSendWindow = {}
local outboundProfileTransfers = {}
local outboundProfileTransferOrder = {}
local profileSyncHelpers
local profileSyncRuntimeState = {
  sessionId = "",
  profileName = "",
  mode = "idle",
  severity = "info",
  message = "",
  sender = "",
  updatedAt = 0,
  remoteDomains = {},
  localDomains = {},
  detailDiff = nil,
}
local PROFILE_REVISION_DOMAINS = { "settings", "permissions", "loot", "session" }
local PROFILE_REVISION_DOMAIN_LABELS = {
  settings = "Settings",
  permissions = "Permissions",
  operational = "Legacy Operational Data",
  loot = "Loot Data",
  session = "Session Data",
}

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

local function ClampNumber(value, minValue, maxValue, fallback)
  local number = tonumber(value)
  if number == nil then
    number = fallback
  end
  number = tonumber(number) or tonumber(fallback) or 0
  minValue = tonumber(minValue) or number
  maxValue = tonumber(maxValue) or number
  if number < minValue then
    return minValue
  end
  if number > maxValue then
    return maxValue
  end
  return number
end

local function GetProfileSyncTuning()
  local root = type(commApi.RootDB) == "function" and commApi.RootDB() or nil
  local ui = type(root) == "table" and type(root.ui) == "table" and root.ui or nil
  local developerMode = type(ui) == "table" and ui.developerMode == true
  local chunkSize = DEFAULT_PROFILE_SNAPSHOT_CHUNK_SIZE
  local intervalSeconds = DEFAULT_COMM_OUTBOUND_INTERVAL_SECONDS
  if developerMode then
    chunkSize = math.max(1, math.floor((tonumber(ui.profileSyncChunkSize) or DEFAULT_PROFILE_SNAPSHOT_CHUNK_SIZE) + 0.5))
    intervalSeconds = math.max(0, tonumber(ui.profileSyncDelaySeconds) or DEFAULT_COMM_OUTBOUND_INTERVAL_SECONDS)
  end
  return {
    developerMode = developerMode,
    chunkSize = math.floor(chunkSize + 0.5),
    intervalSeconds = intervalSeconds,
    defaultChunkSize = DEFAULT_PROFILE_SNAPSHOT_CHUNK_SIZE,
    minChunkSize = MIN_PROFILE_SNAPSHOT_CHUNK_SIZE,
    maxChunkSize = MAX_PROFILE_SNAPSHOT_CHUNK_SIZE,
    defaultIntervalSeconds = DEFAULT_COMM_OUTBOUND_INTERVAL_SECONDS,
    minIntervalSeconds = MIN_COMM_OUTBOUND_INTERVAL_SECONDS,
    maxIntervalSeconds = MAX_COMM_OUTBOUND_INTERVAL_SECONDS,
    safePayloadLimit = COMM_SAFE_PAYLOAD_LIMIT,
  }
end

local function SetProfileSyncTuning(chunkSize, intervalSeconds)
  local root = type(commApi.RootDB) == "function" and commApi.RootDB() or nil
  if type(root) ~= "table" then
    return GetProfileSyncTuning()
  end
  root.ui = root.ui or {}
  root.ui.profileSyncChunkSize = math.max(1, math.floor((tonumber(chunkSize) or DEFAULT_PROFILE_SNAPSHOT_CHUNK_SIZE) + 0.5))
  root.ui.profileSyncDelaySeconds = math.max(0, tonumber(intervalSeconds) or DEFAULT_COMM_OUTBOUND_INTERVAL_SECONDS)
  return GetProfileSyncTuning()
end

local function ResetProfileSyncTuning()
  return SetProfileSyncTuning(DEFAULT_PROFILE_SNAPSHOT_CHUNK_SIZE, DEFAULT_COMM_OUTBOUND_INTERVAL_SECONDS)
end

local function GetProfileSnapshotChunkSize()
  return GetProfileSyncTuning().chunkSize
end

local function GetCommOutboundIntervalSeconds()
  return GetProfileSyncTuning().intervalSeconds
end

local function NormalizeCharacterKey(value)
  local text = TrimText(value)
  if text == "" then
    return ""
  end
  text = text:gsub("%s*%-%s*", "-")
  text = text:gsub("%s+", "")
  return text
end

local function FirstNonEmptyText(...)
  for index = 1, select("#", ...) do
    local candidate = TrimText(select(index, ...))
    if candidate ~= "" then
      return candidate
    end
  end
  return ""
end

local function NormalizeEnabledFlag(value, defaultValue)
  if value == nil then
    if defaultValue == nil then
      return nil
    end
    return defaultValue and true or false
  end
  if type(value) == "boolean" then
    return value
  end
  local normalized = string.lower(TrimText(value))
  if normalized == "1" or normalized == "true" or normalized == "on" or normalized == "enabled" then
    return true
  end
  if normalized == "0" or normalized == "false" or normalized == "off" or normalized == "disabled" then
    return false
  end
  if defaultValue == nil then
    return nil
  end
  return defaultValue and true or false
end

local function BuildCommProtocolMarker(version)
  local resolved = math.max(1, tonumber(version) or COMM_PROTOCOL_VERSION)
  return "P" .. tostring(resolved)
end

local function ParseCommProtocolMarker(value)
  local text = TrimText(value)
  local version = tonumber(text:match("^P(%d+)$"))
  if version and version > 0 then
    return math.floor(version)
  end
  return nil
end

local function CloneProfileSyncDomainList(domains)
  local clone = {}
  if type(domains) == "table" then
    for index, value in ipairs(domains) do
      clone[index] = tostring(value)
    end
  end
  return clone
end

local function SetProfileSyncRuntimeState(sessionId, profileName, mode, severity, message, extra)
  extra = type(extra) == "table" and extra or {}
  profileSyncRuntimeState.sessionId = TrimText(sessionId)
  profileSyncRuntimeState.profileName = TrimText(profileName)
  profileSyncRuntimeState.mode = TrimText(mode ~= "" and mode or "info")
  profileSyncRuntimeState.severity = TrimText(severity ~= "" and severity or "info")
  profileSyncRuntimeState.message = TrimText(message)
  profileSyncRuntimeState.sender = NormalizeCharacterKey(extra.sender)
  profileSyncRuntimeState.updatedAt = tonumber(extra.updatedAt) or ((type(GetServerTime) == "function" and GetServerTime()) or (time and time() or 0))
  profileSyncRuntimeState.remoteDomains = CloneProfileSyncDomainList(extra.remoteDomains)
  profileSyncRuntimeState.localDomains = CloneProfileSyncDomainList(extra.localDomains)
  profileSyncRuntimeState.detailDiff = type(extra.detailDiff) == "table" and extra.detailDiff or nil
  profileSyncRuntimeState.detailConflict = type(extra.detailConflict) == "table" and extra.detailConflict or nil
  profileSyncRuntimeState.detailSnapshotData = type(extra.detailSnapshotData) == "string" and extra.detailSnapshotData or nil
  profileSyncRuntimeState.detailPreviewSummary = type(extra.detailPreviewSummary) == "table" and extra.detailPreviewSummary or nil
  profileSyncRuntimeState.detailBaseSummary = type(extra.detailBaseSummary) == "table" and extra.detailBaseSummary or nil
  if extra.refreshEditor ~= false then
    RefreshEditor()
  end
end

local function GetNow()
  return (type(GetServerTime) == "function" and GetServerTime()) or (time and time() or 0)
end

local function BuildLocalMemberPayloadDetails()
  local className = ""
  local classFile = ""
  if type(UnitClass) == "function" then
    className, classFile = UnitClass("player")
    className = TrimText(className)
    classFile = TrimText(classFile)
  end

  local specName = ""
  local specIcon = ""
  if type(GetSpecialization) == "function" and type(GetSpecializationInfo) == "function" then
    local specIndex = GetSpecialization()
    if specIndex then
      local _, localizedSpecName, _, localizedSpecIcon = GetSpecializationInfo(specIndex)
      specName = TrimText(localizedSpecName)
      specIcon = TrimText(localizedSpecIcon)
    end
  end

  local addonVersion = ""
  if type(GetAddOnMetadata) == "function" then
    addonVersion = TrimText(GetAddOnMetadata("LidCouncil", "Version"))
  end
  if addonVersion == "" and type(C_AddOns) == "table" and type(C_AddOns.GetAddOnMetadata) == "function" then
    addonVersion = TrimText(C_AddOns.GetAddOnMetadata("LidCouncil", "Version"))
  end

  local clientVersion = ""
  if type(GetBuildInfo) == "function" then
    clientVersion = TrimText(select(1, GetBuildInfo()))
  end

  local localAutoPassEnabled = true
  if type(commApi.GetLocalAutoPassEnabled) == "function" then
    localAutoPassEnabled = commApi.GetLocalAutoPassEnabled() and true or false
  end

  return {
    addonVersion = addonVersion,
    clientVersion = clientVersion,
    className = className,
    classFile = classFile,
    specName = specName,
    specIcon = specIcon,
    localAutoPassEnabled = localAutoPassEnabled,
  }
end

local function GetMemberPayloadDetails(details, useLocalFallback)
  local incoming = type(details) == "table" and details or {}
  local fallback = useLocalFallback and BuildLocalMemberPayloadDetails() or {}
  return {
    addonVersion = FirstNonEmptyText(incoming.addonVersion, fallback.addonVersion),
    clientVersion = FirstNonEmptyText(incoming.clientVersion, fallback.clientVersion),
    className = FirstNonEmptyText(incoming.className, fallback.className),
    classFile = FirstNonEmptyText(incoming.classFile, fallback.classFile),
    specName = FirstNonEmptyText(incoming.specName, fallback.specName),
    specIcon = FirstNonEmptyText(incoming.specIcon, fallback.specIcon),
    localAutoPassEnabled = NormalizeEnabledFlag(
      incoming.localAutoPassEnabled,
      fallback.localAutoPassEnabled ~= nil and fallback.localAutoPassEnabled or true
    ),
  }
end

local function RefreshEditor()
  local editor = rawget(_G, "LIDCOUNCIL_Editor")
  if editor and editor.__lidCouncilEditor and editor.IsShown and editor:IsShown() then
    if editor.RefreshLootWorkflowSections then
      editor:RefreshLootWorkflowSections()
      if editor.RefreshLootSessionPage then
        editor:RefreshLootSessionPage()
      end
    elseif editor.Refresh then
      editor:Refresh()
    end
    if editor.aboutSection == "Developer" and editor.RefreshDeveloperSection then
      editor:RefreshDeveloperSection()
    end
  end
end

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

local function WarnCommIssue(kind, message)
  if type(commApi.Print) ~= "function" then
    return
  end
  local warningKey = tostring(kind or "?") .. "|" .. tostring(message or "")
  local now = GetCommWarningTime()
  local previousAt = tonumber(recentCommWarnings[warningKey]) or 0
  if previousAt > 0 and (now - previousAt) < COMM_WARNING_THROTTLE_SECONDS then
    return
  end
  recentCommWarnings[warningKey] = now
  commApi.Print(tostring(message or "LidCouncil comm warning.") .. " (" .. tostring(kind or "?") .. ")")
end

local function GetSendAddonResultState(result)
  if result == nil or result == true then
    return "success"
  end
  if result == 0 then
    return "success"
  end
  local sendResultEnum = type(Enum) == "table" and Enum.SendAddonMessageResult or nil
  if type(sendResultEnum) == "table" then
    if result == sendResultEnum.Success then
      return "success"
    end
    if result == sendResultEnum.AddonMessageThrottle then
      return "throttle"
    end
  end
  local resultText = tostring(result or "")
  if resultText:find("Success", 1, true) then
    return "success"
  end
  if resultText:find("AddonMessageThrottle", 1, true) or resultText:find("Throttle", 1, true) then
    return "throttle"
  end
  return "failed"
end

local function FormatSendAddonResult(result)
  if result == nil then
    return "nil"
  end
  local sendResultEnum = type(Enum) == "table" and Enum.SendAddonMessageResult or nil
  if type(sendResultEnum) == "table" then
    if result == sendResultEnum.Success then
      return "Success"
    end
    if result == sendResultEnum.AddonMessageThrottle then
      return "AddonMessageThrottle"
    end
  end
  return tostring(result)
end

local function EnsurePrefixRegistered()
  if prefixRegistered then
    return true
  end
  if type(C_ChatInfo) ~= "table" or type(C_ChatInfo.RegisterAddonMessagePrefix) ~= "function" then
    return false
  end
  prefixRegistered = C_ChatInfo.RegisterAddonMessagePrefix(COMM_PREFIX) and true or false
  return prefixRegistered
end

local function EscapeField(value)
  local text = tostring(value or "")
  text = text:gsub("%%", "%%25")
  text = text:gsub("\t", "%%09")
  text = text:gsub("\n", "%%0A")
  text = text:gsub("\r", "%%0D")
  return text
end

local function UnescapeField(value)
  local text = tostring(value or "")
  text = text:gsub("%%0D", "\r")
  text = text:gsub("%%0A", "\n")
  text = text:gsub("%%09", "\t")
  text = text:gsub("%%25", "%%")
  return text
end

local function PruneOutboundProfileTransfers()
  local now = GetNow()
  local nextOrder = {}
  for _, transferId in ipairs(outboundProfileTransferOrder) do
    local transfer = outboundProfileTransfers[transferId]
    if type(transfer) == "table" then
      local age = now - math.max(tonumber(transfer.queuedAt) or now, tonumber(transfer.completedAt) or 0, tonumber(transfer.ackedAt) or 0)
      if age <= 300 or transfer.state == "sending" or transfer.state == "queued" then
        nextOrder[#nextOrder + 1] = transferId
      else
        outboundProfileTransfers[transferId] = nil
      end
    end
  end
  outboundProfileTransferOrder = nextOrder
end

local function EnsureOutboundProfileTransfer(transferId, metadata)
  transferId = TrimText(transferId)
  if transferId == "" then
    return nil
  end
  local transfer = outboundProfileTransfers[transferId]
  if type(transfer) ~= "table" then
    transfer = {
      transferId = transferId,
      queuedAt = GetNow(),
      state = "queued",
      sentChunks = 0,
      totalChunks = 0,
      queuedMessages = 0,
    }
    outboundProfileTransfers[transferId] = transfer
    outboundProfileTransferOrder[#outboundProfileTransferOrder + 1] = transferId
  end
  metadata = type(metadata) == "table" and metadata or {}
  transfer.target = FirstNonEmptyText(metadata.target, transfer.target)
  transfer.sessionId = FirstNonEmptyText(metadata.sessionId, transfer.sessionId)
  transfer.profileName = FirstNonEmptyText(metadata.profileName, transfer.profileName)
  transfer.profileId = FirstNonEmptyText(metadata.profileId, transfer.profileId)
  transfer.mode = FirstNonEmptyText(metadata.mode, transfer.mode)
  transfer.domains = FirstNonEmptyText(metadata.domains, transfer.domains)
  transfer.transferKind = FirstNonEmptyText(metadata.transferKind, transfer.transferKind)
  transfer.subdomains = FirstNonEmptyText(metadata.subdomains, transfer.subdomains)
  if type(metadata.scopeDomains) == "table" then
    transfer.scopeDomains = metadata.scopeDomains
  end
  if type(metadata.scopeSubdomains) == "table" then
    transfer.scopeSubdomains = metadata.scopeSubdomains
  end
  if type(metadata.patchManifestSummary) == "table" then
    transfer.patchManifestSummary = metadata.patchManifestSummary
  end
  transfer.totalChunks = math.max(tonumber(transfer.totalChunks) or 0, tonumber(metadata.totalChunks) or 0)
  return transfer
end

local function BuildOutboundCommMetadata(kind, distribution, targetName, ...)
  local resolvedKind = TrimText(kind)
  if resolvedKind == "PSYNC_START" then
    return {
      transferId = TrimText(select(1, ...)),
      sessionId = TrimText(select(2, ...)),
      profileName = TrimText(select(3, ...)),
      profileId = TrimText(select(4, ...)),
      totalChunks = tonumber((select(5, ...))) or 0,
      mode = TrimText(select(6, ...)),
      domains = TrimText(select(7, ...)),
      transferKind = TrimText(select(8, ...)),
      subdomains = TrimText(select(9, ...)),
      target = TrimText(targetName),
    }
  elseif resolvedKind == "PSYNC_CHUNK" or resolvedKind == "PSYNC_END" then
    return {
      transferId = TrimText(select(1, ...)),
      target = TrimText(targetName),
    }
  elseif resolvedKind == "PSYNC_ACK" then
    return {
      transferId = TrimText(select(1, ...)),
      sessionId = TrimText(select(2, ...)),
      profileName = TrimText(select(3, ...)),
      profileId = TrimText(select(4, ...)),
      totalChunks = tonumber((select(5, ...))) or 0,
      target = TrimText(targetName),
    }
  end
  return nil
end

local function RecordOutboundItemQueued(kind, metadata)
  outboundCommStats.queuedTotal = (tonumber(outboundCommStats.queuedTotal) or 0) + 1
  outboundCommStats.lastQueuedAt = GetNow()

  local resolvedKind = TrimText(kind)
  metadata = type(metadata) == "table" and metadata or {}
  if resolvedKind == "PSYNC_START" or resolvedKind == "PSYNC_CHUNK" or resolvedKind == "PSYNC_END" then
    local transfer = EnsureOutboundProfileTransfer(metadata.transferId, metadata)
    if transfer then
      transfer.queuedMessages = (tonumber(transfer.queuedMessages) or 0) + 1
      if resolvedKind == "PSYNC_START" then
        transfer.state = "queued"
      end
    end
  end
end

local function PruneOutboundSendWindow(now)
  local cutoff = (tonumber(now) or GetNow()) - 10
  while outboundCommSendWindow[1] and (tonumber(outboundCommSendWindow[1].at) or 0) < cutoff do
    table.remove(outboundCommSendWindow, 1)
  end
end

local function RecordOutboundItemAttempt(item)
  local payload = type(item) == "table" and tostring(item.payload or "") or ""
  outboundCommStats.attemptedTotal = (tonumber(outboundCommStats.attemptedTotal) or 0) + 1
  outboundCommStats.bytesAttempted = (tonumber(outboundCommStats.bytesAttempted) or 0) + #payload
  outboundCommStats.lastAttemptAt = GetNow()
end

local function RecordOutboundItemThrottle(item, result)
  outboundCommStats.throttledTotal = (tonumber(outboundCommStats.throttledTotal) or 0) + 1
  outboundCommStats.lastThrottleAt = GetNow()
  outboundCommStats.lastFailureReason = FormatSendAddonResult(result)
end

local function RecordOutboundItemFailed(item, reason)
  outboundCommStats.failedTotal = (tonumber(outboundCommStats.failedTotal) or 0) + 1
  outboundCommStats.lastFailureAt = GetNow()
  outboundCommStats.lastFailureReason = TrimText(reason)
end

local function RecordOutboundItemSent(item)
  outboundCommStats.sentTotal = (tonumber(outboundCommStats.sentTotal) or 0) + 1
  local now = GetNow()
  outboundCommStats.lastSentAt = now
  local payloadBytes = #(type(item) == "table" and tostring(item.payload or "") or "")
  outboundCommStats.bytesSent = (tonumber(outboundCommStats.bytesSent) or 0) + payloadBytes
  outboundCommSendWindow[#outboundCommSendWindow + 1] = {
    at = now,
    bytes = payloadBytes,
  }
  PruneOutboundSendWindow(now)

  if type(item) ~= "table" then
    return
  end
  local metadata = type(item.metadata) == "table" and item.metadata or {}
  local resolvedKind = TrimText(item.kind)
  if resolvedKind ~= "PSYNC_START" and resolvedKind ~= "PSYNC_CHUNK" and resolvedKind ~= "PSYNC_END" then
    return
  end
  local transfer = EnsureOutboundProfileTransfer(metadata.transferId, metadata)
  if not transfer then
    return
  end
  if resolvedKind == "PSYNC_START" then
    transfer.state = "sending"
    transfer.startedAt = transfer.startedAt or GetNow()
  elseif resolvedKind == "PSYNC_CHUNK" then
    transfer.state = "sending"
    transfer.sentChunks = math.max(tonumber(transfer.sentChunks) or 0, tonumber(metadata.chunkIndex) or 0, (tonumber(transfer.sentChunks) or 0) + 1)
  elseif resolvedKind == "PSYNC_END" then
    transfer.state = "waiting_ack"
    transfer.completedAt = GetNow()
  end
end

local function MarkOutboundProfileTransferAcked(transferId, sender)
  local transfer = EnsureOutboundProfileTransfer(transferId, {
    target = NormalizeCharacterKey(sender),
  })
  if not transfer then
    return nil
  end
  transfer.state = "acked"
  transfer.ackedAt = GetNow()
  transfer.target = FirstNonEmptyText(sender, transfer.target)
  PruneOutboundProfileTransfers()
  return transfer
end

local function AnnotateOutboundProfileTransfer(transferId, metadata)
  return EnsureOutboundProfileTransfer(transferId, metadata)
end

local function GetSessionCommQueueStatus()
  PruneOutboundProfileTransfers()
  local byKind = {}
  for _, item in ipairs(outboundCommQueue) do
    local kind = TrimText(type(item) == "table" and item.kind or "")
    if kind ~= "" then
      byKind[kind] = (tonumber(byKind[kind]) or 0) + 1
    end
  end

  local transfers = {}
  for _, transferId in ipairs(outboundProfileTransferOrder) do
    local transfer = outboundProfileTransfers[transferId]
    if type(transfer) == "table" then
      transfers[#transfers + 1] = {
        transferId = transferId,
        target = TrimText(transfer.target),
        sessionId = TrimText(transfer.sessionId),
        profileName = TrimText(transfer.profileName),
        profileId = TrimText(transfer.profileId),
        mode = TrimText(transfer.mode),
        state = TrimText(transfer.state),
        sentChunks = math.max(0, tonumber(transfer.sentChunks) or 0),
        totalChunks = math.max(0, tonumber(transfer.totalChunks) or 0),
        queuedMessages = math.max(0, tonumber(transfer.queuedMessages) or 0),
        queuedAt = tonumber(transfer.queuedAt) or 0,
        startedAt = tonumber(transfer.startedAt) or 0,
        completedAt = tonumber(transfer.completedAt) or 0,
        ackedAt = tonumber(transfer.ackedAt) or 0,
      }
    end
  end
  local profileSnapshotBatch = type(profileSyncHelpers) == "table"
    and type(profileSyncHelpers.GetProfileSnapshotBatchStatus) == "function"
    and profileSyncHelpers.GetProfileSnapshotBatchStatus()
    or {}
  local batchPendingTargets = math.max(0, tonumber(type(profileSnapshotBatch) == "table" and profileSnapshotBatch.pendingTargets) or 0)
  local batchRunning = type(profileSnapshotBatch) == "table" and profileSnapshotBatch.running == true
  local now = GetNow()
  PruneOutboundSendWindow(now)
  local rollingBytes = 0
  for _, sample in ipairs(outboundCommSendWindow) do
    rollingBytes = rollingBytes + math.max(0, tonumber(sample.bytes) or 0)
  end
  local rollingWindowSeconds = 10

  return {
    running = outboundCommQueueRunning and true or false,
    pending = #outboundCommQueue,
    busy = outboundCommQueueRunning or #outboundCommQueue > 0 or batchPendingTargets > 0 or batchRunning,
    intervalSeconds = GetCommOutboundIntervalSeconds(),
    byKind = byKind,
    queuedTotal = tonumber(outboundCommStats.queuedTotal) or 0,
    attemptedTotal = tonumber(outboundCommStats.attemptedTotal) or 0,
    sentTotal = tonumber(outboundCommStats.sentTotal) or 0,
    throttledTotal = tonumber(outboundCommStats.throttledTotal) or 0,
    failedTotal = tonumber(outboundCommStats.failedTotal) or 0,
    bytesAttempted = tonumber(outboundCommStats.bytesAttempted) or 0,
    bytesSent = tonumber(outboundCommStats.bytesSent) or 0,
    sendRatePerSecond = #outboundCommSendWindow / rollingWindowSeconds,
    byteRatePerSecond = rollingBytes / rollingWindowSeconds,
    lastQueuedAt = tonumber(outboundCommStats.lastQueuedAt) or 0,
    lastAttemptAt = tonumber(outboundCommStats.lastAttemptAt) or 0,
    lastSentAt = tonumber(outboundCommStats.lastSentAt) or 0,
    lastThrottleAt = tonumber(outboundCommStats.lastThrottleAt) or 0,
    lastFailureAt = tonumber(outboundCommStats.lastFailureAt) or 0,
    lastFailureReason = TrimText(outboundCommStats.lastFailureReason),
    profileTransfers = transfers,
    profileSnapshotBatch = profileSnapshotBatch,
  }
end

local function GetProfileSyncEstimate(byteCount)
  local bytes = math.max(0, tonumber(byteCount) or 0)
  local chunkSize = math.max(1, tonumber(GetProfileSnapshotChunkSize()) or DEFAULT_PROFILE_SNAPSHOT_CHUNK_SIZE)
  local chunks = math.max(1, math.ceil(bytes / chunkSize))
  local messages = chunks + 2
  local intervalSeconds = math.max(0, tonumber(GetCommOutboundIntervalSeconds()) or DEFAULT_COMM_OUTBOUND_INTERVAL_SECONDS)
  return {
    bytes = bytes,
    chunkSize = chunkSize,
    chunks = chunks,
    messages = messages,
    intervalSeconds = intervalSeconds,
    seconds = messages * intervalSeconds,
  }
end

local function CancelQueuedProfileSyncForSession(sessionId)
  local resolvedSessionId = TrimText(sessionId)
  if resolvedSessionId == "" then
    return 0
  end

  local removed = 0
  local keptQueue = {}
  for _, item in ipairs(outboundCommQueue) do
    local kind = TrimText(type(item) == "table" and item.kind or "")
    local metadata = type(item) == "table" and type(item.metadata) == "table" and item.metadata or {}
    local transfer = outboundProfileTransfers[TrimText(metadata.transferId)]
    local transferSessionId = TrimText(metadata.sessionId)
    if transferSessionId == "" and type(transfer) == "table" then
      transferSessionId = TrimText(transfer.sessionId)
    end
    local isProfileSync = kind == "PSYNC_START" or kind == "PSYNC_CHUNK" or kind == "PSYNC_END"
    if isProfileSync and transferSessionId == resolvedSessionId then
      removed = removed + 1
      if type(transfer) == "table" then
        transfer.state = "cancelled"
        transfer.completedAt = GetNow()
      end
    else
      keptQueue[#keptQueue + 1] = item
    end
  end
  outboundCommQueue = keptQueue
  return removed
end

local function BuildPayload(kind, ...)
  local fields = { EscapeField(kind) }
  local args = { ... }
  for i = 1, #args do
    fields[#fields + 1] = EscapeField(args[i])
  end
  return table.concat(fields, "\t")
end

local function SendAddonPayloadNow(kind, distribution, targetName, payload)
  local ok, result = pcall(C_ChatInfo.SendAddonMessage, COMM_PREFIX, payload, distribution, targetName)
  if not ok then
    WarnCommIssue(kind, "LidCouncil could not send an addon message.")
    return false, "failed", "SendAddonMessage failed."
  end
  local state = GetSendAddonResultState(result)
  if state == "success" then
    return true, "success", result
  end
  if state == "throttle" then
    return false, "throttled", result
  end
  return false, "failed", result
end

local function GetOutboundCommPriority(kind)
  local resolvedKind = TrimText(kind)
  if resolvedKind == "START"
      or resolvedKind == "BEGIN"
      or resolvedKind == "JOIN"
      or resolvedKind == "JACK"
      or resolvedKind == "MEMBER"
      or resolvedKind == "STOP"
      or resolvedKind == "DECLINE"
      or resolvedKind == "LOCALAP"
      or resolvedKind == "APNOTICE"
      or resolvedKind == "PSTATE"
      or resolvedKind == "PSYNC_ACK" then
    return 1
  end
  if resolvedKind == "PSUB_REQ"
      or resolvedKind == "PSUB_STATE"
      or resolvedKind == "PSUBPATCH_REQ"
      or resolvedKind == "PSUBPATCH_STATE" then
    return 4
  end
  if resolvedKind == "CHOICE"
      or resolvedKind == "RCHOICE"
      or resolvedKind == "VOTE"
      or resolvedKind == "AWARD"
      or resolvedKind == "UNASSIGN"
      or resolvedKind == "TRADE"
      or resolvedKind == "ITEM_OPEN"
      or resolvedKind == "ITEM_REPORT" then
    return 1
  end
  if resolvedKind == "LL_ADD_REQ"
      or resolvedKind == "LL_ADD"
      or resolvedKind == "LL_DEL_REQ"
      or resolvedKind == "LL_DEL"
      or resolvedKind == "LL_MA_REQ"
      or resolvedKind == "LL_MA"
      or resolvedKind == "LL_NOTE_REQ"
      or resolvedKind == "LL_NOTE"
      or resolvedKind == "LL_CHAR_DEL_REQ"
      or resolvedKind == "LL_CHAR_DEL" then
    return 2
  end
  if resolvedKind == "PSYNC_START"
      or resolvedKind == "PSYNC_CHUNK"
      or resolvedKind == "PSYNC_END"
      or resolvedKind == "PDELTA"
      or resolvedKind == "PDELTA_REQ" then
    return 30
  end
  if resolvedKind == "PDELTA_ACK" or resolvedKind == "PDELTA_NACK" then
    return 3
  end
  return 10
end

local function FlushOutboundCommQueue()
  local item = table.remove(outboundCommQueue, 1)
  local wasThrottled = false
  if item then
    RecordOutboundItemAttempt(item)
    local ok, state, result = SendAddonPayloadNow(item.kind, item.distribution, item.targetName, item.payload)
    if ok then
      RecordOutboundItemSent(item)
    elseif state == "throttled" then
      wasThrottled = true
      RecordOutboundItemThrottle(item, result)
      table.insert(outboundCommQueue, 1, item)
      WarnCommIssue(item.kind, "LidCouncil addon message throttle detected; requeueing message.")
    else
      RecordOutboundItemFailed(item, FormatSendAddonResult(result))
      WarnCommIssue(item.kind, "LidCouncil addon message failed: " .. FormatSendAddonResult(result))
    end
  end

  if #outboundCommQueue > 0 then
    if type(C_Timer) == "table" and type(C_Timer.After) == "function" then
      local delay = wasThrottled and math.max(1.0, GetCommOutboundIntervalSeconds()) or GetCommOutboundIntervalSeconds()
      C_Timer.After(delay, FlushOutboundCommQueue)
    else
      FlushOutboundCommQueue()
    end
  else
    outboundCommQueueRunning = false
  end
end

local function QueueAddonPayload(kind, distribution, targetName, payload, metadata)
  local item = {
    kind = kind,
    distribution = distribution,
    targetName = targetName,
    payload = payload,
    priority = GetOutboundCommPriority(kind),
    metadata = type(metadata) == "table" and metadata or nil,
  }
  local insertAt = #outboundCommQueue + 1
  for index, queued in ipairs(outboundCommQueue) do
    if (tonumber(item.priority) or 10) < (tonumber(queued.priority) or 10) then
      insertAt = index
      break
    end
  end
  table.insert(outboundCommQueue, insertAt, item)
  RecordOutboundItemQueued(kind, item.metadata)

  if not outboundCommQueueRunning then
    outboundCommQueueRunning = true
    if type(C_Timer) == "table" and type(C_Timer.After) == "function" then
      C_Timer.After(0.01, FlushOutboundCommQueue)
    else
      FlushOutboundCommQueue()
    end
  end
  return true
end

local function ParsePayload(message)
  local fields = {}
  local startIndex = 1
  local text = tostring(message or "")
  while true do
    local tabIndex = string.find(text, "\t", startIndex, true)
    if not tabIndex then
      fields[#fields + 1] = UnescapeField(text:sub(startIndex))
      break
    end
    fields[#fields + 1] = UnescapeField(text:sub(startIndex, tabIndex - 1))
    startIndex = tabIndex + 1
  end
  return fields
end

local function BuildRollOptionFields(options)
  local fields = {}
  local count = 0
  if type(options) == "table" then
    for _, option in ipairs(options) do
      if type(option) == "table" then
        count = count + 1
        local color = option.color or {}
        fields[#fields + 1] = tostring(option.key or "")
        fields[#fields + 1] = tostring(option.label or option.key or "")
        fields[#fields + 1] = tostring(tonumber(color[1]) or 0.82)
        fields[#fields + 1] = tostring(tonumber(color[2]) or 0.82)
        fields[#fields + 1] = tostring(tonumber(color[3]) or 0.86)
        fields[#fields + 1] = tostring(option.icon or "")
      end
    end
  end
  return tostring(count), unpack(fields)
end

local function ParseRollOptionFields(fields, startIndex)
  local options = {}
  local count = tonumber(fields[startIndex] or 0) or 0
  local cursor = (tonumber(startIndex) or 1) + 1
  for i = 1, count do
    local key = tostring(fields[cursor] or "")
    local label = tostring(fields[cursor + 1] or key or "")
    local r = tonumber(fields[cursor + 2]) or 0.82
    local g = tonumber(fields[cursor + 3]) or 0.82
    local b = tonumber(fields[cursor + 4]) or 0.86
    local icon = tostring(fields[cursor + 5] or "")
    cursor = cursor + 6
    if label ~= "" then
      options[#options + 1] = {
        key = key,
        label = label,
        color = { r, g, b },
        icon = icon ~= "" and icon or nil,
      }
    end
  end
  return options
end

local function GetGroupDistribution()
  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 NormalizeRealmName(value)
  local text = TrimText(value)
  text = text:gsub("%s+", "")
  return text
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 SplitCharacterKey(value)
  local key = NormalizeCharacterKey(value)
  local name, realm = key:match("^([^%-]+)%-(.+)$")
  if name then
    return string.lower(name), string.lower(NormalizeRealmName(realm))
  end
  return string.lower(key), ""
end

local function AreCharacterKeysSame(left, right)
  local leftKey = NormalizeCharacterKey(left)
  local rightKey = NormalizeCharacterKey(right)
  if leftKey == "" or rightKey == "" then
    return false
  end
  if string.lower(leftKey) == string.lower(rightKey) then
    return true
  end

  local leftName, leftRealm = SplitCharacterKey(leftKey)
  local rightName, rightRealm = SplitCharacterKey(rightKey)
  if leftName == "" or rightName == "" or leftName ~= rightName then
    return false
  end
  return leftRealm == "" or rightRealm == "" or leftRealm == rightRealm
end

local function BuildCharacterKeyFromUnit(unit)
  if TrimText(unit) == "" then
    return ""
  end

  local name, realm = nil, nil
  if type(UnitFullName) == "function" then
    name, realm = UnitFullName(unit)
  end
  if not name and type(UnitName) == "function" then
    name = UnitName(unit)
  end

  local resolvedName = TrimText(name)
  if resolvedName == "" then
    return ""
  end

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

local function CollectGroupCharacterKeys()
  local keys = {}
  local seen = {}

  local function AddUnit(unit)
    local key = BuildCharacterKeyFromUnit(unit)
    if key ~= "" and not seen[key] then
      seen[key] = true
      keys[#keys + 1] = key
    end
  end

  AddUnit("player")

  if type(IsInRaid) == "function" and IsInRaid() and type(GetNumGroupMembers) == "function" then
    local count = tonumber(GetNumGroupMembers()) or 0
    for index = 1, count do
      AddUnit("raid" .. tostring(index))
    end
  elseif type(IsInGroup) == "function" and IsInGroup() and type(GetNumSubgroupMembers) == "function" then
    local count = tonumber(GetNumSubgroupMembers()) or 0
    for index = 1, count do
      AddUnit("party" .. tostring(index))
    end
  end

  table.sort(keys)
  return keys
end

local function IsCurrentGroupCharacter(characterKey)
  local resolvedKey = NormalizeCharacterKey(characterKey)
  if resolvedKey == "" then
    return false
  end
  for _, groupKey in ipairs(CollectGroupCharacterKeys()) do
    if AreCharacterKeysSame(groupKey, resolvedKey) then
      return true
    end
  end
  return false
end

local function NormalizeSenderKey(sender)
  local senderKey = NormalizeCharacterKey(sender)
  if senderKey ~= "" and not senderKey:find("-", 1, true) then
    local realmName = GetLocalRealmName()
    if realmName ~= "" then
      senderKey = NormalizeCharacterKey(senderKey .. "-" .. realmName)
    end
  end
  return senderKey
end

local function IsClaimedActorSenderMatch(sender, claimedCharacterKey)
  local senderKey = NormalizeSenderKey(sender)
  local claimedKey = NormalizeCharacterKey(claimedCharacterKey)
  if senderKey == "" or claimedKey == "" then
    return false
  end
  return AreCharacterKeysSame(senderKey, claimedKey)
end

local function IsActiveSessionHostSender(activeSession, sender)
  return AreCharacterKeysSame(type(activeSession) == "table" and activeSession.host or "", NormalizeSenderKey(sender))
end

local function IsJoinedSessionCharacter(activeSession, characterKey)
  local resolvedKey = NormalizeCharacterKey(characterKey)
  if resolvedKey == "" then
    return false
  end
  if AreCharacterKeysSame(type(activeSession) == "table" and activeSession.host or "", resolvedKey) then
    return true
  end
  local member = type(activeSession) == "table" and type(activeSession.members) == "table" and activeSession.members[resolvedKey] or nil
  if type(member) == "table" and tostring(member.status or "JOINED") == "JOINED" then
    return true
  end
  if type(activeSession) == "table" and type(activeSession.members) == "table" then
    for storedKey, storedMember in pairs(activeSession.members) do
      if AreCharacterKeysSame(storedKey, resolvedKey) and type(storedMember) == "table" and tostring(storedMember.status or "JOINED") == "JOINED" then
        return true
      end
    end
  end
  return false
end

local function DoesActiveSessionMatch(activeSession, activeProfileName, sessionId, profileName)
  if type(activeSession) ~= "table" then
    return false
  end
  if TrimText(activeSession.id) ~= TrimText(sessionId) then
    return false
  end
  local expectedProfileName = TrimText(profileName)
  if expectedProfileName == "" then
    return true
  end
  if TrimText(activeProfileName) == expectedProfileName then
    return true
  end
  if TrimText(activeSession.profileName) == expectedProfileName then
    return true
  end
  if TrimText(activeSession.remoteProfileName) == expectedProfileName then
    return true
  end
  return false
end

local function SendComm(kind, channel, target, ...)
  if not EnsurePrefixRegistered() then
    return false, "Addon message prefix is not registered."
  end
  if type(C_ChatInfo) ~= "table" or type(C_ChatInfo.SendAddonMessage) ~= "function" then
    return false, "Addon message API is unavailable."
  end

  local distribution = TrimText(channel)
  if distribution == "" then
    return false, "No addon message channel was provided."
  end

  local targetName = nil
  if distribution == "WHISPER" then
    targetName = NormalizeCharacterKey(target)
    if targetName == "" then
      return false, "Whisper target is missing."
    end
  end

  local metadata = BuildOutboundCommMetadata(kind, distribution, targetName, ...)
  if type(metadata) == "table" and TrimText(kind) == "PSYNC_CHUNK" then
    metadata.chunkIndex = tonumber((select(2, ...))) or 0
  end
  local payload = BuildPayload(kind, ...)
  if #payload > COMM_SAFE_PAYLOAD_LIMIT then
    WarnCommIssue(kind, "Skipped an oversized LidCouncil addon message.")
    return false, "Payload exceeds safe addon message length."
  end

  return QueueAddonPayload(kind, distribution, targetName, payload, metadata)
end

local CreateProfileSyncHelpers = INTERNAL_API.LIDCOUNCIL_CreateLootSessionProfileSyncHelpers_Impl
if type(CreateProfileSyncHelpers) ~= "function" then
  error("LidCouncil missing loot session profile sync helpers.")
end

local CreateProfileSyncStateStore = INTERNAL_API.LIDCOUNCIL_CreateProfileSyncStateStore_Impl
if type(CreateProfileSyncStateStore) ~= "function" then
  error("LidCouncil missing profile sync state store helpers.")
end

local profileSyncStateStore = CreateProfileSyncStateStore({
  RootDB = commApi.RootDB,
  TrimText = TrimText,
  DeepCopy = DeepCopy,
  NormalizeCharacterKey = NormalizeCharacterKey,
  ExportLootProfileSnapshot = commApi.ExportLootProfileSnapshot,
  ImportLootProfileSnapshot = commApi.ImportLootProfileSnapshot,
})

profileSyncHelpers = CreateProfileSyncHelpers({
  RootDB = commApi.RootDB,
  Print = commApi.Print,
  TrimText = TrimText,
  NormalizeCharacterKey = NormalizeCharacterKey,
  FirstNonEmptyText = FirstNonEmptyText,
  CloneProfileSyncDomainList = CloneProfileSyncDomainList,
  SetProfileSyncRuntimeState = SetProfileSyncRuntimeState,
  profileSyncRuntimeState = profileSyncRuntimeState,
  PROFILE_REVISION_DOMAINS = PROFILE_REVISION_DOMAINS,
  PROFILE_REVISION_DOMAIN_LABELS = PROFILE_REVISION_DOMAIN_LABELS,
  GetCharacterKey = commApi.GetCharacterKey,
  GetActiveLootSession = commApi.GetActiveLootSession,
  GetLootProfileId = commApi.GetLootProfileId,
  FindLootProfileNameById = commApi.FindLootProfileNameById,
  GetLootProfileRevisionSummary = commApi.GetLootProfileRevisionSummary,
  ExportLootProfileSnapshot = commApi.ExportLootProfileSnapshot,
  ExportLootProfileDomainSnapshot = commApi.ExportLootProfileDomainSnapshot,
  ExportLootProfileSubdomainSnapshot = commApi.ExportLootProfileSubdomainSnapshot,
  ExportLootProfileSubdomainPatch = commApi.ExportLootProfileSubdomainPatch,
  ExportLootProfileConflictRowPatch = commApi.ExportLootProfileConflictRowPatch,
  ExportLootProfileSubdomainPatchManifest = commApi.ExportLootProfileSubdomainPatchManifest,
  ImportLootProfileSnapshot = commApi.ImportLootProfileSnapshot,
  ImportLootProfileDomainSnapshot = commApi.ImportLootProfileDomainSnapshot,
  ImportLootProfileSubdomainSnapshot = commApi.ImportLootProfileSubdomainSnapshot,
  ImportLootProfileSubdomainPatch = commApi.ImportLootProfileSubdomainPatch,
  ImportLootProfileConflictRowPatch = commApi.ImportLootProfileConflictRowPatch,
  PreviewLootProfileSnapshot = commApi.PreviewLootProfileSnapshot,
  PreviewLootProfileDomainSnapshot = commApi.PreviewLootProfileDomainSnapshot,
  PreviewLootProfileSubdomainSnapshot = commApi.PreviewLootProfileSubdomainSnapshot,
  PreviewLootProfileSubdomainPatch = commApi.PreviewLootProfileSubdomainPatch,
  PreviewLootProfileConflictRowPatch = commApi.PreviewLootProfileConflictRowPatch,
  PreviewLootProfileSubdomainPatchManifest = commApi.PreviewLootProfileSubdomainPatchManifest,
  BuildLootProfileSnapshotDiff = commApi.BuildLootProfileSnapshotDiff,
  BuildLootProfileDomainSnapshotDiff = commApi.BuildLootProfileDomainSnapshotDiff,
  BuildLootProfileSubdomainSnapshotDiff = commApi.BuildLootProfileSubdomainSnapshotDiff,
  BuildLootProfileSubdomainPatchDiff = commApi.BuildLootProfileSubdomainPatchDiff,
  BuildLootProfileConflictRowPatchDiff = commApi.BuildLootProfileConflictRowPatchDiff,
  BuildLootProfileTransferConflictPreview = commApi.BuildLootProfileTransferConflictPreview,
  ApplyLootProfileTransferConflictResolution = commApi.ApplyLootProfileTransferConflictResolution,
  GetLootProfileSubchecksumManifest = commApi.GetLootProfileSubchecksumManifest,
  NormalizeLootProfileSubdomainList = commApi.NormalizeLootProfileSubdomainList,
  CloneLootProfileSubdomainList = commApi.CloneLootProfileSubdomainList,
  FilterLootProfilePatchCapableSubdomains = commApi.FilterLootProfilePatchCapableSubdomains,
  GetLootProfileSubdomainKeysForDomain = commApi.GetLootProfileSubdomainKeysForDomain,
  GetLootProfileSubdomainLabel = commApi.GetLootProfileSubdomainLabel,
  GetLootProfileSubdomainParentDomain = commApi.GetLootProfileSubdomainParentDomain,
  ResolveLootProfileDomainsForSubdomains = commApi.ResolveLootProfileDomainsForSubdomains,
  SendComm = SendComm,
  MarkOutboundProfileTransferAcked = MarkOutboundProfileTransferAcked,
  AnnotateOutboundProfileTransfer = AnnotateOutboundProfileTransfer,
  IsActiveSessionHostSender = IsActiveSessionHostSender,
  IsJoinedSessionCharacter = IsJoinedSessionCharacter,
  IsCurrentGroupCharacter = IsCurrentGroupCharacter,
  DoesActiveSessionMatch = DoesActiveSessionMatch,
  RefreshEditor = RefreshEditor,
  ResolveOptionalApiFunction = ResolveOptionalApiFunction,
  GetLootProfile = GetLootProfile,
  GetLootProfileRole = commApi.GetLootProfileRole,
  GetLootProfileMaster = commApi.GetLootProfileMaster,
  NormalizeLootRollOptions = NormalizeLootRollOptions,
  UpsertLootSessionMember = commApi.UpsertLootSessionMember,
  RememberPersistedMemberBaseline = profileSyncStateStore.RememberPersistedMemberBaseline,
  GetPersistedMemberBaseline = profileSyncStateStore.GetPersistedMemberBaseline,
  RecordRepairHistory = profileSyncStateStore.RecordRepairHistory,
  GetRepairHistory = profileSyncStateStore.GetRepairHistory,
  PushUndoSnapshot = profileSyncStateStore.PushUndoSnapshot,
  GetUndoStatus = profileSyncStateStore.GetUndoStatus,
  UndoLastRepair = profileSyncStateStore.UndoLastRepair,
  GetProfileSnapshotChunkSize = GetProfileSnapshotChunkSize,
  GetCommOutboundIntervalSeconds = GetCommOutboundIntervalSeconds,
  GetCommOutboundQueueStatus = GetSessionCommQueueStatus,
})

local CreateFlowHelpers = INTERNAL_API.LIDCOUNCIL_CreateLootSessionFlowHelpers_Impl
if type(CreateFlowHelpers) ~= "function" then
  error("LidCouncil missing loot session flow helpers.")
end

local flowHelpers = CreateFlowHelpers({
  Print = commApi.Print,
  TrimText = TrimText,
  NormalizeCharacterKey = NormalizeCharacterKey,
  NormalizeSenderKey = NormalizeSenderKey,
  NormalizeEnabledFlag = NormalizeEnabledFlag,
  FirstNonEmptyText = FirstNonEmptyText,
  GetCharacterKey = commApi.GetCharacterKey,
  AddCharacterToList = commApi.AddCharacterToList,
  StartLootSession = commApi.StartLootSession,
  ActivateLootSession = commApi.ActivateLootSession,
  GetActiveLootSession = commApi.GetActiveLootSession,
  GetLootProfile = GetLootProfile,
  StopLootSession = commApi.StopLootSession,
  JoinLootSession = commApi.JoinLootSession,
  UpsertLootSessionMember = commApi.UpsertLootSessionMember,
  RemoveLootSessionMember = commApi.RemoveLootSessionMember,
  FinishLootSession = commApi.FinishLootSession,
  GetLootProfileRole = commApi.GetLootProfileRole,
  GetLootProfileMaster = commApi.GetLootProfileMaster,
  SyncLootProfilePermissionState = commApi.SyncLootProfilePermissionState,
  EnsureLootProfileMemberRole = commApi.EnsureLootProfileMemberRole,
  SetLocalAutoPassEnabled = commApi.SetLocalAutoPassEnabled,
  GetLootProfileId = commApi.GetLootProfileId,
  FindLootProfileNameById = commApi.FindLootProfileNameById,
  GetLootProfileRevisionSummary = commApi.GetLootProfileRevisionSummary,
  GetMemberPayloadDetails = GetMemberPayloadDetails,
  NormalizeLootRollOptions = NormalizeLootRollOptions,
  RefreshEditor = RefreshEditor,
  ParseRollOptionFields = ParseRollOptionFields,
  BuildRollOptionFields = BuildRollOptionFields,
  GetGroupDistribution = GetGroupDistribution,
  CollectGroupCharacterKeys = CollectGroupCharacterKeys,
  IsCurrentGroupCharacter = IsCurrentGroupCharacter,
  IsClaimedActorSenderMatch = IsClaimedActorSenderMatch,
  IsActiveSessionHostSender = IsActiveSessionHostSender,
  IsJoinedSessionCharacter = IsJoinedSessionCharacter,
  DoesActiveSessionMatch = DoesActiveSessionMatch,
  SendComm = SendComm,
  SetProfileSyncRuntimeState = SetProfileSyncRuntimeState,
  BuildEmptyRevisionSummary = profileSyncHelpers.BuildEmptyRevisionSummary,
  NormalizeRevisionSummary = profileSyncHelpers.NormalizeRevisionSummary,
  CompareProfileRevisionSummaries = profileSyncHelpers.CompareProfileRevisionSummaries,
  FormatProfileRevisionDomains = profileSyncHelpers.FormatProfileRevisionDomains,
  RememberSharedReplicaPlaceholder = profileSyncHelpers.RememberSharedReplicaPlaceholder,
  ResolveLocalSharedProfileName = profileSyncHelpers.ResolveLocalSharedProfileName,
  PrepareProfileSnapshotTransfer = profileSyncHelpers.PrepareProfileSnapshotTransfer,
  BuildPreparedProfileRepairTransfers = profileSyncHelpers.BuildPreparedProfileRepairTransfers,
  HasLoadedPatchManifestSubdomains = profileSyncHelpers.HasLoadedPatchManifestSubdomains,
  FilterLootProfilePatchCapableSubdomains = commApi.FilterLootProfilePatchCapableSubdomains,
  SendProfileSubdomainPatchManifest = profileSyncHelpers.SendProfileSubdomainPatchManifest,
  SendPreparedProfileSnapshot = profileSyncHelpers.SendPreparedProfileSnapshot,
  SendProfileSnapshot = profileSyncHelpers.SendProfileSnapshot,
  SendPreparedProfileSnapshotBatch = profileSyncHelpers.SendPreparedProfileSnapshotBatch,
  SendProfileSnapshotBatch = profileSyncHelpers.SendProfileSnapshotBatch,
  SendLocalProfileStateToHost = profileSyncHelpers.SendLocalProfileStateToHost,
  DelayProfileSnapshotBatchForJoins = profileSyncHelpers.DelayProfileSnapshotBatchForJoins,
  ApplyIncomingLootProfileDelta = commApi.ApplyIncomingLootProfileDelta,
  GetActiveProfileSyncCandidates = profileSyncHelpers.GetActiveProfileSyncCandidates,
  RecordRepairHistory = profileSyncStateStore.RecordRepairHistory,
  ResolveOptionalApiFunction = ResolveOptionalApiFunction,
  COMM_PROTOCOL_VERSION = COMM_PROTOCOL_VERSION,
  BuildCommProtocolMarker = BuildCommProtocolMarker,
  ParseCommProtocolMarker = ParseCommProtocolMarker,
})

local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:RegisterEvent("CHAT_MSG_ADDON")
eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
eventFrame:SetScript("OnEvent", function(_, event, ...)
  if event == "PLAYER_LOGIN" then
    EnsurePrefixRegistered()
    if type(flowHelpers.EnsureSessionPopup) == "function" then
      flowHelpers.EnsureSessionPopup()
    end
    if type(profileSyncHelpers.EnsureProfileSyncPopup) == "function" then
      profileSyncHelpers.EnsureProfileSyncPopup()
    end
    return
  end

  if event == "GROUP_ROSTER_UPDATE" or event == "PLAYER_ENTERING_WORLD" then
    if type(flowHelpers.HandleGroupRosterChanged) == "function" then
      flowHelpers.HandleGroupRosterChanged()
    end
    return
  end

  if event == "CHAT_MSG_ADDON" then
    local prefix, message, _, sender = ...
    if prefix ~= COMM_PREFIX then
      return
    end
    local fields = ParsePayload(message)
    local kind = fields[1]
    if kind == "START" then
      flowHelpers.HandleStartInvite(fields, sender)
    elseif kind == "BEGIN" then
      flowHelpers.HandleBegin(fields, sender)
    elseif kind == "JOIN" then
      flowHelpers.HandleJoin(fields, sender)
    elseif kind == "JACK" then
      flowHelpers.HandleJoinAck(fields, sender)
    elseif kind == "MEMBER" then
      flowHelpers.HandleMember(fields, sender)
    elseif kind == "LOCALAP" then
      flowHelpers.HandleLocalAutoPass(fields, sender)
    elseif kind == "APNOTICE" then
      flowHelpers.HandleAutoPassNotice(fields, sender)
    elseif kind == "DECLINE" then
      flowHelpers.HandleDecline(fields, sender)
    elseif kind == "PSTATE" then
      flowHelpers.HandleProfileState(fields, sender)
    elseif kind == "STOP" then
      flowHelpers.HandleStop(fields, sender)
    elseif kind == "PSYNC_START" then
      profileSyncHelpers.HandleProfileSyncStart(fields, sender)
    elseif kind == "PSYNC_REQ" then
      profileSyncHelpers.HandleProfileSyncRequest(fields, sender)
    elseif kind == "PSYNC_CHUNK" then
      profileSyncHelpers.HandleProfileSyncChunk(fields, sender)
    elseif kind == "PSYNC_END" then
      profileSyncHelpers.HandleProfileSyncEnd(fields, sender)
    elseif kind == "PSYNC_ACK" then
      profileSyncHelpers.HandleProfileSyncAck(fields, sender)
    elseif kind == "PSUB_REQ" then
      profileSyncHelpers.HandleProfileSubchecksumRequest(fields, sender)
    elseif kind == "PSUB_STATE" then
      profileSyncHelpers.HandleProfileSubchecksumState(fields, sender)
    elseif kind == "PSUBPATCH_REQ" then
      profileSyncHelpers.HandleProfileSubdomainPatchManifestRequest(fields, sender)
    elseif kind == "PSUBPATCH_STATE" then
      profileSyncHelpers.HandleProfileSubdomainPatchManifestState(fields, sender)
    elseif kind == "PDELTA_REQ" then
      flowHelpers.HandleProfileDeltaRequest(fields, sender)
    elseif kind == "PDELTA" then
      flowHelpers.HandleProfileDelta(fields, sender)
    elseif kind == "PDELTA_ACK" then
      flowHelpers.HandleProfileDeltaAck(fields, sender)
    elseif kind == "PDELTA_NACK" then
      flowHelpers.HandleProfileDeltaNack(fields, sender)
    end
    return
  end
end)

PublishApi("LIDCOUNCIL_StartLootSessionFlow", flowHelpers.StartLootSessionFlow, { global = false })
PublishApi("LIDCOUNCIL_ActivatePreparedLootSessionFlow", flowHelpers.ActivatePreparedLootSessionFlow, { global = false })
PublishApi("LIDCOUNCIL_StopLootSessionFlow", flowHelpers.StopLootSessionFlow, { global = false })
PublishApi("LIDCOUNCIL_SendActiveSessionInviteFlow", flowHelpers.SendActiveSessionInviteFlow, { global = false })
PublishApi("LIDCOUNCIL_PushActiveProfileSnapshotFlow", flowHelpers.PushActiveProfileSnapshotFlow, { global = false })
PublishApi("LIDCOUNCIL_BroadcastActiveProfileSnapshotFlow", flowHelpers.BroadcastActiveProfileSnapshotFlow, { global = false })
PublishApi("LIDCOUNCIL_BroadcastActiveProfileDeltaFlow", flowHelpers.BroadcastActiveProfileDeltaFlow, { global = false })
PublishApi("LIDCOUNCIL_QueueActiveProfileCheckpointFlow", flowHelpers.QueueActiveProfileCheckpointFlow, { global = false })
PublishApi("LIDCOUNCIL_RequestActiveProfileSubchecksumFlow", flowHelpers.RequestActiveProfileSubchecksumFlow, { global = false })
PublishApi("LIDCOUNCIL_RequestActiveProfileSnapshotFlow", flowHelpers.RequestActiveProfileSnapshotFlow, { global = false })
PublishApi("LIDCOUNCIL_BroadcastSessionMemberUpdate", flowHelpers.BroadcastMember, { global = false })
PublishApi("LIDCOUNCIL_RemoveActiveSessionMemberFlow", flowHelpers.RemoveActiveSessionMemberFlow, { global = false })
PublishApi("LIDCOUNCIL_GetActiveProfileSyncCandidates", profileSyncHelpers.GetActiveProfileSyncCandidates, { global = false })
PublishApi("LIDCOUNCIL_GetActiveProfileSyncStatus", profileSyncHelpers.GetActiveProfileSyncStatus, { global = false })
PublishApi("LIDCOUNCIL_GetActiveProfileSyncConflictPreview", profileSyncHelpers.GetActiveProfileSyncConflictPreview, { global = false })
PublishApi("LIDCOUNCIL_ApplyActiveProfilePreviewConflictResolution", profileSyncHelpers.ApplyActiveProfilePreviewConflictResolution, { global = false })
PublishApi("LIDCOUNCIL_GetProfileRepairHistory", profileSyncStateStore.GetRepairHistory, { global = false })
PublishApi("LIDCOUNCIL_GetProfileUndoStatus", profileSyncStateStore.GetUndoStatus, { global = false })
PublishApi("LIDCOUNCIL_UndoLastProfileRepair", profileSyncStateStore.UndoLastRepair, { global = false })
PublishApi("LIDCOUNCIL_GetSessionCommQueueStatus", GetSessionCommQueueStatus, { global = false })
PublishApi("LIDCOUNCIL_GetProfileSyncEstimate", GetProfileSyncEstimate, { global = false })
PublishApi("LIDCOUNCIL_GetProfileSyncTuning", GetProfileSyncTuning, { global = false })
PublishApi("LIDCOUNCIL_SetProfileSyncTuning", SetProfileSyncTuning, { global = false })
PublishApi("LIDCOUNCIL_ResetProfileSyncTuning", ResetProfileSyncTuning, { global = false })
PublishApi("LIDCOUNCIL_DelayProfileSnapshotBatchForJoins", profileSyncHelpers.DelayProfileSnapshotBatchForJoins, { global = false })
PublishApi("LIDCOUNCIL_CancelQueuedProfileSyncForSession", CancelQueuedProfileSyncForSession, { global = false })
PublishApi("LIDCOUNCIL_CancelProfileSnapshotBatchForSession", profileSyncHelpers.CancelProfileSnapshotBatchForSession, { global = false })
PublishApi("LIDCOUNCIL_BroadcastActiveSessionRoleUpdate", flowHelpers.BroadcastActiveSessionRoleUpdate, { global = false })
PublishApi("LIDCOUNCIL_NotifyLocalAutoPassChanged", flowHelpers.NotifyLocalAutoPassChanged, { global = false })
PublishApi("LIDCOUNCIL_HasJoinedActiveLootSession", flowHelpers.HasJoinedActiveLootSession, { global = false })
PublishApi("LIDCOUNCIL_COMM_PREFIX", COMM_PREFIX, { global = false })
PublishApi("LIDCOUNCIL_SendSessionComm", SendComm, { global = false })
PublishApi("LIDCOUNCIL_ParseSessionComm", ParsePayload, { global = false })
PublishApi("LIDCOUNCIL_NormalizeCharacterKey", NormalizeCharacterKey, { global = false })
