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

-- | One selected storage provider owns all metadata retention capabilities.
module Ecluse.Core.Server.Cache.Provider (
    CacheProvider,
    cacheProvider,
    localCacheProvider,
    providerFull,
    providerVersion,
    providerAssembled,
) where

import Data.ByteString qualified as BS

import Ecluse.Core.Registry.Metadata (VersionRead)
import Ecluse.Core.Server.Cache.Backend (BackendStorage (LocalStorage), RetentionBackend, RetentionOperations, retentionBackend, supportsFullRetention)
import Ecluse.Core.Server.Cache.Backend.Local (newLocalPool, newPooledRetention)
import Ecluse.Core.Server.Cache.Types
import Ecluse.Core.Server.Cache.VersionWeight (weighVersion)

-- | Missing capabilities remain uncached. They never fall back to another storage provider.
data CacheProvider = CacheProvider
    { CacheProvider -> Maybe (RetentionBackend Text CacheEntry)
cpFull :: Maybe (RetentionBackend Text CacheEntry)
    -- ^ Full metadata is absent for local storage, regardless of the supplied operations.
    , CacheProvider -> Maybe (RetentionBackend Text VersionRead)
cpVersion :: Maybe (RetentionBackend Text VersionRead)
    -- ^ Selected reads may use an adapter-owned projection without loading a full value locally.
    , CacheProvider -> Maybe (RetentionBackend Text ByteString)
cpAssembled :: Maybe (RetentionBackend Text ByteString)
    }

-- | Full retention from the selected provider, absent for local storage.
providerFull :: CacheProvider -> Maybe (RetentionBackend Text CacheEntry)
providerFull :: CacheProvider -> Maybe (RetentionBackend Text CacheEntry)
providerFull = CacheProvider -> Maybe (RetentionBackend Text CacheEntry)
cpFull

-- | Selected retention from the same provider, without a full-document read.
providerVersion :: CacheProvider -> Maybe (RetentionBackend Text VersionRead)
providerVersion :: CacheProvider -> Maybe (RetentionBackend Text VersionRead)
providerVersion = CacheProvider -> Maybe (RetentionBackend Text VersionRead)
cpVersion

-- | Assembled retention from the same provider, with no local fallback.
providerAssembled :: CacheProvider -> Maybe (RetentionBackend Text ByteString)
providerAssembled :: CacheProvider -> Maybe (RetentionBackend Text ByteString)
providerAssembled = CacheProvider -> Maybe (RetentionBackend Text ByteString)
cpAssembled

-- | Classify every operation together. Local storage cannot opt into full retention.
cacheProvider :: BackendStorage -> Maybe (RetentionOperations Text CacheEntry) -> Maybe (RetentionOperations Text VersionRead) -> Maybe (RetentionOperations Text ByteString) -> CacheProvider
cacheProvider :: BackendStorage
-> Maybe (RetentionOperations Text CacheEntry)
-> Maybe (RetentionOperations Text VersionRead)
-> Maybe (RetentionOperations Text ByteString)
-> CacheProvider
cacheProvider BackendStorage
storage Maybe (RetentionOperations Text CacheEntry)
full Maybe (RetentionOperations Text VersionRead)
version Maybe (RetentionOperations Text ByteString)
assembled =
    CacheProvider
        { cpFull :: Maybe (RetentionBackend Text CacheEntry)
cpFull = (BackendStorage
-> RetentionOperations Text CacheEntry
-> RetentionBackend Text CacheEntry
forall k v.
BackendStorage -> RetentionOperations k v -> RetentionBackend k v
retentionBackend BackendStorage
storage (RetentionOperations Text CacheEntry
 -> RetentionBackend Text CacheEntry)
-> Maybe (RetentionOperations Text CacheEntry)
-> Maybe (RetentionBackend Text CacheEntry)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe (RetentionOperations Text CacheEntry)
full) Maybe (RetentionBackend Text CacheEntry)
-> Maybe () -> Maybe (RetentionBackend Text CacheEntry)
forall a b. Maybe a -> Maybe b -> Maybe a
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (BackendStorage -> Bool
supportsFullRetention BackendStorage
storage)
        , cpVersion :: Maybe (RetentionBackend Text VersionRead)
cpVersion = BackendStorage
-> RetentionOperations Text VersionRead
-> RetentionBackend Text VersionRead
forall k v.
BackendStorage -> RetentionOperations k v -> RetentionBackend k v
retentionBackend BackendStorage
storage (RetentionOperations Text VersionRead
 -> RetentionBackend Text VersionRead)
-> Maybe (RetentionOperations Text VersionRead)
-> Maybe (RetentionBackend Text VersionRead)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe (RetentionOperations Text VersionRead)
version
        , cpAssembled :: Maybe (RetentionBackend Text ByteString)
cpAssembled = BackendStorage
-> RetentionOperations Text ByteString
-> RetentionBackend Text ByteString
forall k v.
BackendStorage -> RetentionOperations k v -> RetentionBackend k v
retentionBackend BackendStorage
storage (RetentionOperations Text ByteString
 -> RetentionBackend Text ByteString)
-> Maybe (RetentionOperations Text ByteString)
-> Maybe (RetentionBackend Text ByteString)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe (RetentionOperations Text ByteString)
assembled
        }

-- | Retain only selected versions and assembled responses in bounded local stores.
localCacheProvider :: CacheConfig -> IO CacheProvider
localCacheProvider :: CacheConfig -> IO CacheProvider
localCacheProvider CacheConfig
config = do
    pool <- Int -> Int -> IO LocalPool
newLocalPool (CacheConfig -> Int
cacheMaxEntries CacheConfig
config) (CacheConfig -> Int
cacheMaxBytes CacheConfig
config)
    version <- newPooledRetention pool (cacheTtl config) (cacheVersionBudget config) weighVersion
    assembled <- newPooledRetention pool (cacheTtl config) (cacheAssembledBudget config) weighAssembled
    pure (cacheProvider LocalStorage Nothing (Just version) (Just assembled))

weighAssembled :: ByteString -> Int
weighAssembled :: ByteString -> Int
weighAssembled ByteString
bytes = ByteString -> Int
BS.length ByteString
bytes Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
256