//! Memory use cases (ARCHITECTURE §14.5.1).
//!
//! - **CRUD**: [`CreateMemory`], [`UpdateMemory`], [`GetMemory`],
//!   [`ListMemories`], [`DeleteMemory`].
//! - **Navigation** (read-only): [`ReadMemoryIndex`] (structured `MEMORY.md`
//!   rows) and [`ResolveMemoryLinks`] (a note's outgoing `[[slug]]` links).
//!
//! Mutating use cases announce a [`DomainEvent`]; the read use cases emit none.

use std::sync::Arc;

use domain::ports::{EventBus, MemoryQuery, MemoryRecall, MemoryStore};
use domain::{
    DomainEvent, MarkdownDoc, Memory, MemoryFrontmatter, MemoryIndexEntry, MemoryLink, MemorySlug,
    MemoryType, ProjectPath,
};

use crate::error::AppError;

// ---------------------------------------------------------------------------
// CreateMemory
// ---------------------------------------------------------------------------

/// Input for [`CreateMemory::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateMemoryInput {
    /// Active project root (the note is stored under its `.ideai/memory/`).
    pub project_root: ProjectPath,
    /// Raw slug for the new note (validated into a [`MemorySlug`]).
    pub name: String,
    /// Human-readable one-line description (the index hook). Non-empty.
    pub description: String,
    /// The note's kind.
    pub r#type: MemoryType,
    /// Markdown body of the note.
    pub content: String,
}

/// Output of [`CreateMemory::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateMemoryOutput {
    /// The created note.
    pub memory: Memory,
}

/// Creates a memory note in the project's store and upserts the index.
///
/// Announces [`DomainEvent::MemorySaved`] on success.
pub struct CreateMemory {
    memories: Arc<dyn MemoryStore>,
    events: Arc<dyn EventBus>,
}

impl CreateMemory {
    /// Builds the use case from its ports.
    #[must_use]
    pub fn new(memories: Arc<dyn MemoryStore>, events: Arc<dyn EventBus>) -> Self {
        Self { memories, events }
    }

    /// Executes creation.
    ///
    /// # Errors
    /// - [`AppError::Invalid`] if `name` is not a valid kebab-case slug or the
    ///   note's invariants are violated (empty description/body),
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(&self, input: CreateMemoryInput) -> Result<CreateMemoryOutput, AppError> {
        let slug = MemorySlug::new(input.name).map_err(|e| AppError::Invalid(e.to_string()))?;
        let frontmatter = MemoryFrontmatter {
            name: slug.clone(),
            description: input.description,
            r#type: input.r#type,
        };
        let memory = Memory::new(frontmatter, MarkdownDoc::new(input.content))
            .map_err(|e| AppError::Invalid(e.to_string()))?;
        self.memories.save(&input.project_root, &memory).await?;
        self.events.publish(DomainEvent::MemorySaved { slug });
        Ok(CreateMemoryOutput { memory })
    }
}

// ---------------------------------------------------------------------------
// UpdateMemory
// ---------------------------------------------------------------------------

/// Input for [`UpdateMemory::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdateMemoryInput {
    /// Active project root.
    pub project_root: ProjectPath,
    /// Slug of the note to replace.
    pub slug: MemorySlug,
    /// New description (the index hook). Non-empty.
    pub description: String,
    /// New kind.
    pub r#type: MemoryType,
    /// New Markdown body.
    pub content: String,
}

/// Output of [`UpdateMemory::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdateMemoryOutput {
    /// The updated note.
    pub memory: Memory,
}

/// Replaces a memory note's content (re-validating its invariants).
///
/// Announces [`DomainEvent::MemorySaved`] on success. The input carries the full
/// note, so no prior `get` is needed — this is replace semantics.
pub struct UpdateMemory {
    memories: Arc<dyn MemoryStore>,
    events: Arc<dyn EventBus>,
}

impl UpdateMemory {
    /// Builds the use case from its ports.
    #[must_use]
    pub fn new(memories: Arc<dyn MemoryStore>, events: Arc<dyn EventBus>) -> Self {
        Self { memories, events }
    }

    /// Executes the update.
    ///
    /// # Errors
    /// - [`AppError::Invalid`] if the note's invariants are violated (empty
    ///   description/body),
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(&self, input: UpdateMemoryInput) -> Result<UpdateMemoryOutput, AppError> {
        let frontmatter = MemoryFrontmatter {
            name: input.slug.clone(),
            description: input.description,
            r#type: input.r#type,
        };
        let memory = Memory::new(frontmatter, MarkdownDoc::new(input.content))
            .map_err(|e| AppError::Invalid(e.to_string()))?;
        self.memories.save(&input.project_root, &memory).await?;
        self.events
            .publish(DomainEvent::MemorySaved { slug: input.slug });
        Ok(UpdateMemoryOutput { memory })
    }
}

// ---------------------------------------------------------------------------
// ListMemories
// ---------------------------------------------------------------------------

/// Input for [`ListMemories::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListMemoriesInput {
    /// Active project root.
    pub project_root: ProjectPath,
}

/// Output of [`ListMemories::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListMemoriesOutput {
    /// All notes in the project.
    pub memories: Vec<Memory>,
}

/// Lists the memory notes of a project.
pub struct ListMemories {
    memories: Arc<dyn MemoryStore>,
}

impl ListMemories {
    /// Builds the use case.
    #[must_use]
    pub fn new(memories: Arc<dyn MemoryStore>) -> Self {
        Self { memories }
    }

    /// Lists the project's notes.
    ///
    /// # Errors
    /// - [`AppError::Invalid`] if a note's frontmatter is malformed,
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(&self, input: ListMemoriesInput) -> Result<ListMemoriesOutput, AppError> {
        Ok(ListMemoriesOutput {
            memories: self.memories.list(&input.project_root).await?,
        })
    }
}

// ---------------------------------------------------------------------------
// GetMemory
// ---------------------------------------------------------------------------

/// Input for [`GetMemory::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetMemoryInput {
    /// Active project root.
    pub project_root: ProjectPath,
    /// Slug of the note to read.
    pub slug: MemorySlug,
}

/// Output of [`GetMemory::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetMemoryOutput {
    /// The requested note.
    pub memory: Memory,
}

/// Reads one memory note by slug.
pub struct GetMemory {
    memories: Arc<dyn MemoryStore>,
}

impl GetMemory {
    /// Builds the use case.
    #[must_use]
    pub fn new(memories: Arc<dyn MemoryStore>) -> Self {
        Self { memories }
    }

    /// Reads the note.
    ///
    /// # Errors
    /// - [`AppError::NotFound`] if no note carries that slug,
    /// - [`AppError::Invalid`] if the note's frontmatter is malformed,
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(&self, input: GetMemoryInput) -> Result<GetMemoryOutput, AppError> {
        Ok(GetMemoryOutput {
            memory: self.memories.get(&input.project_root, &input.slug).await?,
        })
    }
}

// ---------------------------------------------------------------------------
// DeleteMemory
// ---------------------------------------------------------------------------

/// Input for [`DeleteMemory::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeleteMemoryInput {
    /// Active project root.
    pub project_root: ProjectPath,
    /// Slug of the note to delete.
    pub slug: MemorySlug,
}

/// Deletes a memory note (and removes its index row).
///
/// Announces [`DomainEvent::MemoryDeleted`] on success.
pub struct DeleteMemory {
    memories: Arc<dyn MemoryStore>,
    events: Arc<dyn EventBus>,
}

impl DeleteMemory {
    /// Builds the use case from its ports.
    #[must_use]
    pub fn new(memories: Arc<dyn MemoryStore>, events: Arc<dyn EventBus>) -> Self {
        Self { memories, events }
    }

    /// Deletes the note.
    ///
    /// # Errors
    /// - [`AppError::NotFound`] if no note carries that slug,
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(&self, input: DeleteMemoryInput) -> Result<(), AppError> {
        self.memories
            .delete(&input.project_root, &input.slug)
            .await?;
        self.events
            .publish(DomainEvent::MemoryDeleted { slug: input.slug });
        Ok(())
    }
}

// ---------------------------------------------------------------------------
// ReadMemoryIndex
// ---------------------------------------------------------------------------

/// Input for [`ReadMemoryIndex::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadMemoryIndexInput {
    /// Active project root.
    pub project_root: ProjectPath,
}

/// Output of [`ReadMemoryIndex::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadMemoryIndexOutput {
    /// One row per note in the aggregated `MEMORY.md` index.
    pub entries: Vec<MemoryIndexEntry>,
}

/// Reads the structured `MEMORY.md` index (drives the graphical memory view).
pub struct ReadMemoryIndex {
    memories: Arc<dyn MemoryStore>,
}

impl ReadMemoryIndex {
    /// Builds the use case.
    #[must_use]
    pub fn new(memories: Arc<dyn MemoryStore>) -> Self {
        Self { memories }
    }

    /// Reads the index rows.
    ///
    /// # Errors
    /// - [`AppError::Store`] on an I/O failure.
    pub async fn execute(
        &self,
        input: ReadMemoryIndexInput,
    ) -> Result<ReadMemoryIndexOutput, AppError> {
        Ok(ReadMemoryIndexOutput {
            entries: self.memories.read_index(&input.project_root).await?,
        })
    }
}

// ---------------------------------------------------------------------------
// RecallMemory
// ---------------------------------------------------------------------------

/// Input for [`RecallMemory::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RecallMemoryInput {
    /// Active project root.
    pub project_root: ProjectPath,
    /// The recall query (often the agent's current working context).
    pub text: String,
    /// Approximate token budget bounding the returned entries (`0` ⇒ empty).
    pub token_budget: usize,
}

/// Output of [`RecallMemory::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RecallMemoryOutput {
    /// The recalled index entries, in relevance order, fitting the budget.
    pub entries: Vec<MemoryIndexEntry>,
}

/// Recalls the most relevant memory entries for a query within a token budget
/// (LOT B, étage 1). Read-only — emits no event.
pub struct RecallMemory {
    recall: Arc<dyn MemoryRecall>,
}

impl RecallMemory {
    /// Builds the use case from the [`MemoryRecall`] port.
    #[must_use]
    pub fn new(recall: Arc<dyn MemoryRecall>) -> Self {
        Self { recall }
    }

    /// Executes recall. Best-effort: an empty or absent memory yields an empty
    /// list, and a budget of `0` yields an empty list.
    ///
    /// # Errors
    /// - [`AppError::Store`] on an unexpected I/O failure of the underlying store.
    pub async fn execute(&self, input: RecallMemoryInput) -> Result<RecallMemoryOutput, AppError> {
        let query = MemoryQuery {
            text: input.text,
            token_budget: input.token_budget,
        };
        Ok(RecallMemoryOutput {
            entries: self.recall.recall(&input.project_root, &query).await?,
        })
    }
}

// ---------------------------------------------------------------------------
// ResolveMemoryLinks
// ---------------------------------------------------------------------------

/// Input for [`ResolveMemoryLinks::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolveMemoryLinksInput {
    /// Active project root.
    pub project_root: ProjectPath,
    /// Slug of the source note whose outgoing links are resolved.
    pub slug: MemorySlug,
}

/// Output of [`ResolveMemoryLinks::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolveMemoryLinksOutput {
    /// The note's resolved outgoing `[[slug]]` links (broken links dropped).
    pub links: Vec<MemoryLink>,
}

/// Resolves a note's outgoing `[[slug]]` links (drives link navigation).
pub struct ResolveMemoryLinks {
    memories: Arc<dyn MemoryStore>,
}

impl ResolveMemoryLinks {
    /// Builds the use case.
    #[must_use]
    pub fn new(memories: Arc<dyn MemoryStore>) -> Self {
        Self { memories }
    }

    /// Resolves the outgoing links.
    ///
    /// # Errors
    /// - [`AppError::NotFound`] if the source note does not exist,
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(
        &self,
        input: ResolveMemoryLinksInput,
    ) -> Result<ResolveMemoryLinksOutput, AppError> {
        Ok(ResolveMemoryLinksOutput {
            links: self
                .memories
                .resolve_links(&input.project_root, &input.slug)
                .await?,
        })
    }
}
