//! Concrete archivist coordinator.
//!
//! Owns a `Vec<Arc<ArchiveRegistration>>` sorted by `read_priority`, plus a
//! positive `scroll_id → backend` cache. The registry is constructed from
//! `dirigent.toml` at boot (Task 12). `Archivist::new` remains a legacy
//! convenience for the dev-instance migration path; later tasks migrate
//! consumers to `Archivist::from_config`.

mod admin;
mod archives;
mod boot;
mod connectors;
mod meta;
mod routing;
mod sessions;
pub mod types;

pub use types::{ArchiveInfo, ArchiveMetadata};

use std::path::PathBuf;
use std::sync::Arc;

use tokio::sync::RwLock;

use crate::backend::ArchiveBackend;
use crate::error::{ArchivistError, Result};
use crate::registry::{
    cache::ReadCache, ArchiveRegistration, FailureMode, WritePolicy,
};

pub struct Archivist {
    pub(crate) registrations: RwLock<Vec<Arc<ArchiveRegistration>>>,
    #[allow(dead_code)] // wired up in later tasks (cache-backed reads)
    pub(crate) read_cache: Arc<ReadCache>,
    #[allow(dead_code)] // retained for future admin endpoints / diagnostics
    pub(crate) registry_path: PathBuf,
}

impl Archivist {
    /// Legacy constructor: builds a single JsonlBackend rooted at
    /// `registry_path.parent()`. Kept so dev-instance migration still
    /// succeeds before Task 28 migrates consumers to `from_config`.
    pub async fn new(registry_path: PathBuf) -> Result<Self> {
        use crate::backends::JsonlBackend;

        let mut registrations: Vec<Arc<ArchiveRegistration>> = Vec::new();
        if !registry_path.as_os_str().is_empty() {
            let archive_root = registry_path
                .parent()
                .map(|p| p.to_path_buf())
                .unwrap_or_else(|| registry_path.clone());
            let backend = Arc::new(JsonlBackend::new(archive_root).await?)
                as Arc<dyn ArchiveBackend>;
            let initial_health = backend.health_check().await;
            registrations.push(Arc::new(ArchiveRegistration::new(
                "main".into(),
                "jsonl",
                backend,
                /* write_active */ true,
                FailureMode::Required,
                /* read_priority */ 0,
                /* enabled */ true,
                WritePolicy::Inline,
                /* writer */ None,
                initial_health,
            )));
        }

        Ok(Self {
            registrations: RwLock::new(registrations),
            read_cache: Arc::new(ReadCache::new()),
            registry_path,
        })
    }

    /// Construct a coordinator with a single `JsonlBackend` archive named
    /// "main" rooted at `archive_root`.
    pub async fn new_with_single_archive(archive_root: PathBuf) -> Result<Self> {
        use crate::backends::JsonlBackend;

        let backend = Arc::new(JsonlBackend::new(archive_root).await?)
            as Arc<dyn ArchiveBackend>;
        let initial_health = backend.health_check().await;
        let reg = Arc::new(ArchiveRegistration::new(
            "main".into(),
            "jsonl",
            backend,
            true,
            FailureMode::Required,
            0,
            true,
            WritePolicy::Inline,
            None,
            initial_health,
        ));
        Ok(Self {
            registrations: RwLock::new(vec![reg]),
            read_cache: Arc::new(ReadCache::new()),
            registry_path: PathBuf::new(),
        })
    }

    /// Construct a coordinator with a pre-built single backend (for tests
    /// that need to hold the backend directly alongside the coordinator).
    pub async fn from_single_backend(
        name: String,
        backend: Arc<dyn ArchiveBackend>,
    ) -> Result<Self> {
        let initial_health = backend.health_check().await;
        let reg = Arc::new(ArchiveRegistration::new(
            name,
            "external",
            backend,
            true,
            FailureMode::Required,
            0,
            true,
            WritePolicy::Inline,
            None,
            initial_health,
        ));
        Ok(Self {
            registrations: RwLock::new(vec![reg]),
            read_cache: Arc::new(ReadCache::new()),
            registry_path: PathBuf::new(),
        })
    }

    /// Resolve a single backend by optional name.
    ///
    /// `None` → lowest-`read_priority` enabled write-active `Required`
    /// backend. `Some(name)` → the backend with that name (must exist).
    #[allow(dead_code)] // wired up in later tasks
    pub(crate) async fn resolve_backend(
        &self,
        archive: Option<String>,
    ) -> Result<Arc<dyn ArchiveBackend>> {
        let regs = self.registrations.read().await;

        if regs.is_empty() {
            return Err(ArchivistError::NoArchiveConfigured);
        }

        let chosen = match archive {
            Some(name) => match regs.iter().find(|r| r.name == name) {
                Some(r) => r,
                None => return Err(ArchivistError::ArchiveNameUnknown(name)),
            },
            None => regs
                .iter()
                .filter(|r| {
                    r.enabled && r.write_active && r.failure_mode == FailureMode::Required
                })
                .min_by_key(|r| r.read_priority)
                .ok_or_else(|| ArchivistError::PrimaryUnavailable {
                    name: "<default>".into(),
                    reason: "no required write-active backend".into(),
                })?,
        };

        Ok(chosen.backend.clone())
    }

    /// Resolve the primary `ArchiveRegistration` for a write.
    ///
    /// `None` → default-write target (lowest `read_priority` among enabled
    /// write-active `Required` backends). `Some(name)` → the backend with that
    /// name; errors if disabled or not write-active.
    #[allow(dead_code)] // wired up in Task 16
    pub(crate) async fn resolve_primary(
        &self,
        archive: Option<String>,
    ) -> Result<Arc<crate::registry::ArchiveRegistration>> {
        let regs = self.registrations.read().await;
        if regs.is_empty() {
            return Err(ArchivistError::NoArchiveConfigured);
        }
        let chosen = match archive {
            Some(name) => {
                let r = regs
                    .iter()
                    .find(|r| r.name == name)
                    .ok_or_else(|| ArchivistError::ArchiveNameUnknown(name.clone()))?;
                if !r.enabled {
                    return Err(ArchivistError::PrimaryUnavailable {
                        name: r.name.clone(),
                        reason: "backend is disabled".into(),
                    });
                }
                if !r.write_active {
                    return Err(ArchivistError::PrimaryUnavailable {
                        name: r.name.clone(),
                        reason: "backend is not write-active".into(),
                    });
                }
                r.clone()
            }
            None => regs
                .iter()
                .filter(|r| {
                    r.enabled
                        && r.write_active
                        && r.failure_mode == crate::registry::FailureMode::Required
                })
                .min_by_key(|r| r.read_priority)
                .cloned()
                .ok_or_else(|| ArchivistError::PrimaryUnavailable {
                    name: "<default>".into(),
                    reason: "no required write-active backend".into(),
                })?,
        };
        Ok(chosen)
    }
}

#[cfg(any(test, feature = "test-utils"))]
impl Archivist {
    /// Test-only: construct directly from pre-built registrations.
    pub fn from_registrations(
        regs: Vec<std::sync::Arc<crate::registry::ArchiveRegistration>>,
    ) -> Self {
        Self {
            registrations: tokio::sync::RwLock::new(regs),
            read_cache: std::sync::Arc::new(crate::registry::cache::ReadCache::new()),
            registry_path: std::path::PathBuf::new(),
        }
    }
}

#[cfg(test)]
mod tests;
