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

{- | Every tenant's desired share of a heap ceiling, resolved before the shed ladder in
"Ecluse.Composition.MemoryPlan.Shed" walks it. A configured value wins its own bound here,
so a pinned tenant enters the ladder at the operator's number and a computed one enters at
its share of the application heap, bracketed by the floors and caps in
"Ecluse.Composition.MemoryPlan.Bounds".
-}
module Ecluse.Composition.MemoryPlan.Demands (
    tenantDemands,
) where

import Data.Ord (clamp)

import Ecluse.Composition.MemoryPlan.Bounds (
    anyMountMirrors,
    cacheBytesCap,
    cacheBytesFloor,
    cacheSharePercent,
    fixedBufferBytes,
    memoryQueueCharged,
    mirrorArtifactBytesCap,
    mirrorArtifactEnvelopeMultiplier,
    mirrorArtifactSharePercent,
    publishSharePercent,
    queueDepthCap,
    queueDepthFloor,
    queueSharePercent,
    requestBytesCap,
    requestBytesFloor,
    responseBytesFallback,
    runtimeReserveFloorBytes,
    runtimeReserveShareDiv,
 )
import Ecluse.Composition.MemoryPlan.Internal (
    OverridePins (opArtifact, opCache, opDepth, opRequest),
    PlanInputs (piAllocAreaBytes, piCache, piCapabilities, piCpuAdmission, piLimits, piPublishConfigured, piQueueDemand),
    TenantDemands (..),
 )
import Ecluse.Composition.MemoryPlan.Override (configuredPins)
import Ecluse.Composition.MemoryPlan.Transient (liveCacheShareBytes)
import Ecluse.Config (CacheSettings (csMaxEntries), LimitsSettings (limMaxResponseBytes))
import Ecluse.Core.Server.MemoryModel (mirrorJobEstimatedBytes)

-- | Every tenant's desired share over a heap ceiling h, before the shed ladder walks it.
tenantDemands :: PlanInputs -> Int -> TenantDemands
tenantDemands :: PlanInputs -> Int -> TenantDemands
tenantDemands PlanInputs
inputs Int
h =
    TenantDemands
        { tdCeiling :: Int
tdCeiling = Int
h
        , tdReserve :: Int
tdReserve = Int
reserve
        , tdFixedBuffers :: Int
tdFixedBuffers = QueueTenantDemand -> Int
fixedBufferBytes QueueTenantDemand
demand
        , tdPins :: OverridePins
tdPins = OverridePins
pins
        , tdCacheDesired :: Int
tdCacheDesired = Int -> Maybe Int -> Int
forall a. a -> Maybe a -> a
fromMaybe ((Int, Int) -> Int -> Int
forall a. Ord a => (a, a) -> a -> a
clamp (Int
cacheBytesFloor, Int
cacheBytesCap) (Int -> Int -> Int
forall a. Ord a => a -> a -> a
min Int
liveCacheBytes (Int
appHeap Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
cacheSharePercent Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
100))) (OverridePins -> Maybe Int
opCache OverridePins
pins)
        , tdCacheEntriesExplicit :: Maybe Int
tdCacheEntriesExplicit = CacheSettings -> Maybe Int
csMaxEntries (PlanInputs -> CacheSettings
piCache PlanInputs
inputs)
        , tdAdmissionDesired :: Int
tdAdmissionDesired = PlanInputs -> Int
piCpuAdmission PlanInputs
inputs
        , tdResponseFinal :: Int
tdResponseFinal = Int -> Maybe Int -> Int
forall a. a -> Maybe a -> a
fromMaybe Int
responseBytesFallback (LimitsSettings -> Maybe Int
limMaxResponseBytes (PlanInputs -> LimitsSettings
piLimits PlanInputs
inputs))
        , tdPublishConfigured :: Bool
tdPublishConfigured = PlanInputs -> Bool
piPublishConfigured PlanInputs
inputs
        , tdPublishDesired :: Int
tdPublishDesired = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
requestFinal (Int
appHeap Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
publishSharePercent Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
100)
        , tdRequestFinal :: Int
tdRequestFinal = Int
requestFinal
        , tdRequestComputed :: Int
tdRequestComputed = Int
requestComputed
        , tdDepthDesired :: Int
tdDepthDesired = Int -> Maybe Int -> Int
forall a. a -> Maybe a -> a
fromMaybe ((Int, Int) -> Int -> Int
forall a. Ord a => (a, a) -> a -> a
clamp (Int
queueDepthFloor, Int
queueDepthCap) ((Int
appHeap Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
queueSharePercent Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
100) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
mirrorJobEstimatedBytes)) (OverridePins -> Maybe Int
opDepth OverridePins
pins)
        , tdMemoryBacked :: Bool
tdMemoryBacked = QueueTenantDemand -> Bool
memoryQueueCharged QueueTenantDemand
demand
        , tdMirrors :: Bool
tdMirrors = QueueTenantDemand -> Bool
anyMountMirrors QueueTenantDemand
demand
        , tdArtifactCapDesired :: Int
tdArtifactCapDesired = Int
artifactCapDesired
        , tdMirrorChargeDesired :: Int
tdMirrorChargeDesired = if QueueTenantDemand -> Bool
anyMountMirrors QueueTenantDemand
demand then Int
artifactCapDesired Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
mirrorArtifactEnvelopeMultiplier else Int
0
        }
  where
    demand :: QueueTenantDemand
demand = PlanInputs -> QueueTenantDemand
piQueueDemand PlanInputs
inputs
    pins :: OverridePins
pins = PlanInputs -> OverridePins
configuredPins PlanInputs
inputs
    reserve :: Int
reserve = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
runtimeReserveFloorBytes (Int
h Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
runtimeReserveShareDiv)
    appHeap :: Int
appHeap = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 (Int
h Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
reserve)
    -- The cache is live data, so it takes its share of the live target, not of the heap.
    liveCacheBytes :: Int
liveCacheBytes = Int -> Int -> Int -> Int
liveCacheShareBytes Int
h (PlanInputs -> Int
piCapabilities PlanInputs
inputs) (PlanInputs -> Int
piAllocAreaBytes PlanInputs
inputs)
    requestComputed :: Int
requestComputed = (Int, Int) -> Int -> Int
forall a. Ord a => (a, a) -> a -> a
clamp (Int
requestBytesFloor, Int
requestBytesCap) (Int
appHeap Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
publishSharePercent Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
100)
    requestFinal :: Int
requestFinal = Int -> Maybe Int -> Int
forall a. a -> Maybe a -> a
fromMaybe Int
requestComputed (OverridePins -> Maybe Int
opRequest OverridePins
pins)
    -- The charged envelope is the cap times the envelope multiplier, so dividing the
    -- share back down keeps the mirror tenant a bounded share of the heap.
    artifactCapDesired :: Int
artifactCapDesired =
        Int -> Maybe Int -> Int
forall a. a -> Maybe a -> a
fromMaybe
            (Int -> Int -> Int
forall a. Ord a => a -> a -> a
min Int
mirrorArtifactBytesCap ((Int
appHeap Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
mirrorArtifactSharePercent Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
100) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
mirrorArtifactEnvelopeMultiplier))
            (OverridePins -> Maybe Int
opArtifact OverridePins
pins)