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

INTERNAL_API.LIDCOUNCIL_CreateInboundRateLimitHelpers_Impl = function(deps)
  deps = type(deps) == "table" and deps or {}
  local TrimText = type(deps.TrimText) == "function" and deps.TrimText or function(value)
    return tostring(value or "")
  end
  local NormalizeCharacterKey = type(deps.NormalizeCharacterKey) == "function"
    and deps.NormalizeCharacterKey
    or TrimText
  local GetNow = type(deps.GetNow) == "function" and deps.GetNow or function()
    return (type(GetTime) == "function" and GetTime()) or (time and time() or 0)
  end
  local limits = type(deps.Limits) == "table" and deps.Limits or {}
  local laneLimits = {
    control = {
      windowSeconds = math.max(1, tonumber(type(limits.control) == "table" and limits.control.windowSeconds) or 2),
      maxMessages = math.max(1, math.floor(tonumber(type(limits.control) == "table" and limits.control.maxMessages) or 120)),
      maxBytes = math.max(1024, math.floor(tonumber(type(limits.control) == "table" and limits.control.maxBytes) or 65536)),
    },
    bulk = {
      windowSeconds = math.max(1, tonumber(type(limits.bulk) == "table" and limits.bulk.windowSeconds) or 5),
      maxMessages = math.max(1, math.floor(tonumber(type(limits.bulk) == "table" and limits.bulk.maxMessages) or 320)),
      maxBytes = math.max(1024, math.floor(tonumber(type(limits.bulk) == "table" and limits.bulk.maxBytes) or 262144)),
    },
    board = {
      windowSeconds = math.max(1, tonumber(type(limits.board) == "table" and limits.board.windowSeconds) or 5),
      maxMessages = math.max(1, math.floor(tonumber(type(limits.board) == "table" and limits.board.maxMessages) or 240)),
      maxBytes = math.max(1024, math.floor(tonumber(type(limits.board) == "table" and limits.board.maxBytes) or 196608)),
    },
  }
  local state = {
    bySender = {},
    checkedTotal = 0,
    acceptedTotal = 0,
    rateLimitedTotal = 0,
    rateLimitedByLane = {},
    rateLimitedByKind = {},
    maxTrackedSenders = math.max(1, math.floor(tonumber(limits.maxTrackedSenders) or 40)),
    maxTrackedKinds = math.max(1, math.floor(tonumber(limits.maxTrackedKinds) or 32)),
    retentionSeconds = math.max(5, tonumber(limits.retentionSeconds) or 15),
    lastRateLimitedAt = 0,
    lastRateLimitedSender = "",
    lastRateLimitedKind = "",
    lastRateLimitedLane = "",
    lastRateLimitedReason = "",
  }

  local function ResolveLane(kind)
    local resolvedKind = string.upper(TrimText(kind))
    if resolvedKind == "PSYNC_CHUNK" then
      return "bulk"
    end
    if string.sub(resolvedKind, 1, 6) == "BSYNC_" then
      return "board"
    end
    return "control"
  end

  local function Prune(now)
    local cutoff = math.max(0, tonumber(now) or 0) - state.retentionSeconds
    for senderKey, senderState in pairs(state.bySender) do
      if type(senderState) ~= "table" or math.max(0, tonumber(senderState.updatedAt) or 0) < cutoff then
        state.bySender[senderKey] = nil
      end
    end
  end

  local function ResolveSenderState(sender, now)
    local senderKey = string.lower(NormalizeCharacterKey(sender))
    if senderKey == "" then
      senderKey = "unknown"
    end
    local senderState = state.bySender[senderKey]
    if type(senderState) == "table" then
      return senderKey, senderState
    end

    local trackedSenders = 0
    for _ in pairs(state.bySender) do
      trackedSenders = trackedSenders + 1
    end
    if trackedSenders >= state.maxTrackedSenders then
      senderKey = "other"
      senderState = state.bySender[senderKey]
    end
    if type(senderState) ~= "table" then
      senderState = {
        updatedAt = now,
        lanes = {},
        rateLimitedTotal = 0,
      }
      state.bySender[senderKey] = senderState
    end
    return senderKey, senderState
  end

  local function ResolveKindCounterKey(kind)
    local resolvedKind = string.upper(TrimText(kind))
    if resolvedKind == "" or #resolvedKind > 64 then
      resolvedKind = "UNKNOWN"
    end
    if state.rateLimitedByKind[resolvedKind] ~= nil then
      return resolvedKind
    end
    local trackedKinds = 0
    for _ in pairs(state.rateLimitedByKind) do
      trackedKinds = trackedKinds + 1
    end
    if trackedKinds >= state.maxTrackedKinds then
      return "OTHER"
    end
    return resolvedKind
  end

  local function Check(sender, kind, byteCount)
    local now = math.max(0, tonumber(GetNow()) or 0)
    Prune(now)
    local lane = ResolveLane(kind)
    local laneLimit = laneLimits[lane]
    local senderKey, senderState = ResolveSenderState(sender, now)
    local laneState = type(senderState.lanes[lane]) == "table" and senderState.lanes[lane] or {
      startedAt = now,
      messages = 0,
      bytes = 0,
    }
    if now - math.max(0, tonumber(laneState.startedAt) or 0) >= laneLimit.windowSeconds then
      laneState.startedAt = now
      laneState.messages = 0
      laneState.bytes = 0
    end

    local bytes = math.max(0, tonumber(byteCount) or 0)
    local nextMessages = math.max(0, tonumber(laneState.messages) or 0) + 1
    local nextBytes = math.max(0, tonumber(laneState.bytes) or 0) + bytes
    state.checkedTotal = state.checkedTotal + 1
    senderState.updatedAt = now
    senderState.lanes[lane] = laneState
    if nextMessages <= laneLimit.maxMessages and nextBytes <= laneLimit.maxBytes then
      laneState.messages = nextMessages
      laneState.bytes = nextBytes
      state.acceptedTotal = state.acceptedTotal + 1
      return true, "accepted", lane
    end

    local reason = "incoming_rate_limited_" .. lane
    local kindKey = ResolveKindCounterKey(kind)
    state.rateLimitedTotal = state.rateLimitedTotal + 1
    state.rateLimitedByLane[lane] = math.max(0, tonumber(state.rateLimitedByLane[lane]) or 0) + 1
    state.rateLimitedByKind[kindKey] = math.max(0, tonumber(state.rateLimitedByKind[kindKey]) or 0) + 1
    senderState.rateLimitedTotal = math.max(0, tonumber(senderState.rateLimitedTotal) or 0) + 1
    state.lastRateLimitedAt = now
    state.lastRateLimitedSender = senderKey
    state.lastRateLimitedKind = kindKey
    state.lastRateLimitedLane = lane
    state.lastRateLimitedReason = reason
    return false, reason, lane
  end

  local function CopyCounterMap(source)
    local copy = {}
    for key, value in pairs(type(source) == "table" and source or {}) do
      copy[key] = math.max(0, tonumber(value) or 0)
    end
    return copy
  end

  local function Snapshot()
    Prune(math.max(0, tonumber(GetNow()) or 0))
    local trackedSenders = 0
    for _ in pairs(state.bySender) do
      trackedSenders = trackedSenders + 1
    end
    return {
      checkedTotal = math.max(0, tonumber(state.checkedTotal) or 0),
      acceptedTotal = math.max(0, tonumber(state.acceptedTotal) or 0),
      rateLimitedTotal = math.max(0, tonumber(state.rateLimitedTotal) or 0),
      rateLimitedByLane = CopyCounterMap(state.rateLimitedByLane),
      rateLimitedByKind = CopyCounterMap(state.rateLimitedByKind),
      trackedSenders = trackedSenders,
      maxTrackedSenders = state.maxTrackedSenders,
      retentionSeconds = state.retentionSeconds,
      laneLimits = {
        control = {
          windowSeconds = laneLimits.control.windowSeconds,
          maxMessages = laneLimits.control.maxMessages,
          maxBytes = laneLimits.control.maxBytes,
        },
        bulk = {
          windowSeconds = laneLimits.bulk.windowSeconds,
          maxMessages = laneLimits.bulk.maxMessages,
          maxBytes = laneLimits.bulk.maxBytes,
        },
        board = {
          windowSeconds = laneLimits.board.windowSeconds,
          maxMessages = laneLimits.board.maxMessages,
          maxBytes = laneLimits.board.maxBytes,
        },
      },
      lastRateLimitedAt = math.max(0, tonumber(state.lastRateLimitedAt) or 0),
      lastRateLimitedSender = TrimText(state.lastRateLimitedSender),
      lastRateLimitedKind = TrimText(state.lastRateLimitedKind),
      lastRateLimitedLane = TrimText(state.lastRateLimitedLane),
      lastRateLimitedReason = TrimText(state.lastRateLimitedReason),
    }
  end

  local function Clear()
    state.bySender = {}
    state.checkedTotal = 0
    state.acceptedTotal = 0
    state.rateLimitedTotal = 0
    state.rateLimitedByLane = {}
    state.rateLimitedByKind = {}
    state.lastRateLimitedAt = 0
    state.lastRateLimitedSender = ""
    state.lastRateLimitedKind = ""
    state.lastRateLimitedLane = ""
    state.lastRateLimitedReason = ""
  end

  return {
    Check = Check,
    Snapshot = Snapshot,
    Clear = Clear,
    ResolveLane = ResolveLane,
  }
end
