//! Archive backend trait definitions.
//!
//! `ArchiveBackend` is mandatory for every backend: session + message
//! primitives plus self-description (capabilities, health). Optional
//! sub-traits (`SearchBackend`, `DagBackend`, `MetaEventsBackend`,
//! `ConnectorRegistryBackend`, `SessionMappingBackend`) are surfaced
//! via `as_xxx() -> Option<&dyn SubTrait>` accessors returning a
//! borrow from `self`.
//!
//! See `docs/plans/2026-04-18-archivist-phase2-design.md` §Trait Definitions.

use async_trait::async_trait;
use uuid::Uuid;

use crate::backend::capability::CapabilitySet;
use crate::backend::health::HealthStatus;
use crate::error::Result;
use crate::types::{
    ConnectorRecord, DagEdge, MessageCursor, MessagePage, MessageRecord,
    MetaEventRecord, SessionListQuery, SessionMapping, SessionMetadata,
    SessionPage,
};

// ---------------------------------------------------------------------------
// Mandatory backend surface
// ---------------------------------------------------------------------------

/// An archive storage backend.
///
/// All backends must implement session metadata and message primitives;
/// optional capabilities are exposed through `as_xxx()` accessors that
/// return `None` when unsupported. `JsonlBackend` implements every
/// sub-trait except `SearchBackend`.
#[async_trait]
pub trait ArchiveBackend: Send + Sync {
    // --- Self-description ---
    fn capabilities(&self) -> &CapabilitySet;
    async fn health_check(&self) -> HealthStatus;

    // --- Session metadata ---
    async fn put_session(&self, meta: SessionMetadata) -> Result<()>;
    async fn get_session(&self, scroll_id: Uuid) -> Result<Option<SessionMetadata>>;
    async fn list_sessions_paged(&self, query: SessionListQuery) -> Result<SessionPage>;
    async fn delete_session(&self, scroll_id: Uuid) -> Result<()>;

    // --- Messages ---
    async fn append_messages(
        &self,
        scroll_id: Uuid,
        messages: Vec<MessageRecord>,
    ) -> Result<()>;
    async fn get_messages_paged(
        &self,
        scroll_id: Uuid,
        cursor: Option<MessageCursor>,
        limit: usize,
    ) -> Result<MessagePage>;
    async fn count_messages(&self, scroll_id: Uuid) -> Result<usize>;
    async fn clear_session_messages(&self, scroll_id: Uuid) -> Result<()>;

    // --- Optional capability accessors ---
    fn as_search(&self) -> Option<&dyn SearchBackend> {
        None
    }
    fn as_dag(&self) -> Option<&dyn DagBackend> {
        None
    }
    fn as_meta_events(&self) -> Option<&dyn MetaEventsBackend> {
        None
    }
    fn as_connector_registry(&self) -> Option<&dyn ConnectorRegistryBackend> {
        None
    }
    fn as_session_mapping(&self) -> Option<&dyn SessionMappingBackend> {
        None
    }
}

// ---------------------------------------------------------------------------
// Optional sub-traits
// ---------------------------------------------------------------------------

/// Content search. Reserved in Phase 2; not wired to `JsonlBackend`.
///
/// `packages/api/src/archivist/search_task.rs` continues to serve content
/// search via ripgrep — this trait exists as a forward-compatible hook for
/// indexed backends (ChromaDB, tantivy, …) arriving in Phase 3+.
#[async_trait]
pub trait SearchBackend: Send + Sync {
    // Deliberately left without methods; Phase 3 adds the concrete
    // query/result shapes when a real indexed backend lands.
}

#[async_trait]
pub trait DagBackend: Send + Sync {
    async fn append_dag_edge(&self, edge: DagEdge) -> Result<()>;
    async fn get_children(&self, parent: Uuid) -> Result<Vec<SessionMetadata>>;
    async fn get_dag_edges(&self, root: Uuid) -> Result<Vec<DagEdge>>;
}

#[async_trait]
pub trait MetaEventsBackend: Send + Sync {
    async fn append_meta_events(
        &self,
        scroll_id: Uuid,
        events: Vec<MetaEventRecord>,
    ) -> Result<()>;
    async fn get_meta_events(&self, scroll_id: Uuid) -> Result<Vec<MetaEventRecord>>;
    async fn update_meta_session_status(
        &self,
        scroll_id: Uuid,
        is_connected: bool,
        current_session_id: Option<Uuid>,
    ) -> Result<()>;
    async fn list_meta_sessions(&self) -> Result<Vec<SessionMetadata>>;
    async fn find_meta_session_by_client(
        &self,
        client_id: &str,
    ) -> Result<Option<SessionMetadata>>;
}

#[async_trait]
pub trait ConnectorRegistryBackend: Send + Sync {
    async fn put_connector(&self, record: ConnectorRecord) -> Result<()>;
    async fn get_connector(&self, connector_uid: Uuid) -> Result<Option<ConnectorRecord>>;
    async fn list_connectors(&self) -> Result<Vec<ConnectorRecord>>;
    async fn resolve_connector_uid(&self, client_native_id: &str) -> Result<Option<Uuid>>;
    async fn update_connector_fingerprint(
        &self,
        connector_uid: Uuid,
        fingerprint: String,
    ) -> Result<()>;
}

#[async_trait]
pub trait SessionMappingBackend: Send + Sync {
    async fn put_mapping(
        &self,
        connector_uid: Uuid,
        native_session_id: &str,
        scroll_id: Uuid,
    ) -> Result<()>;
    async fn get_mapping(
        &self,
        connector_uid: Uuid,
        native_session_id: &str,
    ) -> Result<Option<Uuid>>;
    async fn list_mappings_for_connector(
        &self,
        connector_uid: Uuid,
    ) -> Result<Vec<SessionMapping>>;
    async fn find_owner(&self, native_session_id: &str) -> Result<Option<(Uuid, Uuid)>>;

    /// Replace the entire mapping table for `connector_uid` with `mappings`.
    ///
    /// Phase 2 uses this to remove an individual mapping — callers read the
    /// current table via `list_mappings_for_connector`, filter out the
    /// unwanted row, and call this method with the remainder. Implementations
    /// must also invalidate any in-memory cache entries that reference the
    /// removed rows so subsequent `get_mapping` / `find_owner` calls don't
    /// return stale hits.
    async fn rewrite_connector_mappings(
        &self,
        connector_uid: Uuid,
        mappings: Vec<SessionMapping>,
    ) -> Result<()>;
}
