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_CreateProfileSyncGenerationOps_Impl = function(deps)
  deps = type(deps) == "table" and deps or {}

  local TrimText = deps.TrimText
  local NormalizeCharacterKey = deps.NormalizeCharacterKey
  local ttlSeconds = math.max(0, tonumber(deps.ttlSeconds) or 0)
  local memberTransferGenerationStateBySession = {}

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

  local function CloneGenerationLaneState(state)
    return {
      generation = math.max(0, tonumber(type(state) == "table" and state.generation or 0) or 0),
      at = math.max(0, tonumber(type(state) == "table" and state.at or 0) or 0),
    }
  end

  local function NormalizeProfileTransferLane(reason, subdomains, transferKind)
    local resolvedReason = string.lower(TrimText(reason))
    if resolvedReason ~= "" then
      if string.find(resolvedReason, "archive", 1, true) ~= nil then
        return "archive"
      end
      if string.find(resolvedReason, "stats", 1, true) ~= nil
        or string.find(resolvedReason, "award", 1, true) ~= nil
        or string.find(resolvedReason, "vote", 1, true) ~= nil
      then
        return "stats"
      end
      return "live"
    end

    local onlyLootStats = true
    local sawSubdomain = false
    for _, subdomain in ipairs(type(subdomains) == "table" and subdomains or {}) do
      local resolvedSubdomain = string.lower(TrimText(subdomain))
      if resolvedSubdomain ~= "" then
        sawSubdomain = true
        if resolvedSubdomain == "lrsr"
          or resolvedSubdomain == "lrsv"
          or resolvedSubdomain:find("^lre:", 1, false)
          or resolvedSubdomain:find("^lve:", 1, false)
        then
          -- stats lane
        elseif resolvedSubdomain:find("^llr:", 1, false) or resolvedSubdomain:find("^lmr:", 1, false) then
          onlyLootStats = false
          break
        else
          onlyLootStats = false
          break
        end
      end
    end
    if sawSubdomain and onlyLootStats then
      return "stats"
    end
    if string.upper(TrimText(transferKind)) == "FULL" then
      return "live"
    end
    return "live"
  end

  local function GetMemberTransferGenerationBucket(sessionId, memberKey, create)
    local resolvedSessionId = string.lower(TrimText(sessionId))
    local resolvedMemberKey = string.lower(NormalizeCharacterKey(memberKey))
    if resolvedSessionId == "" or resolvedMemberKey == "" then
      return nil
    end
    local sessionState = memberTransferGenerationStateBySession[resolvedSessionId]
    if type(sessionState) ~= "table" then
      if not create then
        return nil
      end
      sessionState = {}
      memberTransferGenerationStateBySession[resolvedSessionId] = sessionState
    end
    local bucket = sessionState[resolvedMemberKey]
    if type(bucket) ~= "table" then
      if not create then
        return nil
      end
      bucket = {
        sent = {},
        applied = {},
      }
      sessionState[resolvedMemberKey] = bucket
    end
    return bucket
  end

  local function RememberTransferGenerationState(sessionId, memberKey, lane, generation, at, applied)
    local resolvedLane = string.lower(TrimText(lane))
    local resolvedGeneration = math.max(0, tonumber(generation) or 0)
    if resolvedLane == "" or resolvedGeneration <= 0 then
      return
    end
    local bucket = GetMemberTransferGenerationBucket(sessionId, memberKey, true)
    if type(bucket) ~= "table" then
      return
    end
    local target = applied and bucket.applied or bucket.sent
    local current = CloneGenerationLaneState(target[resolvedLane])
    if resolvedGeneration >= current.generation then
      target[resolvedLane] = {
        generation = resolvedGeneration,
        at = math.max(current.at, math.max(0, tonumber(at) or 0)),
      }
    end
  end

  local function GetMemberTransferGenerationState(sessionId, memberKey)
    local bucket = GetMemberTransferGenerationBucket(sessionId, memberKey, false)
    if type(bucket) ~= "table" then
      return {
        sent = {},
        applied = {},
      }
    end
    local sent = {}
    local applied = {}
    for lane, state in pairs(type(bucket.sent) == "table" and bucket.sent or {}) do
      sent[lane] = CloneGenerationLaneState(state)
    end
    for lane, state in pairs(type(bucket.applied) == "table" and bucket.applied or {}) do
      applied[lane] = CloneGenerationLaneState(state)
    end
    return {
      sent = sent,
      applied = applied,
    }
  end

  local function HasRecentGenerationConvergence(candidate, laneState)
    candidate = type(candidate) == "table" and candidate or {}
    laneState = type(laneState) == "table" and laneState or {}
    local appliedGeneration = math.max(0, tonumber(type(laneState.applied) == "table" and laneState.applied.generation or 0) or 0)
    local appliedAt = math.max(0, tonumber(type(laneState.applied) == "table" and laneState.applied.at or 0) or 0)
    local sentGeneration = math.max(0, tonumber(type(laneState.sent) == "table" and laneState.sent.generation or 0) or 0)
    if appliedGeneration <= 0 or appliedAt <= 0 then
      return false
    end
    if sentGeneration > 0 and appliedGeneration < sentGeneration then
      return false
    end
    if ttlSeconds > 0 and math.max(0, GetNow() - appliedAt) > ttlSeconds then
      return false
    end
    return appliedAt >= math.max(0, tonumber(candidate.updatedAt) or 0)
  end

  local function ClearSessionTransferGenerationState(sessionId)
    local resolvedSessionId = string.lower(TrimText(sessionId))
    if resolvedSessionId ~= "" then
      memberTransferGenerationStateBySession[resolvedSessionId] = nil
    end
  end

  local function NormalizeArchiveTransactionScope(scope)
    local resolvedScope = string.lower(TrimText(scope))
    if resolvedScope == "lootlist" or resolvedScope == "loot_list" or resolvedScope == "loot" then
      return "lootList"
    end
    if resolvedScope == "itemhistory" or resolvedScope == "item_history" or resolvedScope == "items" then
      return "itemHistory"
    end
    if resolvedScope == "sessionhistory" or resolvedScope == "session_history" or resolvedScope == "session" then
      return "sessionHistory"
    end
    if resolvedScope == "funawards" or resolvedScope == "fun_awards" or resolvedScope == "awards" then
      return "funAwards"
    end
    return ""
  end

  local function EnsureArchiveTransactionStore(member)
    if type(member) ~= "table" then
      return nil
    end
    if type(member.p3ArchiveTransactions) ~= "table" then
      member.p3ArchiveTransactions = {}
    end
    return member.p3ArchiveTransactions
  end

  local function BeginArchiveTransaction(member, scope, details)
    local resolvedScope = NormalizeArchiveTransactionScope(scope)
    local store = EnsureArchiveTransactionStore(member)
    if resolvedScope == "" or type(store) ~= "table" then
      return nil, "invalid_archive_transaction"
    end
    details = type(details) == "table" and details or {}
    local previous = type(store[resolvedScope]) == "table" and store[resolvedScope] or {}
    local expectedChecksum = TrimText(details.expectedChecksum)
    local transferId = TrimText(details.transferId)
    local sessionId = TrimText(details.sessionId)
    local generation = math.max(0, tonumber(details.generation) or 0)
    local sameSession = sessionId ~= "" and sessionId == TrimText(previous.sessionId)
    local sameAttempt = sameSession and transferId ~= "" and transferId == TrimText(previous.transferId)
      and generation > 0 and generation == math.max(0, tonumber(previous.generation) or 0)
    local attempts = math.max(0, tonumber(previous.attempts) or 0)
    if not sameAttempt then
      attempts = details.resetAttempts ~= true and sameSession and expectedChecksum ~= ""
        and expectedChecksum == TrimText(previous.expectedChecksum)
        and TrimText(previous.state) ~= "checksum_confirmed"
        and attempts + 1
        or 1
    end
    local now = math.max(0, tonumber(details.at) or GetNow())
    local transaction = {
      scope = resolvedScope,
      state = "queued",
      sessionId = sessionId,
      transferId = transferId,
      generation = generation,
      expectedChecksum = expectedChecksum,
      manifestChecksum = TrimText(details.manifestChecksum) ~= "" and TrimText(details.manifestChecksum) or expectedChecksum,
      manifestLeafCount = math.max(0, tonumber(details.manifestLeafCount) or 0),
      appliedChecksum = "",
      attempts = attempts,
      maxAttempts = math.max(1, tonumber(details.maxAttempts) or tonumber(previous.maxAttempts) or 3),
      queuedAt = now,
      sentAt = 0,
      receiverQueuedAt = 0,
      appliedAt = 0,
      confirmedAt = 0,
      retryAt = 0,
      failedAt = 0,
      updatedAt = now,
      lastError = "",
      reason = TrimText(details.reason),
      transferKind = TrimText(details.transferKind),
    }
    store[resolvedScope] = transaction
    return transaction, "queued"
  end

  local function MarkArchiveTransactionState(member, scope, state, details)
    local resolvedScope = NormalizeArchiveTransactionScope(scope)
    local store = EnsureArchiveTransactionStore(member)
    local transaction = type(store) == "table" and store[resolvedScope] or nil
    if type(transaction) ~= "table" then
      return false, "missing_archive_transaction"
    end
    details = type(details) == "table" and details or {}
    local transferId = TrimText(details.transferId)
    local generation = math.max(0, tonumber(details.generation) or 0)
    if transferId ~= "" and TrimText(transaction.transferId) ~= "" and transferId ~= TrimText(transaction.transferId) then
      return false, "stale_archive_transfer"
    end
    if generation > 0 and math.max(0, tonumber(transaction.generation) or 0) > 0
      and generation ~= math.max(0, tonumber(transaction.generation) or 0)
    then
      return false, "stale_archive_generation"
    end
    local resolvedState = string.lower(TrimText(state))
    if resolvedState ~= "queued" and resolvedState ~= "sending" and resolvedState ~= "receiver_queued"
      and resolvedState ~= "applied" and resolvedState ~= "checksum_confirmed"
      and resolvedState ~= "retrying" and resolvedState ~= "failed"
    then
      return false, "invalid_archive_transaction_state"
    end
    if TrimText(transaction.state) == "checksum_confirmed" and resolvedState ~= "checksum_confirmed" then
      return false, "archive_transaction_already_confirmed"
    end
    local now = math.max(0, tonumber(details.at) or GetNow())
    transaction.state = resolvedState
    transaction.updatedAt = now
    transaction.lastError = TrimText(details.error)
    if resolvedState == "sending" then
      transaction.sentAt = now
    elseif resolvedState == "receiver_queued" then
      transaction.receiverQueuedAt = now
    elseif resolvedState == "applied" then
      transaction.appliedAt = now
    elseif resolvedState == "checksum_confirmed" then
      transaction.confirmedAt = now
      transaction.appliedChecksum = TrimText(details.appliedChecksum)
      transaction.lastError = ""
    elseif resolvedState == "retrying" then
      transaction.retryAt = now
    elseif resolvedState == "failed" then
      transaction.failedAt = now
    end
    return true, resolvedState
  end

  local function ReconcileArchiveTransactions(member)
    local store = type(member) == "table" and member.p3ArchiveTransactions or nil
    if type(store) ~= "table" then
      return false, {}
    end
    local changed = false
    local confirmedScopes = {}
    local checksumFields = {
      lootList = "lootListArchiveChecksum",
      itemHistory = "itemHistoryArchiveChecksum",
      sessionHistory = "sessionHistoryArchiveChecksum",
      funAwards = "funAwardsArchiveChecksum",
    }
    for scope, checksumField in pairs(checksumFields) do
      local transaction = type(store[scope]) == "table" and store[scope] or nil
      local expectedChecksum = TrimText(type(transaction) == "table" and transaction.expectedChecksum or "")
      local appliedChecksum = TrimText(type(member) == "table" and member[checksumField] or "")
      if type(transaction) == "table" and expectedChecksum ~= "" and appliedChecksum == expectedChecksum
        and TrimText(transaction.state) ~= "checksum_confirmed"
      then
        local confirmed = MarkArchiveTransactionState(member, scope, "checksum_confirmed", {
          transferId = transaction.transferId,
          generation = transaction.generation,
          appliedChecksum = appliedChecksum,
        })
        if confirmed then
          changed = true
          confirmedScopes[#confirmedScopes + 1] = scope
        end
      end
    end
    return changed, confirmedScopes
  end

  local function GetArchiveTransactionState(member)
    local store = type(member) == "table" and member.p3ArchiveTransactions or nil
    local result = {}
    for _, scope in ipairs({ "lootList", "itemHistory", "sessionHistory", "funAwards" }) do
      local transaction = type(store) == "table" and store[scope] or nil
      if type(transaction) == "table" then
        local clone = {}
        for key, value in pairs(transaction) do
          if type(value) ~= "table" then
            clone[key] = value
          end
        end
        result[scope] = clone
      end
    end
    return result
  end

  local function RunArchiveTransactionValidation()
    local checks = {}
    local passed = 0
    local function AddCheck(name, expected, actual)
      local ok = expected == actual
      checks[#checks + 1] = {
        name = name,
        expected = tostring(expected),
        actual = tostring(actual),
        passed = ok,
      }
      if ok then
        passed = passed + 1
      end
    end
    local member = {}
    local first = BeginArchiveTransaction(member, "funAwards", {
      sessionId = "SESSION-A",
      transferId = "TRANSFER-1",
      generation = 1,
      expectedChecksum = "FUN-AWARDS-CHECKSUM",
    })
    local retry = BeginArchiveTransaction(member, "funAwards", {
      sessionId = "SESSION-A",
      transferId = "TRANSFER-2",
      generation = 2,
      expectedChecksum = "FUN-AWARDS-CHECKSUM",
    })
    local newLobby = BeginArchiveTransaction(member, "funAwards", {
      sessionId = "SESSION-B",
      transferId = "TRANSFER-3",
      generation = 1,
      expectedChecksum = "FUN-AWARDS-CHECKSUM",
    })
    local explicitRepair = BeginArchiveTransaction(member, "funAwards", {
      sessionId = "SESSION-B",
      transferId = "TRANSFER-4",
      generation = 2,
      expectedChecksum = "FUN-AWARDS-CHECKSUM",
      resetAttempts = true,
    })
    AddCheck("archive transaction stores its session", "SESSION-A", type(first) == "table" and first.sessionId or "")
    AddCheck("archive retry increments inside one session", 2, type(retry) == "table" and retry.attempts or 0)
    AddCheck("archive retry resets for a new session", 1, type(newLobby) == "table" and newLobby.attempts or 0)
    AddCheck("explicit host repair resets its bounded retry budget", 1,
      type(explicitRepair) == "table" and explicitRepair.attempts or 0)

    local partialMember = {}
    BeginArchiveTransaction(partialMember, "lootList", {
      sessionId = "SESSION-PARTIAL",
      transferId = "TRANSFER-LOOT",
      generation = 1,
      expectedChecksum = "LOOT-LIST-CHECKSUM",
    })
    BeginArchiveTransaction(partialMember, "funAwards", {
      sessionId = "SESSION-PARTIAL",
      transferId = "TRANSFER-AWARDS",
      generation = 1,
      expectedChecksum = "FUN-AWARDS-CHECKSUM",
    })
    partialMember.lootListArchiveChecksum = "LOOT-LIST-CHECKSUM"
    local partialChanged = ReconcileArchiveTransactions(partialMember)
    local partialState = GetArchiveTransactionState(partialMember)
    AddCheck("independent archive proof reconciles its matching scope", true, partialChanged)
    AddCheck("independent archive proof confirms the matching transaction", "checksum_confirmed",
      type(partialState.lootList) == "table" and partialState.lootList.state or "")
    AddCheck("independent archive proof leaves unrelated transactions pending", "queued",
      type(partialState.funAwards) == "table" and partialState.funAwards.state or "")
    return passed == #checks, {
      suite = "archive_transaction_sessions",
      total = #checks,
      passed = passed,
      failed = #checks - passed,
      checks = checks,
    }
  end

  return {
    NormalizeProfileTransferLane = NormalizeProfileTransferLane,
    RememberTransferGenerationState = RememberTransferGenerationState,
    GetMemberTransferGenerationState = GetMemberTransferGenerationState,
    HasRecentGenerationConvergence = HasRecentGenerationConvergence,
    ClearSessionTransferGenerationState = ClearSessionTransferGenerationState,
    NormalizeArchiveTransactionScope = NormalizeArchiveTransactionScope,
    BeginArchiveTransaction = BeginArchiveTransaction,
    MarkArchiveTransactionState = MarkArchiveTransactionState,
    ReconcileArchiveTransactions = ReconcileArchiveTransactions,
    GetArchiveTransactionState = GetArchiveTransactionState,
    RunArchiveTransactionValidation = RunArchiveTransactionValidation,
  }
end
