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

local INBOX_LIMITS = {
  maxSessions = 8,
  maxItems = 512,
  maxItemsPerSession = 384,
  maxBytes = 16 * 1024 * 1024,
  maxBytesPerSession = 12 * 1024 * 1024,
  maxEntryBytes = 560 * 1024,
  retentionSeconds = 20 * 60,
  maxFlushRetries = 3,
  flushRetryDelaySeconds = 0.5,
}

local inboxState = {
  sessions = {},
  totalItems = 0,
  totalBytes = 0,
  nextOrdinal = 0,
  queuedTotal = 0,
  duplicateTotal = 0,
  coveredTotal = 0,
  supersededTotal = 0,
  reorderedTotal = 0,
  rejectedTotal = 0,
  expiredTotal = 0,
  clearedTotal = 0,
  drainedTotal = 0,
  lastRejectReason = "",
  lastRejectAt = 0,
  flushRunning = false,
  flushTotal = 0,
  appliedTotal = 0,
  applyFailedTotal = 0,
  unhandledTotal = 0,
  combatInterruptedTotal = 0,
  lastFlushReason = "",
  lastFlushAt = 0,
  lastApplyFailureOpcode = "",
  lastApplyFailureReason = "",
  lastApplyFailureAt = 0,
  flushRetryScheduled = false,
  flushRetryAttempt = 0,
  flushRetryTotal = 0,
  flushRetryExhaustedTotal = 0,
  flushRetryToken = 0,
}
local applierRegistry = {}
local combatInboxEventFrame
local EnsureCombatInboxEventFrame
local FlushInbox
local ScheduleFlushRetry

local function TrimText(value)
  return tostring(value or ""):match("^%s*(.-)%s*$")
end

local function NormalizeKey(value)
  return string.lower(TrimText(value))
end

local function GetNow(nowOverride)
  if type(nowOverride) == "number" then
    return math.max(0, nowOverride)
  end
  if type(GetServerTime) == "function" then
    return math.max(0, tonumber(GetServerTime()) or 0)
  end
  return math.max(0, tonumber(time and time() or 0) or 0)
end

local function IsCombatLocked(combatLockedOverride)
  if type(combatLockedOverride) == "boolean" then
    return combatLockedOverride
  end
  return type(InCombatLockdown) == "function" and InCombatLockdown() and true or false
end

local function ResolveFlushRetryDecision(totalItems, combatLocked, retryScheduled, retryAttempt, schedulerAvailable)
  local remaining = math.max(0, tonumber(totalItems) or 0)
  local attempt = math.max(0, math.floor(tonumber(retryAttempt) or 0))
  if remaining <= 0 then
    return "drained", attempt, 0
  end
  if combatLocked == true then
    return "combat_locked", attempt, 0
  end
  if retryScheduled == true then
    return "already_scheduled", attempt, 0
  end
  if schedulerAvailable ~= true then
    return "scheduler_unavailable", attempt, 0
  end
  if attempt >= INBOX_LIMITS.maxFlushRetries then
    return "exhausted", attempt, 0
  end
  local nextAttempt = attempt + 1
  return "schedule", nextAttempt, INBOX_LIMITS.flushRetryDelaySeconds * nextAttempt
end

local function ResetFlushRetry()
  inboxState.flushRetryToken = math.max(0, tonumber(inboxState.flushRetryToken) or 0) + 1
  inboxState.flushRetryScheduled = false
  inboxState.flushRetryAttempt = 0
end

local function CloneQueueValue(value, seen)
  local valueType = type(value)
  if valueType == "nil" or valueType == "string" or valueType == "number" or valueType == "boolean" then
    return value
  end
  if valueType ~= "table" then
    return nil, "unsupported_payload_type:" .. valueType
  end

  seen = type(seen) == "table" and seen or {}
  if seen[value] then
    return seen[value]
  end
  local clone = {}
  seen[value] = clone
  for key, entry in pairs(value) do
    local clonedKey, keyError = CloneQueueValue(key, seen)
    if keyError then
      return nil, keyError
    end
    local clonedEntry, entryError = CloneQueueValue(entry, seen)
    if entryError then
      return nil, entryError
    end
    clone[clonedKey] = clonedEntry
  end
  return clone
end

local function EstimateQueueValueBytes(value, seen, depth)
  local valueType = type(value)
  if valueType == "nil" then
    return 0
  elseif valueType == "string" then
    return #value
  elseif valueType == "number" then
    return 8
  elseif valueType == "boolean" then
    return 1
  elseif valueType ~= "table" then
    return nil, "unsupported_payload_type:" .. valueType
  end

  depth = math.max(0, tonumber(depth) or 0)
  if depth >= 32 then
    return nil, "payload_depth_limit"
  end
  seen = type(seen) == "table" and seen or {}
  if seen[value] then
    return 0
  end
  seen[value] = true

  local bytes = 16
  for key, entry in pairs(value) do
    local keyBytes, keyError = EstimateQueueValueBytes(key, seen, depth + 1)
    if keyError then
      return nil, keyError
    end
    local entryBytes, entryError = EstimateQueueValueBytes(entry, seen, depth + 1)
    if entryError then
      return nil, entryError
    end
    bytes = bytes + keyBytes + entryBytes + 8
    if bytes > INBOX_LIMITS.maxEntryBytes then
      return bytes
    end
  end
  return bytes
end

local function BuildDedupeKey(entry)
  entry = type(entry) == "table" and entry or {}
  local explicitKey = NormalizeKey(entry.dedupeKey)
  if explicitKey ~= "" then
    return explicitKey
  end

  local identity = TrimText(entry.changeId)
  if identity == "" then
    identity = TrimText(entry.transferId)
  end
  if identity == "" and math.max(0, tonumber(entry.sequence) or 0) > 0 then
    identity = "sequence:" .. tostring(math.floor(tonumber(entry.sequence) or 0))
  end
  if identity == "" then
    return ""
  end

  return table.concat({
    NormalizeKey(entry.opcode),
    NormalizeKey(entry.sender),
    NormalizeKey(entry.profileName),
    NormalizeKey(identity),
    tostring(math.max(0, math.floor(tonumber(entry.generation) or 0))),
  }, "\31")
end

local function SnapshotEntry(entry, includePayload)
  if type(entry) ~= "table" then
    return nil
  end
  local snapshot = {
    ordinal = entry.ordinal,
    opcode = entry.opcode,
    category = entry.category,
    sessionId = entry.sessionId,
    profileName = entry.profileName,
    profileId = entry.profileId,
    sender = entry.sender,
    changeId = entry.changeId,
    transferId = entry.transferId,
    generation = entry.generation,
    versionStamp = entry.versionStamp,
    sequence = entry.sequence,
    domain = entry.domain,
    action = entry.action,
    dedupeKey = entry.dedupeKey,
    streamKey = entry.streamKey,
    orderingKey = entry.orderingKey,
    barrierKey = entry.barrierKey,
    orderingBarrier = entry.orderingBarrier == true,
    payloadBytes = entry.payloadBytes,
    queuedAt = entry.queuedAt,
    applyAttempts = entry.applyAttempts,
    lastApplyReason = entry.lastApplyReason,
    lastApplyAt = entry.lastApplyAt,
  }
  if includePayload then
    snapshot.payload = CloneQueueValue(entry.payload)
    snapshot.completion = CloneQueueValue(entry.completion)
    snapshot.coveredCompletions = CloneQueueValue(entry.coveredCompletions)
  end
  return snapshot
end

local function GetSessionBucket(sessionId, create)
  local normalizedSessionId = NormalizeKey(sessionId)
  if normalizedSessionId == "" then
    return nil, ""
  end
  local bucket = inboxState.sessions[normalizedSessionId]
  if type(bucket) ~= "table" and create then
    bucket = {
      sessionId = TrimText(sessionId),
      entries = {},
      byDedupeKey = {},
      bytes = 0,
      createdAt = 0,
      updatedAt = 0,
    }
    inboxState.sessions[normalizedSessionId] = bucket
  end
  return bucket, normalizedSessionId
end

local function CountSessions()
  local count = 0
  for _ in pairs(inboxState.sessions) do
    count = count + 1
  end
  return count
end

local function RemoveEntryAt(bucket, index)
  local entry = type(bucket) == "table" and type(bucket.entries) == "table" and bucket.entries[index] or nil
  if type(entry) ~= "table" then
    return nil
  end
  table.remove(bucket.entries, index)
  if TrimText(entry.dedupeKey) ~= "" then
    bucket.byDedupeKey[entry.dedupeKey] = nil
  end
  local entryBytes = math.max(0, tonumber(entry.payloadBytes) or 0)
  bucket.bytes = math.max(0, (tonumber(bucket.bytes) or 0) - entryBytes)
  inboxState.totalItems = math.max(0, inboxState.totalItems - 1)
  inboxState.totalBytes = math.max(0, inboxState.totalBytes - entryBytes)
  return entry
end

local function RemoveEmptyBucket(normalizedSessionId, bucket)
  if type(bucket) == "table" and #bucket.entries == 0 then
    inboxState.sessions[normalizedSessionId] = nil
  end
end

local function PruneExpiredEntries(nowOverride)
  local now = GetNow(nowOverride)
  local expired = 0
  for normalizedSessionId, bucket in pairs(inboxState.sessions) do
    for index = #bucket.entries, 1, -1 do
      local entry = bucket.entries[index]
      local queuedAt = math.max(0, tonumber(type(entry) == "table" and entry.queuedAt or 0) or 0)
      if queuedAt <= 0 or (now - queuedAt) > INBOX_LIMITS.retentionSeconds then
        RemoveEntryAt(bucket, index)
        expired = expired + 1
      end
    end
    RemoveEmptyBucket(normalizedSessionId, bucket)
  end
  inboxState.expiredTotal = inboxState.expiredTotal + expired
  return expired
end

local function RejectEntry(reason, nowOverride)
  inboxState.rejectedTotal = inboxState.rejectedTotal + 1
  inboxState.lastRejectReason = TrimText(reason)
  inboxState.lastRejectAt = GetNow(nowOverride)
  return false, inboxState.lastRejectReason
end

local function AppendCompletion(target, completion)
  if type(completion) == "table" then
    target[#target + 1] = completion
  end
end

local function AppendCoveredCompletions(target, entry)
  if type(entry) ~= "table" then
    return
  end
  AppendCompletion(target, entry.completion)
  for _, completion in ipairs(type(entry.coveredCompletions) == "table" and entry.coveredCompletions or {}) do
    AppendCompletion(target, completion)
  end
end

local function CompareStreamVersions(existing, generation, versionStamp)
  local existingGeneration = math.max(0, tonumber(type(existing) == "table" and existing.generation or 0) or 0)
  local existingVersionStamp = math.max(0, tonumber(type(existing) == "table" and existing.versionStamp or 0) or 0)
  if existingVersionStamp > 0 and versionStamp > 0 and existingVersionStamp ~= versionStamp then
    return versionStamp > existingVersionStamp and "incoming_newer" or "existing_covers"
  end
  if existingGeneration > 0 and generation > 0 and existingGeneration ~= generation then
    return generation > existingGeneration and "incoming_newer" or "existing_covers"
  end
  if (existingVersionStamp > 0 and versionStamp > 0)
    or (existingGeneration > 0 and generation > 0)
  then
    return "existing_covers"
  end
  return "incomparable"
end

local function BuildGenerationSupersessionPlan(bucket, opcode, streamKey, generation, versionStamp)
  local plan = {
    coveredBy = nil,
    supersededIndexes = {},
    coveredCompletions = {},
    supersededBytes = 0,
  }
  if type(bucket) ~= "table" or streamKey == "" or generation <= 0 then
    return plan
  end

  for index, existing in ipairs(bucket.entries) do
    if type(existing) == "table"
      and existing.opcode == opcode
      and existing.streamKey == streamKey
    then
      local comparison = CompareStreamVersions(existing, generation, versionStamp)
      if comparison == "existing_covers" then
        if type(plan.coveredBy) ~= "table"
          or CompareStreamVersions(
            plan.coveredBy,
            math.max(0, tonumber(existing.generation) or 0),
            math.max(0, tonumber(existing.versionStamp) or 0)
          ) == "incoming_newer"
        then
          plan.coveredBy = existing
        end
      elseif comparison == "incoming_newer" then
        plan.supersededIndexes[#plan.supersededIndexes + 1] = index
        plan.supersededBytes = plan.supersededBytes + math.max(0, tonumber(existing.payloadBytes) or 0)
        AppendCoveredCompletions(plan.coveredCompletions, existing)
      end
    end
  end
  return plan
end

local function EstimateInboxEntryBytes(entry, coveredCompletions)
  entry = type(entry) == "table" and entry or {}
  local storageBytes, storageError = EstimateQueueValueBytes({
    payload = entry.payload,
    completion = entry.completion,
    coveredCompletions = coveredCompletions,
  })
  if storageError then
    return nil, storageError
  end
  local bytes = math.max(0, tonumber(storageBytes) or 0)
    + #TrimText(entry.opcode)
    + #TrimText(entry.sessionId)
    + #TrimText(entry.profileName)
    + #TrimText(entry.profileId)
    + #TrimText(entry.sender)
    + #TrimText(entry.changeId)
    + #TrimText(entry.transferId)
    + #TrimText(entry.domain)
    + #TrimText(entry.action)
    + #TrimText(entry.dedupeKey)
    + #TrimText(entry.streamKey)
    + #TrimText(entry.orderingKey)
    + #TrimText(entry.barrierKey)
    + 128
  return bytes
end

local function ReorderSequencedEntries(bucket, orderingKey, barrierKey)
  if type(bucket) ~= "table" or orderingKey == "" then
    return 0
  end

  local moved = 0
  local positions = {}
  local function SortSegment()
    if #positions <= 1 then
      positions = {}
      return
    end
    local ordered = {}
    for _, index in ipairs(positions) do
      ordered[#ordered + 1] = bucket.entries[index]
    end
    table.sort(ordered, function(left, right)
      local leftSequence = math.max(0, tonumber(type(left) == "table" and left.sequence or 0) or 0)
      local rightSequence = math.max(0, tonumber(type(right) == "table" and right.sequence or 0) or 0)
      if leftSequence ~= rightSequence then
        return leftSequence < rightSequence
      end
      return math.max(0, tonumber(type(left) == "table" and left.ordinal or 0) or 0)
        < math.max(0, tonumber(type(right) == "table" and right.ordinal or 0) or 0)
    end)
    for offset, index in ipairs(positions) do
      if bucket.entries[index] ~= ordered[offset] then
        moved = moved + 1
      end
      bucket.entries[index] = ordered[offset]
    end
    positions = {}
  end

  for index, queued in ipairs(bucket.entries) do
    if type(queued) == "table"
      and queued.orderingBarrier == true
      and barrierKey ~= ""
      and queued.barrierKey == barrierKey
    then
      SortSegment()
    elseif type(queued) == "table"
      and queued.orderingKey == orderingKey
      and math.max(0, tonumber(queued.sequence) or 0) > 0
    then
      positions[#positions + 1] = index
    end
  end
  SortSegment()
  inboxState.reorderedTotal = inboxState.reorderedTotal + moved
  return moved
end

local function QueueInboxEntry(entry, opts)
  entry = type(entry) == "table" and entry or {}
  opts = type(opts) == "table" and opts or {}
  local opcode = string.upper(TrimText(entry.opcode))
  local sessionId = TrimText(entry.sessionId)
  if opcode == "" then
    return RejectEntry("missing_opcode", opts.now)
  end
  if sessionId == "" then
    return RejectEntry("missing_session_id", opts.now)
  end

  local resolveDecision = INTERNAL_API.LIDCOUNCIL_ResolveCombatSyncDecision_Impl
  if type(resolveDecision) ~= "function" then
    return RejectEntry("combat_policy_unavailable", opts.now)
  end
  local decision = resolveDecision(opcode, { combatLocked = true })
  if type(decision) ~= "table" or decision.stageData ~= true then
    return RejectEntry("opcode_not_stageable", opts.now)
  end

  local dedupeKey = BuildDedupeKey(entry)
  if dedupeKey == "" then
    return RejectEntry("missing_dedupe_identity", opts.now)
  end

  PruneExpiredEntries(opts.now)
  local bucket, normalizedSessionId = GetSessionBucket(sessionId, false)
  local duplicate = type(bucket) == "table" and bucket.byDedupeKey[dedupeKey] or nil
  if type(duplicate) == "table" then
    inboxState.duplicateTotal = inboxState.duplicateTotal + 1
    return true, "duplicate", SnapshotEntry(duplicate, false)
  end

  local now = GetNow(opts.now)
  local generation = math.max(0, math.floor(tonumber(entry.generation) or 0))
  local versionStamp = math.max(0, tonumber(entry.versionStamp) or 0)
  local streamKey = NormalizeKey(entry.streamKey)
  local orderingKey = NormalizeKey(entry.orderingKey)
  local barrierKey = NormalizeKey(entry.barrierKey)
  local supersessionPlan = entry.supersedeOlder == true
    and BuildGenerationSupersessionPlan(bucket, opcode, streamKey, generation, versionStamp)
    or {
      coveredBy = nil,
      supersededIndexes = {},
      coveredCompletions = {},
      supersededBytes = 0,
    }
  if type(supersessionPlan.coveredBy) == "table" then
    local coveredEntry = supersessionPlan.coveredBy
    local coveredCompletions = {}
    for _, completion in ipairs(type(coveredEntry.coveredCompletions) == "table" and coveredEntry.coveredCompletions or {}) do
      AppendCompletion(coveredCompletions, completion)
    end
    AppendCoveredCompletions(coveredCompletions, {
      completion = entry.completion,
      coveredCompletions = entry.coveredCompletions,
    })
    local coveredBytes, coveredError = EstimateInboxEntryBytes(coveredEntry, coveredCompletions)
    if coveredError then
      return RejectEntry(coveredError, opts.now)
    end
    coveredBytes = math.max(0, tonumber(coveredBytes) or 0)
    local coveredByteDelta = coveredBytes - math.max(0, tonumber(coveredEntry.payloadBytes) or 0)
    if coveredBytes > INBOX_LIMITS.maxEntryBytes then
      return RejectEntry("entry_byte_limit", opts.now)
    end
    if inboxState.totalBytes + coveredByteDelta > INBOX_LIMITS.maxBytes then
      return RejectEntry("global_byte_limit", opts.now)
    end
    if math.max(0, tonumber(bucket.bytes) or 0) + coveredByteDelta > INBOX_LIMITS.maxBytesPerSession then
      return RejectEntry("session_byte_limit", opts.now)
    end
    local clonedCoveredCompletions, cloneCoveredError = CloneQueueValue(coveredCompletions)
    if cloneCoveredError then
      return RejectEntry(cloneCoveredError, opts.now)
    end
    coveredEntry.coveredCompletions = clonedCoveredCompletions
    coveredEntry.payloadBytes = coveredBytes
    bucket.bytes = math.max(0, tonumber(bucket.bytes) or 0) + coveredByteDelta
    bucket.updatedAt = now
    inboxState.totalBytes = inboxState.totalBytes + coveredByteDelta
    inboxState.coveredTotal = inboxState.coveredTotal + 1
    return true, "covered", SnapshotEntry(coveredEntry, false)
  end

  local coveredCompletions = {}
  for _, completion in ipairs(type(entry.coveredCompletions) == "table" and entry.coveredCompletions or {}) do
    AppendCompletion(coveredCompletions, completion)
  end
  for _, completion in ipairs(supersessionPlan.coveredCompletions) do
    AppendCompletion(coveredCompletions, completion)
  end
  local normalizedEntry = {
    opcode = opcode,
    sessionId = sessionId,
    profileName = TrimText(entry.profileName),
    profileId = TrimText(entry.profileId),
    sender = TrimText(entry.sender),
    changeId = TrimText(entry.changeId),
    transferId = TrimText(entry.transferId),
    generation = generation,
    versionStamp = versionStamp,
    sequence = math.max(0, math.floor(tonumber(entry.sequence) or 0)),
    domain = TrimText(entry.domain),
    action = TrimText(entry.action),
    dedupeKey = dedupeKey,
    streamKey = streamKey,
    orderingKey = orderingKey,
    barrierKey = barrierKey,
    orderingBarrier = entry.orderingBarrier == true,
    payload = entry.payload,
    completion = entry.completion,
  }
  local payloadBytes, payloadError = EstimateInboxEntryBytes(normalizedEntry, coveredCompletions)
  if payloadError then
    return RejectEntry(payloadError, opts.now)
  end
  payloadBytes = math.max(0, tonumber(payloadBytes) or 0)
  if payloadBytes > INBOX_LIMITS.maxEntryBytes then
    return RejectEntry("entry_byte_limit", opts.now)
  end

  if type(bucket) ~= "table" and CountSessions() >= INBOX_LIMITS.maxSessions then
    return RejectEntry("session_limit", opts.now)
  end
  local sessionItems = type(bucket) == "table" and #bucket.entries or 0
  local sessionBytes = type(bucket) == "table" and math.max(0, tonumber(bucket.bytes) or 0) or 0
  local supersededCount = #supersessionPlan.supersededIndexes
  local projectedTotalItems = inboxState.totalItems - supersededCount + 1
  local projectedSessionItems = sessionItems - supersededCount + 1
  local projectedTotalBytes = inboxState.totalBytes - supersessionPlan.supersededBytes + payloadBytes
  local projectedSessionBytes = sessionBytes - supersessionPlan.supersededBytes + payloadBytes
  if projectedTotalItems > INBOX_LIMITS.maxItems then
    return RejectEntry("global_item_limit", opts.now)
  end
  if projectedSessionItems > INBOX_LIMITS.maxItemsPerSession then
    return RejectEntry("session_item_limit", opts.now)
  end
  if projectedTotalBytes > INBOX_LIMITS.maxBytes then
    return RejectEntry("global_byte_limit", opts.now)
  end
  if projectedSessionBytes > INBOX_LIMITS.maxBytesPerSession then
    return RejectEntry("session_byte_limit", opts.now)
  end

  local clonedStorage, cloneError = CloneQueueValue({
    payload = entry.payload,
    completion = entry.completion,
    coveredCompletions = coveredCompletions,
  })
  if cloneError then
    return RejectEntry(cloneError, opts.now)
  end
  bucket = bucket or select(1, GetSessionBucket(sessionId, true))
  table.sort(supersessionPlan.supersededIndexes, function(left, right)
    return left > right
  end)
  for _, index in ipairs(supersessionPlan.supersededIndexes) do
    RemoveEntryAt(bucket, index)
  end
  inboxState.nextOrdinal = inboxState.nextOrdinal + 1
  local queued = {
    ordinal = inboxState.nextOrdinal,
    opcode = opcode,
    category = TrimText(decision.category),
    sessionId = sessionId,
    profileName = normalizedEntry.profileName,
    profileId = normalizedEntry.profileId,
    sender = normalizedEntry.sender,
    changeId = normalizedEntry.changeId,
    transferId = normalizedEntry.transferId,
    generation = normalizedEntry.generation,
    versionStamp = normalizedEntry.versionStamp,
    sequence = normalizedEntry.sequence,
    domain = normalizedEntry.domain,
    action = normalizedEntry.action,
    dedupeKey = dedupeKey,
    streamKey = streamKey,
    orderingKey = orderingKey,
    barrierKey = barrierKey,
    orderingBarrier = normalizedEntry.orderingBarrier,
    payload = clonedStorage.payload,
    completion = clonedStorage.completion,
    coveredCompletions = clonedStorage.coveredCompletions,
    payloadBytes = payloadBytes,
    queuedAt = now,
    applyAttempts = 0,
    lastApplyReason = "",
    lastApplyAt = 0,
  }
  bucket.entries[#bucket.entries + 1] = queued
  bucket.byDedupeKey[dedupeKey] = queued
  bucket.bytes = math.max(0, tonumber(bucket.bytes) or 0) + payloadBytes
  bucket.createdAt = math.max(0, tonumber(bucket.createdAt) or 0)
  if bucket.createdAt <= 0 then
    bucket.createdAt = now
  end
  bucket.updatedAt = now
  inboxState.totalItems = inboxState.totalItems + 1
  inboxState.totalBytes = inboxState.totalBytes + payloadBytes
  inboxState.queuedTotal = inboxState.queuedTotal + 1
  inboxState.supersededTotal = inboxState.supersededTotal + supersededCount
  ReorderSequencedEntries(bucket, orderingKey, barrierKey)
  if type(EnsureCombatInboxEventFrame) == "function" then
    EnsureCombatInboxEventFrame()
  end
  return true, "queued", SnapshotEntry(queued, false)
end

local function DrainInboxSession(sessionId, opts)
  opts = type(opts) == "table" and opts or {}
  if IsCombatLocked(opts.combatLocked) then
    return nil, "combat_locked"
  end
  PruneExpiredEntries(opts.now)
  local bucket, normalizedSessionId = GetSessionBucket(sessionId, false)
  if type(bucket) ~= "table" then
    return {}, "empty"
  end

  local drained = {}
  for _, entry in ipairs(bucket.entries) do
    drained[#drained + 1] = SnapshotEntry(entry, true)
  end
  inboxState.totalItems = math.max(0, inboxState.totalItems - #bucket.entries)
  inboxState.totalBytes = math.max(0, inboxState.totalBytes - math.max(0, tonumber(bucket.bytes) or 0))
  inboxState.sessions[normalizedSessionId] = nil
  inboxState.drainedTotal = inboxState.drainedTotal + #drained
  return drained, "drained"
end

local function ClearInboxSession(sessionId)
  local bucket, normalizedSessionId = GetSessionBucket(sessionId, false)
  if type(bucket) ~= "table" then
    return 0
  end
  local cleared = #bucket.entries
  inboxState.totalItems = math.max(0, inboxState.totalItems - cleared)
  inboxState.totalBytes = math.max(0, inboxState.totalBytes - math.max(0, tonumber(bucket.bytes) or 0))
  inboxState.sessions[normalizedSessionId] = nil
  inboxState.clearedTotal = inboxState.clearedTotal + cleared
  if inboxState.totalItems <= 0 then
    ResetFlushRetry()
  end
  return cleared
end

local function ClearInbox()
  local cleared = inboxState.totalItems
  inboxState.sessions = {}
  inboxState.totalItems = 0
  inboxState.totalBytes = 0
  inboxState.clearedTotal = inboxState.clearedTotal + cleared
  ResetFlushRetry()
  return cleared
end

local function BuildSessionStatus(bucket, now)
  local status = {
    sessionId = TrimText(type(bucket) == "table" and bucket.sessionId or ""),
    items = type(bucket) == "table" and #bucket.entries or 0,
    bytes = math.max(0, tonumber(type(bucket) == "table" and bucket.bytes or 0) or 0),
    oldestAgeSeconds = 0,
    createdAt = math.max(0, tonumber(type(bucket) == "table" and bucket.createdAt or 0) or 0),
    updatedAt = math.max(0, tonumber(type(bucket) == "table" and bucket.updatedAt or 0) or 0),
    byOpcode = {},
  }
  local oldestQueuedAt = 0
  for _, entry in ipairs(type(bucket) == "table" and bucket.entries or {}) do
    local opcode = TrimText(type(entry) == "table" and entry.opcode or "")
    status.byOpcode[opcode] = (tonumber(status.byOpcode[opcode]) or 0) + 1
    local queuedAt = math.max(0, tonumber(type(entry) == "table" and entry.queuedAt or 0) or 0)
    if queuedAt > 0 and (oldestQueuedAt <= 0 or queuedAt < oldestQueuedAt) then
      oldestQueuedAt = queuedAt
    end
  end
  if oldestQueuedAt > 0 then
    status.oldestAgeSeconds = math.max(0, now - oldestQueuedAt)
  end
  return status
end

local function GetInboxStatus(sessionId, opts)
  opts = type(opts) == "table" and opts or {}
  local now = GetNow(opts.now)
  if opts.pruneExpired ~= false then
    PruneExpiredEntries(now)
  end
  local status = {
    totalItems = inboxState.totalItems,
    totalBytes = inboxState.totalBytes,
    sessionCount = CountSessions(),
    queuedTotal = inboxState.queuedTotal,
    duplicateTotal = inboxState.duplicateTotal,
    coveredTotal = inboxState.coveredTotal,
    supersededTotal = inboxState.supersededTotal,
    reorderedTotal = inboxState.reorderedTotal,
    rejectedTotal = inboxState.rejectedTotal,
    expiredTotal = inboxState.expiredTotal,
    clearedTotal = inboxState.clearedTotal,
    drainedTotal = inboxState.drainedTotal,
    lastRejectReason = inboxState.lastRejectReason,
    lastRejectAt = inboxState.lastRejectAt,
    flushRunning = inboxState.flushRunning == true,
    flushTotal = inboxState.flushTotal,
    appliedTotal = inboxState.appliedTotal,
    applyFailedTotal = inboxState.applyFailedTotal,
    unhandledTotal = inboxState.unhandledTotal,
    combatInterruptedTotal = inboxState.combatInterruptedTotal,
    lastFlushReason = inboxState.lastFlushReason,
    lastFlushAt = inboxState.lastFlushAt,
    lastApplyFailureOpcode = inboxState.lastApplyFailureOpcode,
    lastApplyFailureReason = inboxState.lastApplyFailureReason,
    lastApplyFailureAt = inboxState.lastApplyFailureAt,
    flushRetryScheduled = inboxState.flushRetryScheduled == true,
    flushRetryAttempt = math.max(0, tonumber(inboxState.flushRetryAttempt) or 0),
    flushRetryTotal = math.max(0, tonumber(inboxState.flushRetryTotal) or 0),
    flushRetryExhaustedTotal = math.max(0, tonumber(inboxState.flushRetryExhaustedTotal) or 0),
    sessions = {},
    registeredAppliers = {},
    limits = {
      maxSessions = INBOX_LIMITS.maxSessions,
      maxItems = INBOX_LIMITS.maxItems,
      maxItemsPerSession = INBOX_LIMITS.maxItemsPerSession,
      maxBytes = INBOX_LIMITS.maxBytes,
      maxBytesPerSession = INBOX_LIMITS.maxBytesPerSession,
      maxEntryBytes = INBOX_LIMITS.maxEntryBytes,
      retentionSeconds = INBOX_LIMITS.retentionSeconds,
      maxFlushRetries = INBOX_LIMITS.maxFlushRetries,
      flushRetryDelaySeconds = INBOX_LIMITS.flushRetryDelaySeconds,
    },
  }
  for opcode, registration in pairs(applierRegistry) do
    status.registeredAppliers[#status.registeredAppliers + 1] = {
      opcode = opcode,
      owner = TrimText(type(registration) == "table" and registration.owner or ""),
    }
  end
  table.sort(status.registeredAppliers, function(left, right)
    return TrimText(left.opcode) < TrimText(right.opcode)
  end)

  local requestedSessionId = NormalizeKey(sessionId)
  if requestedSessionId ~= "" then
    local bucket = inboxState.sessions[requestedSessionId]
    if type(bucket) == "table" then
      status.sessions[1] = BuildSessionStatus(bucket, now)
    end
    return status
  end

  for _, bucket in pairs(inboxState.sessions) do
    status.sessions[#status.sessions + 1] = BuildSessionStatus(bucket, now)
  end
  table.sort(status.sessions, function(left, right)
    return NormalizeKey(left.sessionId) < NormalizeKey(right.sessionId)
  end)
  return status
end

local function RegisterInboxApplier(opcode, owner, handler)
  local normalizedOpcode = string.upper(TrimText(opcode))
  local resolvedOwner = TrimText(owner)
  if normalizedOpcode == "" then
    return false, "missing_opcode"
  end
  if resolvedOwner == "" then
    return false, "missing_owner"
  end
  if type(handler) ~= "function" then
    return false, "missing_handler"
  end

  local resolveDecision = INTERNAL_API.LIDCOUNCIL_ResolveCombatSyncDecision_Impl
  local decision = type(resolveDecision) == "function"
    and resolveDecision(normalizedOpcode, { combatLocked = true })
    or nil
  if type(decision) ~= "table" or decision.stageData ~= true then
    return false, "opcode_not_stageable"
  end

  local existing = applierRegistry[normalizedOpcode]
  if type(existing) == "table" and TrimText(existing.owner) ~= resolvedOwner then
    return false, "owner_conflict"
  end
  applierRegistry[normalizedOpcode] = {
    owner = resolvedOwner,
    handler = handler,
  }
  if type(EnsureCombatInboxEventFrame) == "function" then
    EnsureCombatInboxEventFrame()
  end
  return true, "registered"
end

local function UnregisterInboxApplier(opcode, owner)
  local normalizedOpcode = string.upper(TrimText(opcode))
  local resolvedOwner = TrimText(owner)
  local existing = applierRegistry[normalizedOpcode]
  if type(existing) ~= "table" then
    return false, "not_registered"
  end
  if resolvedOwner == "" or TrimText(existing.owner) ~= resolvedOwner then
    return false, "owner_mismatch"
  end
  applierRegistry[normalizedOpcode] = nil
  return true, "unregistered"
end

local function BuildFlushSessionOrder()
  local ordered = {}
  for normalizedSessionId, bucket in pairs(inboxState.sessions) do
    local firstOrdinal = 0
    for _, entry in ipairs(type(bucket) == "table" and type(bucket.entries) == "table" and bucket.entries or {}) do
      local ordinal = math.max(0, tonumber(type(entry) == "table" and entry.ordinal or 0) or 0)
      if ordinal > 0 and (firstOrdinal <= 0 or ordinal < firstOrdinal) then
        firstOrdinal = ordinal
      end
    end
    ordered[#ordered + 1] = {
      key = normalizedSessionId,
      firstOrdinal = firstOrdinal,
    }
  end
  table.sort(ordered, function(left, right)
    if left.firstOrdinal ~= right.firstOrdinal then
      return left.firstOrdinal < right.firstOrdinal
    end
    return tostring(left.key or "") < tostring(right.key or "")
  end)
  return ordered
end

local function RecordApplyFailure(entry, reason, now)
  entry.applyAttempts = math.max(0, tonumber(entry.applyAttempts) or 0) + 1
  entry.lastApplyReason = TrimText(reason)
  entry.lastApplyAt = now
  inboxState.applyFailedTotal = inboxState.applyFailedTotal + 1
  inboxState.lastApplyFailureOpcode = TrimText(entry.opcode)
  inboxState.lastApplyFailureReason = entry.lastApplyReason
  inboxState.lastApplyFailureAt = now
end

FlushInbox = function(opts)
  opts = type(opts) == "table" and opts or {}
  if inboxState.flushRunning then
    return false, {
      reason = "flush_already_running",
      applied = 0,
      remaining = inboxState.totalItems,
    }
  end
  if IsCombatLocked(opts.combatLocked) then
    return false, {
      reason = "combat_locked",
      applied = 0,
      remaining = inboxState.totalItems,
    }
  end

  local now = GetNow(opts.now)
  PruneExpiredEntries(now)
  local report = {
    reason = TrimText(opts.reason) ~= "" and TrimText(opts.reason) or "manual",
    applied = 0,
    failed = 0,
    unhandled = 0,
    combatInterrupted = false,
    remaining = inboxState.totalItems,
  }
  local maxEntries = math.max(1, math.floor(tonumber(opts.maxEntries) or INBOX_LIMITS.maxItems))
  inboxState.flushRunning = true
  inboxState.flushTotal = inboxState.flushTotal + 1
  inboxState.lastFlushReason = report.reason
  inboxState.lastFlushAt = now

  local sessionOrder = BuildFlushSessionOrder()
  for _, sessionRef in ipairs(sessionOrder) do
    local bucket = inboxState.sessions[sessionRef.key]
    while type(bucket) == "table" and #bucket.entries > 0 and report.applied < maxEntries do
      if IsCombatLocked(opts.combatLocked) then
        report.combatInterrupted = true
        inboxState.combatInterruptedTotal = inboxState.combatInterruptedTotal + 1
        break
      end

      local entry = bucket.entries[1]
      local registration = type(entry) == "table" and applierRegistry[entry.opcode] or nil
      if type(registration) ~= "table" or type(registration.handler) ~= "function" then
        report.unhandled = report.unhandled + 1
        inboxState.unhandledTotal = inboxState.unhandledTotal + 1
        break
      end

      local callOk, applied, applyReason = pcall(registration.handler, SnapshotEntry(entry, true), {
        flushReason = report.reason,
        owner = registration.owner,
        now = now,
      })
      if not callOk then
        report.failed = report.failed + 1
        RecordApplyFailure(entry, "handler_error:" .. tostring(applied), now)
        break
      end
      if applied ~= true then
        report.failed = report.failed + 1
        RecordApplyFailure(entry, TrimText(applyReason) ~= "" and applyReason or "handler_rejected", now)
        break
      end

      RemoveEntryAt(bucket, 1)
      report.applied = report.applied + 1
      inboxState.appliedTotal = inboxState.appliedTotal + 1
    end
    RemoveEmptyBucket(sessionRef.key, bucket)
    if report.combatInterrupted or report.applied >= maxEntries then
      break
    end
  end

  inboxState.flushRunning = false
  report.remaining = inboxState.totalItems
  if report.combatInterrupted then
    report.outcome = "combat_interrupted"
  elseif report.failed > 0 then
    report.outcome = "apply_failed"
  elseif report.unhandled > 0 then
    report.outcome = "missing_applier"
  elseif report.remaining > 0 then
    report.outcome = "entry_limit_reached"
  else
    report.outcome = "drained"
  end
  if type(INTERNAL_API.LIDCOUNCIL_FlushCombatUiRefreshChannels_Impl) == "function" then
    INTERNAL_API.LIDCOUNCIL_FlushCombatUiRefreshChannels_Impl("combat_inbox_" .. report.outcome)
  end
  return report.outcome == "drained", report
end

ScheduleFlushRetry = function(reason)
  local compat = rawget(_G, "LIDCOUNCIL_Compat")
  local after = type(compat) == "table" and type(compat.After) == "function" and compat.After
    or (type(C_Timer) == "table" and type(C_Timer.After) == "function" and C_Timer.After or nil)
  local decision, nextAttempt, delaySeconds = ResolveFlushRetryDecision(
    inboxState.totalItems,
    IsCombatLocked(),
    inboxState.flushRetryScheduled,
    inboxState.flushRetryAttempt,
    type(after) == "function"
  )
  if decision == "exhausted" then
    inboxState.flushRetryExhaustedTotal = inboxState.flushRetryExhaustedTotal + 1
  end
  if decision ~= "schedule" then
    return false
  end

  inboxState.flushRetryScheduled = true
  inboxState.flushRetryAttempt = nextAttempt
  inboxState.flushRetryTotal = inboxState.flushRetryTotal + 1
  inboxState.flushRetryToken = math.max(0, tonumber(inboxState.flushRetryToken) or 0) + 1
  local retryToken = inboxState.flushRetryToken
  after(delaySeconds, function()
    if retryToken ~= inboxState.flushRetryToken then
      return
    end
    inboxState.flushRetryScheduled = false
    if IsCombatLocked() then
      return
    end
    local drained, report = FlushInbox({
      reason = "post_combat_retry_" .. tostring(nextAttempt) .. ":" .. TrimText(reason),
    })
    if drained then
      ResetFlushRetry()
    elseif type(report) == "table" and math.max(0, tonumber(report.remaining) or 0) > 0 then
      ScheduleFlushRetry(report.outcome)
    end
  end)
  return true
end

local function RunCombatSyncInboxValidation()
  local checks = {}
  local passed = 0
  local function AddCheck(name, expected, actual)
    local matched = expected == actual
    checks[#checks + 1] = {
      name = name,
      passed = matched,
      expected = tostring(expected),
      actual = tostring(actual),
    }
    if matched then
      passed = passed + 1
    end
  end

  local action, attempt, delay = ResolveFlushRetryDecision(0, false, false, 0, true)
  AddCheck("empty inbox does not schedule a retry", "drained/0/0", table.concat({ action, tostring(attempt), tostring(delay) }, "/"))
  action, attempt, delay = ResolveFlushRetryDecision(1, true, false, 0, true)
  AddCheck("combat blocks post-combat retry execution", "combat_locked/0/0", table.concat({ action, tostring(attempt), tostring(delay) }, "/"))
  action, attempt, delay = ResolveFlushRetryDecision(1, false, true, 1, true)
  AddCheck("existing retry coalesces duplicate scheduling", "already_scheduled/1/0", table.concat({ action, tostring(attempt), tostring(delay) }, "/"))
  action, attempt, delay = ResolveFlushRetryDecision(1, false, false, 0, true)
  AddCheck("first retry uses bounded initial delay", "schedule/1/0.5", table.concat({ action, tostring(attempt), tostring(delay) }, "/"))
  action, attempt, delay = ResolveFlushRetryDecision(1, false, false, 1, true)
  AddCheck("later retry increases bounded delay", "schedule/2/1.0", table.concat({ action, tostring(attempt), string.format("%.1f", delay) }, "/"))
  action, attempt, delay = ResolveFlushRetryDecision(1, false, false, INBOX_LIMITS.maxFlushRetries, true)
  AddCheck("retry budget terminates after maximum attempts", "exhausted/3/0", table.concat({ action, tostring(attempt), tostring(delay) }, "/"))

  return passed == #checks, {
    suite = "combat_sync_inbox",
    version = 1,
    total = #checks,
    passed = passed,
    failed = #checks - passed,
    checks = checks,
  }
end

EnsureCombatInboxEventFrame = function()
  if combatInboxEventFrame or type(CreateFrame) ~= "function" then
    return combatInboxEventFrame
  end
  combatInboxEventFrame = CreateFrame("Frame")
  combatInboxEventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
  combatInboxEventFrame:SetScript("OnEvent", function(_, event)
    if event == "PLAYER_REGEN_ENABLED" and type(FlushInbox) == "function" then
      ResetFlushRetry()
      local drained, report = FlushInbox({
        reason = "player_regen_enabled",
      })
      if not drained and type(report) == "table" and math.max(0, tonumber(report.remaining) or 0) > 0 then
        ScheduleFlushRetry(report.outcome)
      end
    end
  end)
  return combatInboxEventFrame
end

EnsureCombatInboxEventFrame()

INTERNAL_API.LIDCOUNCIL_QueueCombatSyncInboxEntry_Impl = QueueInboxEntry
INTERNAL_API.LIDCOUNCIL_DrainCombatSyncInboxSession_Impl = DrainInboxSession
INTERNAL_API.LIDCOUNCIL_ClearCombatSyncInboxSession_Impl = ClearInboxSession
INTERNAL_API.LIDCOUNCIL_ClearCombatSyncInbox_Impl = ClearInbox
INTERNAL_API.LIDCOUNCIL_PruneCombatSyncInbox_Impl = PruneExpiredEntries
INTERNAL_API.LIDCOUNCIL_GetCombatSyncInboxStatus_Impl = GetInboxStatus
INTERNAL_API.LIDCOUNCIL_RegisterCombatSyncInboxApplier_Impl = RegisterInboxApplier
INTERNAL_API.LIDCOUNCIL_UnregisterCombatSyncInboxApplier_Impl = UnregisterInboxApplier
INTERNAL_API.LIDCOUNCIL_FlushCombatSyncInbox_Impl = FlushInbox
INTERNAL_API.LIDCOUNCIL_RunCombatSyncInboxValidation_Impl = RunCombatSyncInboxValidation
INTERNAL_API.LIDCOUNCIL_IsCombatSyncInboxFlushRunning_Impl = function()
  return inboxState.flushRunning == true
end
