//! Errors raised by the memory subsystem.

use std::path::PathBuf;

use lint_common::RecordLintError;
use thiserror::Error;

/// Top-level error for memory operations that don't fit the lint flow.
#[derive(Debug, Error)]
pub enum MemoryError {
    #[error("path is not under the memory tree: {}", .0.display())]
    OutsideMemoryTree(PathBuf),
    #[error("path is not absolute: {}", .0.display())]
    RelativePath(PathBuf),
    #[error("io error at {}: {source}", .path.display())]
    Io {
        path: PathBuf,
        #[source]
        source: std::io::Error,
    },
}

impl MemoryError {
    pub fn io(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
        Self::Io {
            path: path.into(),
            source,
        }
    }
}

/// A single Linter violation. Multiple are aggregated in a [`LintReport`].
///
/// `Display` produces a one-line message used directly in the `ToolError`
/// payload returned to the LLM.
#[derive(Debug, Clone, Error, PartialEq, Eq)]
pub enum LintError {
    #[error("path is not a valid memory record location: {}", .0.display())]
    InvalidPath(PathBuf),

    #[error("path is for a different record kind than expected at this location: {}", .0.display())]
    WrongRecordKind(PathBuf),

    #[error(transparent)]
    Record(#[from] RecordLintError),

    #[error("missing required frontmatter field: `{0}`")]
    MissingField(&'static str),

    #[error("invalid value for `{field}`: {message}")]
    InvalidField {
        field: &'static str,
        message: String,
    },

    #[error("Decisions `status` must be one of open|resolved|replaced (got `{0}`)")]
    InvalidStatus(String),

    #[error("body exceeds the size limit for this record kind: {actual} chars > {limit}")]
    BodyTooLong { actual: usize, limit: usize },

    #[error("slug `{0}` already exists; use the edit tool instead of creating a new record")]
    SlugAlreadyExists(String),

    #[error("`{field}` references unknown {kind} slug `{slug}`")]
    UnknownReference {
        field: &'static str,
        kind: &'static str,
        slug: String,
    },

    #[error("`replaced_by` chain forms a cycle: {chain}")]
    ReplacedByCycle { chain: String },

    #[error("`replaced_by` must point to a different slug than the record itself")]
    ReplacedBySelf,
}

/// A single Linter warning (non-blocking).
///
/// Warnings ride along in the `ToolOutput.summary` so the agent can act
/// on them when convenient; they never abort the write.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LintWarning {
    /// Single-source record exceeds the importance/size threshold.
    LowImportanceLargeRecord { chars: usize },
    /// `sources` array has grown past the soft cap.
    SourcesOverflow { count: usize },
    /// Multiple slugs in the same kind are within Levenshtein distance 2.
    SimilarSlugs(Vec<String>),
}

impl std::fmt::Display for LintWarning {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::LowImportanceLargeRecord { chars } => write!(
                f,
                "record is large ({chars} chars) but only has 1 source — consider splitting or trimming"
            ),
            Self::SourcesOverflow { count } => write!(
                f,
                "`sources` has {count} entries — consider keeping only the most recent and relying on git log for the rest"
            ),
            Self::SimilarSlugs(slugs) => {
                write!(f, "similar slugs detected (consider merging): ")?;
                for (i, s) in slugs.iter().enumerate() {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{s}")?;
                }
                Ok(())
            }
        }
    }
}
