//! Error handling for typedialog - Canonical Error Structs
//!
//! Provides structured error types for all operations following M-ERRORS-CANONICAL-STRUCTS.
//! Each error type is a concrete struct with specific error kind enum.

#![allow(clippy::result_large_err)]

use std::fmt;
use std::io;
use std::path::PathBuf;

// ============================================================================
// CANCELLED ERROR
// ============================================================================

/// User cancelled the operation
#[derive(Debug)]
pub struct CancelledError {
    pub context: String,
}

impl fmt::Display for CancelledError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Operation cancelled{}",
            if self.context.is_empty() {
                String::new()
            } else {
                format!(": {}", self.context)
            }
        )
    }
}

impl std::error::Error for CancelledError {}

// ============================================================================
// FORM PARSE ERROR
// ============================================================================

/// Form parsing failed
#[derive(Debug)]
pub struct FormParseError {
    pub kind: FormParseErrorKind,
    pub message: String,
    pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
}

#[derive(Debug)]
pub enum FormParseErrorKind {
    InvalidToml {
        line: usize,
        column: usize,
    },
    MissingField {
        field: String,
    },
    InvalidFieldType {
        field: String,
        expected: String,
        got: String,
    },
    InvalidOption {
        field: String,
        option: String,
    },
    MissingRepeatingGroupFragment {
        field: String,
    },
}

impl FormParseError {
    pub fn is_missing_field(&self) -> bool {
        matches!(self.kind, FormParseErrorKind::MissingField { .. })
    }

    pub fn missing_field_name(&self) -> Option<&str> {
        if let FormParseErrorKind::MissingField { field } = &self.kind {
            Some(field)
        } else {
            None
        }
    }
}

impl fmt::Display for FormParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.kind {
            FormParseErrorKind::InvalidToml { line, column } => {
                write!(
                    f,
                    "TOML parse error at line {}, column {}: {}",
                    line, column, self.message
                )
            }
            FormParseErrorKind::MissingField { field } => {
                write!(f, "Missing field '{}': {}", field, self.message)
            }
            FormParseErrorKind::InvalidFieldType {
                field,
                expected,
                got,
            } => {
                write!(f, "Field '{}': expected {}, got {}", field, expected, got)
            }
            FormParseErrorKind::InvalidOption { field, option } => {
                write!(f, "Field '{}': invalid option '{}'", field, option)
            }
            FormParseErrorKind::MissingRepeatingGroupFragment { field } => {
                write!(f, "RepeatingGroup '{}': missing fragment template", field)
            }
        }
    }
}

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

// ============================================================================
// I/O ERROR
// ============================================================================

/// I/O operation failed
#[derive(Debug)]
pub struct IoError {
    pub operation: String,
    pub path: Option<PathBuf>,
    pub source: io::Error,
}

impl fmt::Display for IoError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.path {
            Some(p) => write!(
                f,
                "I/O error ({}) on '{}': {}",
                self.operation,
                p.display(),
                self.source
            ),
            None => write!(f, "I/O error ({}): {}", self.operation, self.source),
        }
    }
}

impl std::error::Error for IoError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&self.source)
    }
}

// ============================================================================
// VALIDATION ERROR
// ============================================================================

/// Validation failed
#[derive(Debug)]
pub struct ValidationError {
    pub kind: ValidationErrorKind,
    pub field: String,
    pub value: Option<serde_json::Value>,
    pub message: String,
}

#[derive(Debug)]
pub enum ValidationErrorKind {
    RequiredFieldMissing,
    ContractViolation { contract: String, reason: String },
    TypeMismatch { expected: String, got: String },
    RangeViolation { min: Option<f64>, max: Option<f64> },
    InvalidDate { format: String },
}

impl ValidationError {
    pub fn is_required_field(&self) -> bool {
        matches!(self.kind, ValidationErrorKind::RequiredFieldMissing)
    }

    pub fn is_contract_violation(&self) -> bool {
        matches!(self.kind, ValidationErrorKind::ContractViolation { .. })
    }

    pub fn contract_details(&self) -> Option<(&str, &str)> {
        if let ValidationErrorKind::ContractViolation { contract, reason } = &self.kind {
            Some((contract, reason))
        } else {
            None
        }
    }
}

impl fmt::Display for ValidationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Validation error on field '{}': {}",
            self.field,
            match &self.kind {
                ValidationErrorKind::RequiredFieldMissing => "field is required".to_string(),
                ValidationErrorKind::ContractViolation { contract, reason } =>
                    format!("contract '{}' violated ({})", contract, reason),
                ValidationErrorKind::TypeMismatch { expected, got } =>
                    format!("expected {}, got {}", expected, got),
                ValidationErrorKind::RangeViolation { min, max } => match (min, max) {
                    (Some(min_val), Some(max_val)) =>
                        format!("value must be between {} and {}", min_val, max_val),
                    (Some(min_val), None) => format!("value must be >= {}", min_val),
                    (None, Some(max_val)) => format!("value must be <= {}", max_val),
                    (None, None) => "value out of range".to_string(),
                },
                ValidationErrorKind::InvalidDate { format } =>
                    format!("invalid date format: {}", format),
            }
        )
    }
}

impl std::error::Error for ValidationError {}

// ============================================================================
// I18N ERROR
// ============================================================================

/// Internationalization error
#[derive(Debug)]
pub struct I18nError {
    pub kind: I18nErrorKind,
    pub locale: Option<String>,
    pub message: String,
}

#[derive(Debug)]
pub enum I18nErrorKind {
    LoadFailed { path: PathBuf },
    MessageNotFound { key: String },
    InvalidSyntax { line: usize },
}

impl fmt::Display for I18nError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let locale_str = self
            .locale
            .as_ref()
            .map(|l| format!(" (locale: {})", l))
            .unwrap_or_default();

        match &self.kind {
            I18nErrorKind::LoadFailed { path } => write!(
                f,
                "Failed to load i18n from '{}'{}: {}",
                path.display(),
                locale_str,
                self.message
            ),
            I18nErrorKind::MessageNotFound { key } => {
                write!(f, "Message not found: '{}'{}", key, locale_str)
            }
            I18nErrorKind::InvalidSyntax { line } => write!(
                f,
                "Invalid i18n syntax at line {}{}: {}",
                line, locale_str, self.message
            ),
        }
    }
}

impl std::error::Error for I18nError {}

// ============================================================================
// TEMPLATE ERROR
// ============================================================================

/// Template processing failed
#[derive(Debug)]
pub struct TemplateError {
    pub kind: TemplateErrorKind,
    pub template_name: Option<String>,
    pub message: String,
    pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
}

#[derive(Debug)]
pub enum TemplateErrorKind {
    ParseFailed { line: usize, column: usize },
    RenderFailed { variable: String },
    VariableNotFound { name: String },
}

impl fmt::Display for TemplateError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let template = self
            .template_name
            .as_ref()
            .map(|t| format!(" in '{}'", t))
            .unwrap_or_default();

        match &self.kind {
            TemplateErrorKind::ParseFailed { line, column } => write!(
                f,
                "Template parse error at line {}, column {}{}: {}",
                line, column, template, self.message
            ),
            TemplateErrorKind::RenderFailed { variable } => write!(
                f,
                "Failed to render variable '{}'{}:{}",
                variable, template, self.message
            ),
            TemplateErrorKind::VariableNotFound { name } => {
                write!(f, "Variable '{}' not found{}", name, template)
            }
        }
    }
}

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

// ============================================================================
// CONFIG NOT FOUND ERROR
// ============================================================================

/// Configuration file not found
#[derive(Debug)]
pub struct ConfigNotFoundError {
    pub search_paths: Vec<PathBuf>,
    pub config_name: String,
}

impl fmt::Display for ConfigNotFoundError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Configuration '{}' not found in: {}",
            self.config_name,
            self.search_paths
                .iter()
                .map(|p| p.display().to_string())
                .collect::<Vec<_>>()
                .join(", ")
        )
    }
}

impl std::error::Error for ConfigNotFoundError {}

// ============================================================================
// UNIFIED ERROR WRAPPER (for public API boundaries)
// ============================================================================

/// Unified error type wrapping all specific error types
#[derive(Debug)]
pub enum ErrorWrapper {
    Cancelled(CancelledError),
    FormParse(FormParseError),
    Io(IoError),
    Validation(ValidationError),
    I18n(I18nError),
    Template(TemplateError),
    ConfigNotFound(ConfigNotFoundError),
}

impl fmt::Display for ErrorWrapper {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ErrorWrapper::Cancelled(e) => write!(f, "{}", e),
            ErrorWrapper::FormParse(e) => write!(f, "{}", e),
            ErrorWrapper::Io(e) => write!(f, "{}", e),
            ErrorWrapper::Validation(e) => write!(f, "{}", e),
            ErrorWrapper::I18n(e) => write!(f, "{}", e),
            ErrorWrapper::Template(e) => write!(f, "{}", e),
            ErrorWrapper::ConfigNotFound(e) => write!(f, "{}", e),
        }
    }
}

impl std::error::Error for ErrorWrapper {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            ErrorWrapper::FormParse(e) => e.source(),
            ErrorWrapper::Io(e) => e.source(),
            ErrorWrapper::Template(e) => e.source(),
            _ => None,
        }
    }
}

// ============================================================================
// FROM IMPLEMENTATIONS
// ============================================================================

impl From<io::Error> for ErrorWrapper {
    fn from(err: io::Error) -> Self {
        ErrorWrapper::Io(IoError {
            operation: "unknown".into(),
            path: None,
            source: err,
        })
    }
}

impl From<io::Error> for IoError {
    fn from(err: io::Error) -> Self {
        IoError {
            operation: "unknown".into(),
            path: None,
            source: err,
        }
    }
}

impl From<toml::de::Error> for ErrorWrapper {
    fn from(err: toml::de::Error) -> Self {
        // toml 0.9 doesn't expose line/column in error, use defaults
        ErrorWrapper::FormParse(FormParseError {
            kind: FormParseErrorKind::InvalidToml { line: 0, column: 0 },
            message: err.to_string(),
            source: None,
        })
    }
}

impl From<toml::de::Error> for FormParseError {
    fn from(err: toml::de::Error) -> Self {
        // toml 0.9 doesn't expose line/column in error, use defaults
        FormParseError {
            kind: FormParseErrorKind::InvalidToml { line: 0, column: 0 },
            message: err.to_string(),
            source: None,
        }
    }
}

impl From<serde_json::Error> for ErrorWrapper {
    fn from(err: serde_json::Error) -> Self {
        ErrorWrapper::Validation(ValidationError {
            kind: ValidationErrorKind::TypeMismatch {
                expected: "valid JSON".into(),
                got: format!("JSON error: {}", err),
            },
            field: "json".into(),
            value: None,
            message: err.to_string(),
        })
    }
}

impl From<serde_yaml::Error> for ErrorWrapper {
    fn from(err: serde_yaml::Error) -> Self {
        ErrorWrapper::Validation(ValidationError {
            kind: ValidationErrorKind::TypeMismatch {
                expected: "valid YAML".into(),
                got: format!("YAML error: {}", err),
            },
            field: "yaml".into(),
            value: None,
            message: err.to_string(),
        })
    }
}

impl From<chrono::ParseError> for ErrorWrapper {
    fn from(err: chrono::ParseError) -> Self {
        ErrorWrapper::Validation(ValidationError {
            kind: ValidationErrorKind::InvalidDate {
                format: "see chrono documentation".into(),
            },
            field: "date".into(),
            value: None,
            message: err.to_string(),
        })
    }
}

#[cfg(feature = "cli")]
impl From<inquire::InquireError> for ErrorWrapper {
    fn from(err: inquire::InquireError) -> Self {
        match err {
            inquire::InquireError::OperationCanceled => ErrorWrapper::Cancelled(CancelledError {
                context: String::new(),
            }),
            _ => ErrorWrapper::Validation(ValidationError {
                kind: ValidationErrorKind::TypeMismatch {
                    expected: "valid input".into(),
                    got: "prompt error".into(),
                },
                field: "prompt".into(),
                value: None,
                message: err.to_string(),
            }),
        }
    }
}

// ============================================================================
// PUBLIC RESULT TYPE
// ============================================================================

pub type Result<T> = std::result::Result<T, ErrorWrapper>;

/// Error type alias for convenient use
pub type Error = ErrorWrapper;

// ============================================================================
// ERROR CONTEXT TRAITS (for cleaner error boundaries)
// ============================================================================

/// Trait for converting module-specific errors to ErrorWrapper at boundaries
pub trait IntoTypedialogError {
    fn into_typedialog_error(self) -> ErrorWrapper;
}

/// Trait for operations that can produce typedialog errors with context
pub trait TypedialogErrorContext<T> {
    /// Add context message to any error
    fn context(self, msg: impl Into<String>) -> Result<T>;

    /// Add field-specific context to validation errors
    fn with_field(self, field: impl Into<String>) -> Result<T>;

    /// Add I/O operation context
    fn with_io_context(self, operation: impl Into<String>, path: Option<PathBuf>) -> Result<T>;
}

impl<T, E> TypedialogErrorContext<T> for std::result::Result<T, E>
where
    E: std::error::Error + Send + Sync + 'static,
{
    fn context(self, msg: impl Into<String>) -> Result<T> {
        self.map_err(|e| {
            ErrorWrapper::FormParse(FormParseError {
                kind: FormParseErrorKind::InvalidToml { line: 0, column: 0 },
                message: format!("{}: {}", msg.into(), e),
                source: Some(Box::new(e)),
            })
        })
    }

    fn with_field(self, field: impl Into<String>) -> Result<T> {
        self.map_err(|e| {
            ErrorWrapper::Validation(ValidationError {
                kind: ValidationErrorKind::TypeMismatch {
                    expected: "valid value".into(),
                    got: e.to_string(),
                },
                field: field.into(),
                value: None,
                message: e.to_string(),
            })
        })
    }

    fn with_io_context(self, operation: impl Into<String>, path: Option<PathBuf>) -> Result<T> {
        self.map_err(|e| {
            ErrorWrapper::Io(IoError {
                operation: operation.into(),
                path,
                source: io::Error::other(e.to_string()),
            })
        })
    }
}

// ============================================================================
// HELPER CONSTRUCTORS (for migration compatibility)
// ============================================================================

impl ErrorWrapper {
    pub fn cancelled(context: impl Into<String>) -> Self {
        ErrorWrapper::Cancelled(CancelledError {
            context: context.into(),
        })
    }

    pub fn cancelled_no_context() -> Self {
        ErrorWrapper::Cancelled(CancelledError {
            context: String::new(),
        })
    }

    pub fn form_parse_failed(msg: impl Into<String>) -> Self {
        ErrorWrapper::FormParse(FormParseError {
            kind: FormParseErrorKind::InvalidToml { line: 0, column: 0 },
            message: msg.into(),
            source: None,
        })
    }

    /// Helper for converting io::Error to IoError (supports .map_err(ErrorWrapper::io))
    pub fn io(source: io::Error) -> Self {
        ErrorWrapper::Io(IoError {
            operation: "unknown".into(),
            path: None,
            source,
        })
    }

    pub fn validation_failed(msg: impl Into<String>) -> Self {
        let msg_str = msg.into();
        ErrorWrapper::Validation(ValidationError {
            kind: ValidationErrorKind::TypeMismatch {
                expected: "valid input".into(),
                got: msg_str.clone(),
            },
            field: "input".into(),
            value: None,
            message: msg_str,
        })
    }

    pub fn validation_failed_field(field: impl Into<String>, msg: impl Into<String>) -> Self {
        let msg_str = msg.into();
        ErrorWrapper::Validation(ValidationError {
            kind: ValidationErrorKind::TypeMismatch {
                expected: "valid input".into(),
                got: msg_str.clone(),
            },
            field: field.into(),
            value: None,
            message: msg_str,
        })
    }

    pub fn i18n_failed(msg: impl Into<String>) -> Self {
        ErrorWrapper::I18n(I18nError {
            kind: I18nErrorKind::LoadFailed {
                path: PathBuf::new(),
            },
            locale: None,
            message: msg.into(),
        })
    }

    pub fn i18n_failed_locale(locale: impl Into<String>, msg: impl Into<String>) -> Self {
        ErrorWrapper::I18n(I18nError {
            kind: I18nErrorKind::LoadFailed {
                path: PathBuf::new(),
            },
            locale: Some(locale.into()),
            message: msg.into(),
        })
    }

    pub fn template_failed(msg: impl Into<String>) -> Self {
        ErrorWrapper::Template(TemplateError {
            kind: TemplateErrorKind::ParseFailed { line: 0, column: 0 },
            template_name: None,
            message: msg.into(),
            source: None,
        })
    }

    pub fn template_failed_named(template: impl Into<String>, msg: impl Into<String>) -> Self {
        ErrorWrapper::Template(TemplateError {
            kind: TemplateErrorKind::ParseFailed { line: 0, column: 0 },
            template_name: Some(template.into()),
            message: msg.into(),
            source: None,
        })
    }

    pub fn config_not_found(config_name: impl Into<String>) -> Self {
        ErrorWrapper::ConfigNotFound(ConfigNotFoundError {
            search_paths: Vec::new(),
            config_name: config_name.into(),
        })
    }

    pub fn is_cancelled(&self) -> bool {
        matches!(self, ErrorWrapper::Cancelled(_))
    }

    /// Generic error constructor for migration (treats as form parse error)
    pub fn new(msg: impl Into<String>) -> Self {
        ErrorWrapper::form_parse_failed(msg)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_cancelled_error() {
        let err = CancelledError {
            context: "user click".into(),
        };
        assert_eq!(err.to_string(), "Operation cancelled: user click");
    }

    #[test]
    fn test_form_parse_error() {
        let err = FormParseError {
            kind: FormParseErrorKind::InvalidToml { line: 1, column: 5 },
            message: "unexpected token".into(),
            source: None,
        };
        assert!(err.to_string().contains("TOML parse error"));
    }

    #[test]
    fn test_validation_error_required() {
        let err = ValidationError {
            kind: ValidationErrorKind::RequiredFieldMissing,
            field: "name".into(),
            value: None,
            message: "required".into(),
        };
        assert!(err.is_required_field());
    }

    #[test]
    fn test_error_wrapper_cancelled() {
        let err = ErrorWrapper::cancelled("test");
        assert!(err.is_cancelled());
    }

    #[test]
    fn test_from_io_error() {
        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
        let err: ErrorWrapper = io_err.into();
        assert!(matches!(err, ErrorWrapper::Io(_)));
    }
}
