//! Diagnostic types for error reporting.

#[cfg(feature = "diagnostics")]
use builder_pattern::Builder;
#[cfg(feature = "diagnostics")]
use miette::{Diagnostic as MietteDiagnostic, LabeledSpan, Severity, SourceCode};
#[cfg(feature = "diagnostics")]
use sipha_core::span::Span;
#[cfg(feature = "diagnostics")]
use std::fmt;

/// Severity levels for diagnostics.
///
/// Only available when the `diagnostics` feature is enabled.
#[cfg(feature = "diagnostics")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Level {
    /// Fatal error.
    Error,
    /// Recoverable warning.
    Warning,
    /// Informational note.
    Note,
}

#[cfg(feature = "diagnostics")]
impl fmt::Display for Level {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Level::Error => write!(f, "error"),
            Level::Warning => write!(f, "warning"),
            Level::Note => write!(f, "note"),
        }
    }
}

/// Lightweight diagnostic representation.
///
/// Requires the `diagnostics` feature to be enabled.
/// This struct can be converted to and from `miette::Report` for enhanced error reporting.
#[cfg(feature = "diagnostics")]
#[derive(Builder, Clone, Debug)]
pub struct Diagnostic {
    /// Severity level.
    #[default(Level::Error)]
    pub level: Level,
    /// Human-readable message.
    #[into]
    pub message: String,
    /// Primary spans associated with the diagnostic.
    #[default(Vec::new())]
    pub spans: Vec<Span>,
    /// Additional notes providing context.
    #[default(Vec::new())]
    pub notes: Vec<String>,
    /// Hints providing additional guidance.
    #[default(Vec::new())]
    pub hints: Vec<String>,
    /// Suggestions for fixing the error.
    #[default(Vec::new())]
    pub suggestions: Vec<String>,
}

/// Internal error type that implements miette's Diagnostic trait.
#[cfg(feature = "diagnostics")]
#[derive(Debug)]
struct MietteDiagnosticError {
    diagnostic: Diagnostic,
}

#[cfg(feature = "diagnostics")]
impl std::error::Error for MietteDiagnosticError {}

#[cfg(feature = "diagnostics")]
impl std::fmt::Display for MietteDiagnosticError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.diagnostic.message)
    }
}

#[cfg(feature = "diagnostics")]
impl MietteDiagnostic for MietteDiagnosticError {
    fn code(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
        None
    }

    fn severity(&self) -> Option<Severity> {
        Some(match self.diagnostic.level {
            Level::Error => Severity::Error,
            Level::Warning => Severity::Warning,
            Level::Note => Severity::Advice,
        })
    }

    fn help(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
        let mut help_parts = Vec::new();

        if !self.diagnostic.suggestions.is_empty() {
            help_parts.push(format!(
                "Suggestions:\n{}",
                self.diagnostic
                    .suggestions
                    .iter()
                    .map(|s| format!("  • {}", s))
                    .collect::<Vec<_>>()
                    .join("\n")
            ));
        }

        if !self.diagnostic.hints.is_empty() {
            help_parts.push(format!(
                "Hints:\n{}",
                self.diagnostic
                    .hints
                    .iter()
                    .map(|h| format!("  → {}", h))
                    .collect::<Vec<_>>()
                    .join("\n")
            ));
        }

        if !self.diagnostic.notes.is_empty() {
            help_parts.push(format!(
                "Context:\n{}",
                self.diagnostic
                    .notes
                    .iter()
                    .map(|n| format!("  • {}", n))
                    .collect::<Vec<_>>()
                    .join("\n")
            ));
        }

        if help_parts.is_empty() {
            None
        } else {
            Some(Box::new(help_parts.join("\n\n")))
        }
    }

    fn url(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
        None
    }

    fn source_code(&self) -> Option<&dyn SourceCode> {
        None
    }

    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
        if self.diagnostic.spans.is_empty() {
            return None;
        }

        let labels: Vec<LabeledSpan> = self
            .diagnostic
            .spans
            .iter()
            .enumerate()
            .map(|(i, span)| {
                let label = if self.diagnostic.spans.len() == 1 {
                    None
                } else {
                    Some(format!("Location {}", i + 1))
                };

                LabeledSpan::new_with_span(label, span.start()..span.end())
            })
            .collect();

        Some(Box::new(labels.into_iter()))
    }

    fn related(&self) -> Option<Box<dyn Iterator<Item = &dyn MietteDiagnostic> + '_>> {
        None
    }
}

#[cfg(feature = "diagnostics")]
impl Diagnostic {
    /// Convert this diagnostic to a miette Report.
    pub fn to_miette_report(&self, source: Option<&str>) -> miette::Report {
        use miette::{NamedSource, Report};

        let error = MietteDiagnosticError {
            diagnostic: self.clone(),
        };

        let mut report = Report::new(error);

        if let Some(src) = source {
            let named_source = NamedSource::new("input", src.to_string());
            report = report.with_source_code(named_source);
        }

        report
    }

    /// Format the diagnostic with source code context using miette's fancy formatter.
    ///
    /// This method provides enhanced error reporting with:
    /// - Line and column numbers
    /// - Source code snippets
    /// - Visual indicators for error locations
    pub fn format_with_source(&self, source: &str) -> String {
        let report = self.to_miette_report(Some(source));
        format!("{report:?}")
    }
}

#[cfg(feature = "diagnostics")]
impl fmt::Display for Diagnostic {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let report = self.to_miette_report(None);
        write!(f, "{report:?}")
    }
}

// DiagnosticBuilder is automatically generated by the Builder derive macro on Diagnostic
