//! Read priority walk shared by every per-scroll_id and collection-shape
//! coordinator method.
//!
//! The walk honours per-backend `enabled`, caller-supplied capability
//! predicates, and current health. Per-scroll_id reads populate a positive
//! LRU cache keyed on `scroll_id`, so the second read for the same session
//! can short-circuit the priority walk.

use std::sync::Arc;

use uuid::Uuid;

use crate::backend::ArchiveBackend;
use crate::error::Result;
use crate::registry::ArchiveRegistration;

use super::Archivist;

impl Archivist {
    /// Walk enabled + healthy registrations in `read_priority` order.
    ///
    /// `predicate` decides whether a backend can serve the read (typically a
    /// capability check). `op` is invoked on the first matching backend:
    /// - `Ok(Some(value))` — wins the walk; per-scroll_id cache is updated; returned.
    /// - `Ok(None)` — backend doesn't have it; continue.
    /// - `Err(_)` — drift the backend's health and continue.
    pub(crate) async fn read_walk_per_session<T, F, Fut, P>(
        &self,
        scroll_id: Uuid,
        predicate: P,
        op: F,
    ) -> Result<Option<T>>
    where
        T: Send,
        P: Fn(&ArchiveRegistration) -> bool + Send + Sync,
        F: Fn(Arc<dyn ArchiveBackend>) -> Fut + Send + Sync,
        Fut: std::future::Future<Output = Result<Option<T>>> + Send,
    {
        // Cache hit: try the cached backend first.
        if let Some(cached_name) = self.read_cache.get(scroll_id).await {
            if let Some(reg) = self.find_registration(&cached_name).await {
                if predicate(&reg) && reg.enabled && !self.is_unavailable(&reg).await {
                    match op(reg.backend.clone()).await {
                        Ok(Some(value)) => return Ok(Some(value)),
                        Ok(None) => {
                            // Cached entry no longer holds — invalidate and fall through.
                            self.read_cache.invalidate(scroll_id).await;
                        }
                        Err(_) => {
                            self.record_read_failure(&reg).await;
                            self.read_cache.invalidate(scroll_id).await;
                        }
                    }
                }
            }
        }

        // Priority walk.
        let regs: Vec<Arc<ArchiveRegistration>> = self.registrations.read().await.clone();
        for reg in regs.iter() {
            if !reg.enabled || !predicate(reg) || self.is_unavailable(reg).await {
                continue;
            }
            match op(reg.backend.clone()).await {
                Ok(Some(value)) => {
                    self.record_read_success(reg).await;
                    self.read_cache.put(scroll_id, reg.name.clone()).await;
                    return Ok(Some(value));
                }
                Ok(None) => {
                    self.record_read_success(reg).await;
                    continue;
                }
                Err(_) => {
                    self.record_read_failure(reg).await;
                    continue;
                }
            }
        }

        Ok(None)
    }

    /// Collection-shape read variant: returns the first enabled/healthy backend's
    /// result, no cache. `op`'s return type is `Result<T>` (no `Option<T>`):
    /// an error is treated as "backend couldn't serve this" and drifted; `Ok(T)`
    /// is the answer.
    pub(crate) async fn read_walk_collection<T, F, Fut, P>(
        &self,
        predicate: P,
        op: F,
    ) -> Result<Option<T>>
    where
        T: Send,
        P: Fn(&ArchiveRegistration) -> bool + Send + Sync,
        F: Fn(Arc<dyn ArchiveBackend>) -> Fut + Send + Sync,
        Fut: std::future::Future<Output = Result<T>> + Send,
    {
        let regs: Vec<Arc<ArchiveRegistration>> = self.registrations.read().await.clone();
        for reg in regs.iter() {
            if !reg.enabled || !predicate(reg) || self.is_unavailable(reg).await {
                continue;
            }
            match op(reg.backend.clone()).await {
                Ok(value) => {
                    self.record_read_success(reg).await;
                    return Ok(Some(value));
                }
                Err(_) => {
                    self.record_read_failure(reg).await;
                    continue;
                }
            }
        }
        Ok(None)
    }

    pub(crate) async fn find_registration(
        &self,
        name: &str,
    ) -> Option<Arc<ArchiveRegistration>> {
        self.registrations
            .read()
            .await
            .iter()
            .find(|r| r.name == name)
            .cloned()
    }

    async fn is_unavailable(&self, reg: &ArchiveRegistration) -> bool {
        matches!(
            *reg.last_health.read().await,
            crate::backend::HealthStatus::Unavailable { .. }
        )
    }
}
