/// Error handling following M-ERRORS-CANONICAL-STRUCTS pattern
/// Each detector has specific errors with context and backtrace
use std::error::Error;
use std::fmt;
use std::path::PathBuf;

/// Detection errors - specific struct, not generic enum
#[derive(Debug)]
pub struct DetectionError {
    kind: DetectionErrorKind,
    context: String,
    source: Option<Box<dyn Error + Send + Sync>>,
}

#[derive(Debug)]
pub enum DetectionErrorKind {
    FileNotFound { path: PathBuf },
    InvalidJson { path: PathBuf, reason: String },
    InvalidToml { path: PathBuf, reason: String },
    ReadError { path: PathBuf },
    IoError,
    InvalidVersion { version: String },
    RegexError { pattern: String },
}

impl DetectionError {
    pub fn file_not_found(path: impl Into<PathBuf>) -> Self {
        Self {
            kind: DetectionErrorKind::FileNotFound { path: path.into() },
            context: "Failed to find required file".into(),
            source: None,
        }
    }

    pub fn invalid_json(path: impl Into<PathBuf>, reason: impl Into<String>) -> Self {
        Self {
            kind: DetectionErrorKind::InvalidJson {
                path: path.into(),
                reason: reason.into(),
            },
            context: "Invalid JSON in configuration file".into(),
            source: None,
        }
    }

    pub fn invalid_toml(path: impl Into<PathBuf>, reason: impl Into<String>) -> Self {
        Self {
            kind: DetectionErrorKind::InvalidToml {
                path: path.into(),
                reason: reason.into(),
            },
            context: "Invalid TOML in configuration file".into(),
            source: None,
        }
    }

    pub fn with_source(mut self, source: Box<dyn Error + Send + Sync>) -> Self {
        self.source = Some(source);
        self
    }

    pub fn with_context(mut self, context: impl Into<String>) -> Self {
        self.context = context.into();
        self
    }

    pub fn is_file_not_found(&self) -> bool {
        matches!(self.kind, DetectionErrorKind::FileNotFound { .. })
    }

    pub fn is_invalid_json(&self) -> bool {
        matches!(self.kind, DetectionErrorKind::InvalidJson { .. })
    }

    pub fn context(&self) -> &str {
        &self.context
    }
}

impl fmt::Display for DetectionError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.kind {
            DetectionErrorKind::FileNotFound { path } => {
                write!(f, "File not found: {}", path.display())
            }
            DetectionErrorKind::InvalidJson { path, reason } => {
                write!(f, "Invalid JSON in {}: {}", path.display(), reason)
            }
            DetectionErrorKind::InvalidToml { path, reason } => {
                write!(f, "Invalid TOML in {}: {}", path.display(), reason)
            }
            DetectionErrorKind::ReadError { path } => {
                write!(f, "Failed to read file: {}", path.display())
            }
            DetectionErrorKind::IoError => {
                write!(f, "I/O error during detection")
            }
            DetectionErrorKind::InvalidVersion { version } => {
                write!(f, "Invalid version format: {}", version)
            }
            DetectionErrorKind::RegexError { pattern } => {
                write!(f, "Invalid regex pattern: {}", pattern)
            }
        }
    }
}

impl Error for DetectionError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.source.as_ref().map(|e| e.as_ref() as &dyn Error)
    }
}

/// InferenceError - for composition rules and requirement analysis
#[derive(Debug)]
pub struct InferenceError {
    kind: InferenceErrorKind,
    context: String,
    source: Option<Box<dyn Error + Send + Sync>>,
}

#[derive(Debug)]
pub enum InferenceErrorKind {
    InvalidRule { name: String },
    ConflictingRequirements { taskservs: Vec<String> },
    MissingDependency { taskserv: String },
}

impl InferenceError {
    pub fn conflicting_requirements(taskservs: Vec<String>) -> Self {
        Self {
            kind: InferenceErrorKind::ConflictingRequirements {
                taskservs: taskservs.clone(),
            },
            context: format!(
                "Taskservs {} have conflicting requirements",
                taskservs.join(", ")
            ),
            source: None,
        }
    }

    pub fn is_conflict(&self) -> bool {
        matches!(
            self.kind,
            InferenceErrorKind::ConflictingRequirements { .. }
        )
    }
}

impl fmt::Display for InferenceError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.kind {
            InferenceErrorKind::InvalidRule { name } => {
                write!(f, "Invalid inference rule: {}", name)
            }
            InferenceErrorKind::ConflictingRequirements { taskservs } => {
                write!(f, "Conflicting requirements: {}", taskservs.join(", "))
            }
            InferenceErrorKind::MissingDependency { taskserv } => {
                write!(f, "Missing dependency for {}", taskserv)
            }
        }
    }
}

impl Error for InferenceError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.source.as_ref().map(|e| e.as_ref() as &dyn Error)
    }
}

/// Type alias for Results with DetectionError
pub type DetectionResult<T> = Result<T, DetectionError>;

/// Type alias for Results with InferenceError
pub type InferenceResult<T> = Result<T, InferenceError>;
