//! Auto-memory harvest use case (Lot E1).
//!
//! [`HarvestMemoryFromTurn`] turns the text of a finished agent turn into persisted
//! memory notes when it embeds ` ```idea-memory ` directives (parsed by the pure
//! [`domain::memory_harvest`] module). It is **best-effort strict**: it only acts on
//! a `Response` turn, parses **only the current turn's text** (never the full log),
//! and never fails — parse/store errors are collected into the returned
//! [`MemoryHarvestOutcome`] so the orchestrator can drop them without ever turning a
//! successful reply/ticket/handoff into an error.

use std::sync::Arc;

use domain::conversation_log::TurnRole;
use domain::ports::{EventBus, MemoryStore};
use domain::{
    parse_memory_directives, DomainEvent, MarkdownDoc, Memory, MemoryFrontmatter,
    MemoryHarvestDirective, MemorySlug, ProjectPath,
};

/// The outcome of harvesting one turn: a report the caller may log or ignore.
///
/// `found` is the number of `idea-memory` directives the agent emitted; `saved`
/// the slugs successfully persisted; `skipped` how many were dropped (parse or
/// store failures); `errors` the human-readable reasons (parse + store).
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct MemoryHarvestOutcome {
    /// Number of `idea-memory` directives detected in the turn text.
    pub found: usize,
    /// Slugs of the notes that were saved (and announced).
    pub saved: Vec<MemorySlug>,
    /// Number of directives dropped (invalid, over-limit, or store failure).
    pub skipped: usize,
    /// One message per dropped directive (parse + persistence reasons).
    pub errors: Vec<String>,
}

/// Validates and persists the memory directives embedded in a finished turn.
///
/// Talks only to ports ([`MemoryStore`], [`EventBus`]); announces
/// [`DomainEvent::MemorySaved`] per saved note like the manual CRUD use cases.
pub struct HarvestMemoryFromTurn {
    memories: Arc<dyn MemoryStore>,
    events: Arc<dyn EventBus>,
}

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

    /// Harvests `text` (the current turn's content) into `root`'s memory store.
    ///
    /// Best-effort: returns a [`MemoryHarvestOutcome`] and **never** an error. A
    /// non-`Response` turn is a no-op (empty outcome) — prompts and tool activity
    /// never carry harvest directives.
    pub async fn harvest(
        &self,
        root: &ProjectPath,
        role: TurnRole,
        text: &str,
    ) -> MemoryHarvestOutcome {
        if role != TurnRole::Response {
            return MemoryHarvestOutcome::default();
        }
        let report = parse_memory_directives(text);
        let mut outcome = MemoryHarvestOutcome {
            found: report.found,
            // Parse-level skips are already final; store-level skips are added below.
            skipped: report.errors.len(),
            errors: report.errors,
            saved: Vec::new(),
        };
        for directive in report.directives {
            match self.persist(root, directive).await {
                Ok(slug) => outcome.saved.push(slug),
                Err(err) => {
                    outcome.skipped += 1;
                    outcome.errors.push(err);
                }
            }
        }
        outcome
    }

    /// Persists one validated directive and announces it, mapping the (today
    /// title-less) [`Memory`] entity from the directive. Returns the saved slug or
    /// a message on a build/store failure (never panics).
    async fn persist(
        &self,
        root: &ProjectPath,
        directive: MemoryHarvestDirective,
    ) -> Result<MemorySlug, String> {
        let slug = directive.slug.clone();
        // The persisted `Memory` has no title field today; the directive's `title`
        // is intentionally not stored separately (the index derives its title from
        // the slug). See the Lot E1 report's écart note.
        let frontmatter = MemoryFrontmatter {
            name: slug.clone(),
            description: directive.description,
            r#type: directive.r#type,
        };
        let memory = Memory::new(frontmatter, MarkdownDoc::new(directive.body.as_str()))
            .map_err(|e| format!("invalid memory {slug}: {e}"))?;
        self.memories
            .save(root, &memory)
            .await
            .map_err(|e| format!("store failed for {slug}: {e}"))?;
        self.events
            .publish(DomainEvent::MemorySaved { slug: slug.clone() });
        Ok(slug)
    }
}
