//! Project-level context stored under `.ideai/CONTEXT.md`.
//!
//! This context is model-agnostic and shared by every agent/profile launch. It is
//! deliberately project-local: deleting `.ideai/` removes the context together
//! with every other IdeA artefact for that project.

use std::sync::Arc;

use domain::ports::{FileSystem, FsError, RemotePath};
use domain::Project;

use crate::error::AppError;

use super::meta::{join_root, IDEAI_DIR};

/// Project-context file name inside `.ideai/`.
pub const PROJECT_CONTEXT_FILE: &str = "CONTEXT.md";

/// Input for [`ReadProjectContext::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadProjectContextInput {
    /// Project whose `.ideai/CONTEXT.md` should be read.
    pub project: Project,
}

/// Output of [`ReadProjectContext::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadProjectContextOutput {
    /// Markdown content. Empty when the file does not exist yet.
    pub content: String,
}

/// Reads `.ideai/CONTEXT.md` tolerantly.
pub struct ReadProjectContext {
    fs: Arc<dyn FileSystem>,
}

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

    /// Executes the read.
    ///
    /// # Errors
    /// Returns [`AppError::FileSystem`] on non-missing filesystem failures, and
    /// [`AppError::Store`] if the file is not valid UTF-8.
    pub async fn execute(
        &self,
        input: ReadProjectContextInput,
    ) -> Result<ReadProjectContextOutput, AppError> {
        match self.fs.read(&project_context_path(&input.project)).await {
            Ok(bytes) => {
                let content = String::from_utf8(bytes)
                    .map_err(|e| AppError::Store(format!("project context is not UTF-8: {e}")))?;
                Ok(ReadProjectContextOutput { content })
            }
            Err(FsError::NotFound(_)) => Ok(ReadProjectContextOutput {
                content: String::new(),
            }),
            Err(e) => Err(AppError::FileSystem(e.to_string())),
        }
    }
}

/// Input for [`UpdateProjectContext::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UpdateProjectContextInput {
    /// Project whose `.ideai/CONTEXT.md` should be overwritten.
    pub project: Project,
    /// New Markdown content.
    pub content: String,
}

/// Overwrites `.ideai/CONTEXT.md`.
pub struct UpdateProjectContext {
    fs: Arc<dyn FileSystem>,
}

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

    /// Executes the update.
    ///
    /// # Errors
    /// Returns [`AppError::FileSystem`] if `.ideai/` cannot be created or the
    /// context file cannot be written.
    pub async fn execute(&self, input: UpdateProjectContextInput) -> Result<(), AppError> {
        self.fs
            .create_dir_all(&RemotePath::new(join_root(&input.project.root, IDEAI_DIR)))
            .await?;
        self.fs
            .write(
                &project_context_path(&input.project),
                input.content.as_bytes(),
            )
            .await?;
        Ok(())
    }
}

/// Absolute path to `<root>/.ideai/CONTEXT.md`.
#[must_use]
pub fn project_context_path(project: &Project) -> RemotePath {
    RemotePath::new(join_root(
        &project.root,
        &format!("{IDEAI_DIR}/{PROJECT_CONTEXT_FILE}"),
    ))
}
