-- SPDX-FileCopyrightText: 2026 Alexandra de Wit
--
-- SPDX-License-Identifier: MIT

-- | Default tenant shares, computed bounds, and fallbacks without a heap ceiling.
module Ecluse.Composition.MemoryPlan.Bounds (
    -- * Shares of the application heap
    runtimeReserveShareDiv,
    runtimeReserveFloorBytes,
    cacheSharePercent,
    publishSharePercent,
    queueSharePercent,
    mirrorArtifactSharePercent,

    -- * Byte floors, caps, and no-ceiling fallbacks
    responseBytesFallback,
    requestBytesFloor,
    requestBytesCap,
    requestBytesFallback,
    cacheBytesFloor,
    cacheBytesCap,
    cacheBytesFallback,
    cacheEntryExpectedBytes,
    cacheEntriesFloor,
    cacheEntriesCap,
    queueDepthFloor,
    queueDepthCap,
    queueDepthFallback,
    publishAggregateFallbackRequests,
    mirrorArtifactEnvelopeMultiplier,
    mirrorArtifactBytesCap,

    -- * Tenant charges
    queueCharge,
    fixedBufferBytes,
    anyMountMirrors,
    memoryQueueCharged,
) where

import Ecluse.Composition.MemoryPlan.Types (QueueTenantDemand (MemoryQueueTenant, NoQueueTenant))
import Ecluse.Composition.Sizing (mirrorEnqueueBufferDepth)
import Ecluse.Core.Security.Limits (Limits (maxMetadataBytes), defaultLimits)
import Ecluse.Core.Server.MemoryModel (mirrorJobEstimatedBytes)

{- | The divisor taking the runtime reserve off the ceiling. The GC and the RTS get a fifth of
whatever the pod has.
-}
runtimeReserveShareDiv :: Int
runtimeReserveShareDiv :: Int
runtimeReserveShareDiv = Int
5

-- | The smallest runtime reserve, so a tiny pod still leaves the RTS something to breathe with.
runtimeReserveFloorBytes :: Int
runtimeReserveFloorBytes :: Int
runtimeReserveFloorBytes = Int
33554432

{- | The cache aggregate's share of the application heap, the ceiling less the runtime reserve. The
computed shares sum to under 100%, so a plan with no floor and no pin in it fits by construction.
-}
cacheSharePercent :: Int
cacheSharePercent :: Int
cacheSharePercent = Int
30

-- | The publish aggregate's share of the application heap, and the computed request cap's.
publishSharePercent :: Int
publishSharePercent :: Int
publishSharePercent = Int
15

-- | The memory-queue depth's share of the application heap.
queueSharePercent :: Int
queueSharePercent :: Int
queueSharePercent = Int
5

{- | The mirror-artifact tenant's share of the application heap. It is the charged envelope, kept
small so the background back-fill never crowds the serve hot path.
-}
mirrorArtifactSharePercent :: Int
mirrorArtifactSharePercent :: Int
mirrorArtifactSharePercent = Int
4

-- | The metadata ingest ceiling, independent of the heap and CPU controls.
responseBytesFallback :: Int
responseBytesFallback :: Int
responseBytesFallback = Limits -> Int
maxMetadataBytes Limits
defaultLimits

-- | The smallest computed publish-body cap.
requestBytesFloor :: Int
requestBytesFloor :: Int
requestBytesFloor = Int
26214400

-- | The largest computed publish-body cap.
requestBytesCap :: Int
requestBytesCap :: Int
requestBytesCap = Int
104857600

-- | The publish-body cap a pod with no heap-ceiling datapoint gets.
requestBytesFallback :: Int
requestBytesFallback :: Int
requestBytesFallback = Int
26214400

{- | The floor keeps a pod that can afford one caching a useful working set. The shed ladder may
still take the aggregate to zero.
-}
cacheBytesFloor :: Int
cacheBytesFloor :: Int
cacheBytesFloor = Int
67108864

-- | The maximum computed aggregate for local metadata retention.
cacheBytesCap :: Int
cacheBytesCap :: Int
cacheBytesCap = Int
1073741824

-- | The cache aggregate a pod with no heap-ceiling datapoint gets.
cacheBytesFallback :: Int
cacheBytesFallback :: Int
cacheBytesFallback = Int
268435456

{- | Shared entry allowance (16 KiB), matching the present-selected base charge.
Calibration and cardinality limits: <https://github.com/AlexaDeWit/Ecluse/pull/1469#issuecomment-5756635550 PR #1469>.
-}
cacheEntryExpectedBytes :: Int
cacheEntryExpectedBytes :: Int
cacheEntryExpectedBytes = Int
16384

-- | The smallest computed cache entry bound.
cacheEntriesFloor :: Int
cacheEntriesFloor :: Int
cacheEntriesFloor = Int
256

-- | The largest computed cache entry bound.
cacheEntriesCap :: Int
cacheEntriesCap :: Int
cacheEntriesCap = Int
65536

-- | The depth the queue tenant sheds to, and the floor under any computed depth.
queueDepthFloor :: Int
queueDepthFloor :: Int
queueDepthFloor = Int
5000

-- | The largest computed memory-queue depth.
queueDepthCap :: Int
queueDepthCap :: Int
queueDepthCap = Int
100000

-- | The memory-queue depth a pod with no heap-ceiling datapoint gets.
queueDepthFallback :: Int
queueDepthFallback :: Int
queueDepthFallback = Int
50000

{- | With no ceiling datapoint the publish aggregate falls back to a few maximum requests' worth of
concurrent body room.
-}
publishAggregateFallbackRequests :: Int
publishAggregateFallbackRequests :: Int
publishAggregateFallbackRequests = Int
4

{- | The transient envelope one mirrored artifact holds, as a multiple of the buffered tarball B. The
tarball, its base64 'Text', and the publish document coexist at ~3.7x B, rounded up so the peak fits.
-}
mirrorArtifactEnvelopeMultiplier :: Int
mirrorArtifactEnvelopeMultiplier :: Int
mirrorArtifactEnvelopeMultiplier = Int
4

{- | The ceiling the plan clamps the computed artifact cap to, and the no-ceiling fallback. The
charged envelope is therefore at most this times 'mirrorArtifactEnvelopeMultiplier'.
-}
mirrorArtifactBytesCap :: Int
mirrorArtifactBytesCap :: Int
mirrorArtifactBytesCap = Int
512 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1024 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1024

-- | The bytes a memory-queue depth charges. Zero unless the memory backend runs.
queueCharge :: Bool -> Int -> Int
queueCharge :: Bool -> Int -> Int
queueCharge Bool
memoryBacked Int
d = if Bool
memoryBacked then Int
d Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
mirrorJobEstimatedBytes else Int
0

-- | The enqueue hand-off buffer, charged whatever the backend behind it.
fixedBufferBytes :: QueueTenantDemand -> Int
fixedBufferBytes :: QueueTenantDemand -> Int
fixedBufferBytes QueueTenantDemand
demand
    | QueueTenantDemand -> Bool
anyMountMirrors QueueTenantDemand
demand = Int
mirrorEnqueueBufferDepth Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
mirrorJobEstimatedBytes
    | Bool
otherwise = Int
0

-- | Whether any mount mirrors, whatever backend carries the jobs. The enqueue buffer rides this.
anyMountMirrors :: QueueTenantDemand -> Bool
anyMountMirrors :: QueueTenantDemand -> Bool
anyMountMirrors = (QueueTenantDemand -> QueueTenantDemand -> Bool
forall a. Eq a => a -> a -> Bool
/= QueueTenantDemand
NoQueueTenant)

-- | Whether the in-memory queue runs, so its depth charges the heap.
memoryQueueCharged :: QueueTenantDemand -> Bool
memoryQueueCharged :: QueueTenantDemand -> Bool
memoryQueueCharged = (QueueTenantDemand -> QueueTenantDemand -> Bool
forall a. Eq a => a -> a -> Bool
== QueueTenantDemand
MemoryQueueTenant)