//! Memory-scoped tools: Read / Write / Edit / Search.
//!
//! All four take `kind` + `slug` (Summary takes only `kind`) and
//! resolve the path through [`WorkspaceLayout`]. The agent never has
//! to know the on-disk layout — Search returns `{slug, kind, ...}` and
//! that pair feeds straight into Read / Edit.

mod delete;
mod edit;
mod query;
mod read;
mod write;

pub use delete::delete_tool;

use std::path::PathBuf;

use llm_engine::tool::ToolError;
use serde::Deserialize;

use crate::Slug;
use crate::workspace::{RecordKind, WorkspaceLayout};

pub use edit::edit_tool;
pub use query::{QueryConfig, knowledge_query_tool, memory_query_tool};
pub use read::{read_tool, read_tool_with_usage};
pub use write::write_tool;

/// Kinds the memory tools accept as input. `Workflow` is intentionally
/// excluded — workflows are sub-Engine context, not agent-editable.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum MemoryToolKind {
    Summary,
    Decision,
    Request,
    Knowledge,
}

impl std::fmt::Display for MemoryToolKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            Self::Summary => "summary",
            Self::Decision => "decision",
            Self::Request => "request",
            Self::Knowledge => "knowledge",
        })
    }
}

impl MemoryToolKind {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Summary => "summary",
            Self::Decision => "decision",
            Self::Request => "request",
            Self::Knowledge => "knowledge",
        }
    }

    pub fn record_kind(self) -> RecordKind {
        match self {
            Self::Summary => RecordKind::Summary,
            Self::Decision => RecordKind::Decision,
            Self::Request => RecordKind::Request,
            Self::Knowledge => RecordKind::Knowledge,
        }
    }

    /// Resolve `(kind, slug)` to an absolute path under the workspace.
    /// Summary forbids a slug; the per-record kinds require one.
    pub fn resolve_path(
        self,
        layout: &WorkspaceLayout,
        slug: Option<&str>,
    ) -> Result<PathBuf, ToolError> {
        match self {
            Self::Summary => {
                if slug.is_some() {
                    return Err(ToolError::InvalidArgument(
                        "kind=summary does not accept a slug".to_string(),
                    ));
                }
                Ok(layout.summary_path())
            }
            other => {
                let raw = slug.ok_or_else(|| {
                    ToolError::InvalidArgument(format!("kind={} requires `slug`", other.as_str()))
                })?;
                let parsed =
                    Slug::parse(raw).map_err(|e| ToolError::InvalidArgument(e.to_string()))?;
                Ok(match other {
                    Self::Decision => layout.decision_path(&parsed),
                    Self::Request => layout.request_path(&parsed),
                    Self::Knowledge => layout.knowledge_path(&parsed),
                    Self::Summary => unreachable!(),
                })
            }
        }
    }
}
