// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

//! Primitives for building a cache
//!
//! The entrypoint of this crate is the [`CacheBuilder`] struct.

use super::fifo_cache::FifoCache;
use super::Cache;
use pdk_core::classy::extract::context::ConfigureContext;
use pdk_core::classy::extract::{Extract, FromContext};
use pdk_core::classy::{Clock, SharedData};
use pdk_core::policy_context::api::Metadata;
use std::convert::Infallible;
use std::rc::Rc;
cfg_if::cfg_if! {
    if #[cfg(test)] {
        use crate::mocks::MockConcurrentSharedData as ConcurrentSharedData;
    } else {
        use pdk_core::classy::shared_data::concurrent_shared_data::ConcurrentSharedData;
    }
}

const DEFAULT_MAX_ENTRIES: usize = 100;

/// Represents the base builder for a [`Cache`] instance.
pub struct CacheBuilder {
    clock: Rc<dyn Clock>,
    shared_data: Rc<dyn SharedData>,
    max_entries: usize,
    prefix: String,
}

/// CacheBuilder can be injected in your configuration function.
/// ```rust
/// #[entrypoint]
/// async fn configure(
///     launcher: Launcher,
///     cache_builder: CacheBuilder,
///     Configuration(configuration): Configuration,
/// ) -> anyhow::Result<()> {
///     // configure the cache here.
/// }
/// ```
impl FromContext<ConfigureContext> for CacheBuilder {
    type Error = Infallible;

    fn from_context(context: &ConfigureContext) -> Result<Self, Self::Error> {
        let clock: Rc<dyn Clock> = context.extract()?;
        let shared_data: Rc<dyn SharedData> = context.extract()?;
        let metadata: Metadata = context.extract()?;
        Ok(CacheBuilder {
            clock,
            shared_data,
            max_entries: DEFAULT_MAX_ENTRIES,
            prefix: format!(
                "isolated-cache_id-{}-{}",
                metadata.policy_metadata.policy_name, metadata.policy_metadata.policy_namespace
            ),
        })
    }
}

impl CacheBuilder {
    /// Returns a [`CacheBuilderInstance`] with an ID to uniquely identify the cache.
    ///
    /// _Note: if 2 caches are assigned the same ID in a same policy, they will share the state.
    /// This behavior is extended to other policies when using the
    /// [shared cache option](CacheBuilderInstance::shared)._
    #[allow(clippy::new_ret_no_self)]
    pub fn new(&self, cache_id: String) -> CacheBuilderInstance {
        CacheBuilderInstance {
            clock: Rc::clone(&self.clock),
            shared_data: Rc::clone(&self.shared_data),
            max_entries: self.max_entries,
            prefix: self.prefix.clone(),
            key: cache_id,
        }
    }
}

/// Represents a particular instance of a [`CacheBuilder`]. It includes extra metadata of the
/// policy, required to successfully work in all scenarios involving multiple policies.
pub struct CacheBuilderInstance {
    clock: Rc<dyn Clock>,
    shared_data: Rc<dyn SharedData>,
    max_entries: usize,
    prefix: String,
    key: String,
}

impl CacheBuilderInstance {
    /// Sets the maximum number of entries available for the cache.
    pub fn max_entries(mut self, max_entries: usize) -> Self {
        self.max_entries = max_entries;
        self
    }

    /// Indicates that the cache should not be isolated to the policy. The state will be shared
    /// across different policy instances and different policies that use the same cache ID.
    pub fn shared(mut self) -> Self {
        self.prefix = "shared-cache_id".to_string();
        self
    }

    /// Builds a new [`Cache`] with the corresponding specifications.
    pub fn build(self) -> impl Cache + 'static {
        FifoCache::new(
            format!("{}-{}", self.prefix, self.key),
            self.max_entries,
            ConcurrentSharedData::new(self.clock, self.shared_data),
        )
    }
}
