//! # `error-enum-core`
//!
//! A core crate for [`error-enum`](https://crates.io/crates/error-enum) providing
//! traits and implementations for error enums with rich diagnostics support.
//!
//! Please refer to [`error-enum`](https://crates.io/crates/error-enum) and
//! [`its documentation`](https://docs.rs/error-enum/) for more details.
#![warn(unused_crate_dependencies)]
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]

pub use alloc::{boxed::Box, format, string::String};
use core::fmt;
pub use indexer::{Indexer, LineIndexer};
pub use span::{SimpleSpan, Span};

extern crate alloc;
#[cfg(feature = "std")]
extern crate std;

mod indexer;
mod span;

#[cfg(feature = "annotate-snippets")]
mod annotate_snippets_impl;
#[cfg(feature = "ariadne")]
mod ariadne_impl;
#[cfg(feature = "codespan-reporting")]
mod codespan_reporting_impl;
#[cfg(feature = "miette")]
mod miette_impl;

/// Enum representing the kind of an error.
#[derive(Clone, Copy, Default)]
pub enum Kind {
    /// Error kind.
    #[default]
    Error,
    /// Warning kind.
    Warn,
}

impl Kind {
    /// Get short representation of the [Kind].
    pub fn short_str(&self) -> &'static str {
        match self {
            Kind::Error => "E",
            Kind::Warn => "W",
        }
    }
}

/// Trait for error types generated by [`error_type!`] macro and [`ErrorType`] derive macro.
///
/// For conversion to other diagnostic types, see [`ErrorTypeExt`].
///
/// [`error_type!`]: https://docs.rs/error-enum-macros/latest/error_enum_macros/macro.error_type.html
/// [`ErrorType`]: https://docs.rs/error-enum-macros/latest/error_enum_macros/derive.ErrorType.html
pub trait ErrorType: core::error::Error {
    /// The span type associated with the error type.
    type Span: Span + Default;
    /// The message type associated with the error type.
    type Message: fmt::Display;

    /// Get the kind of the error.
    fn kind(&self) -> Kind;
    /// Get the number of the error.
    fn number(&self) -> &str;
    /// Get the code of the error.
    ///
    /// Normally the code is a combination of kind short string and number,
    /// like "E0", "W1", etc.
    fn code(&self) -> &str;
    /// Get the primary span of the error.
    fn primary_span(&self) -> Option<Self::Span>;
    /// Get the primary message of the error.
    fn primary_message(&self) -> Self::Message;
    /// Get the primary label of the error.
    fn primary_label(&self) -> Self::Message;

    /// Get the primary diagnostic of the error.
    fn primary(&self) -> (Option<Self::Span>, Self::Message, Self::Message) {
        (
            self.primary_span(),
            self.primary_message(),
            self.primary_label(),
        )
    }

    /// Get additional spans, messages, and labels of the error.
    fn additional(
        &self,
    ) -> Box<dyn Iterator<Item = (Option<Self::Span>, Self::Message, Self::Message)>>;
}

impl<T: ErrorType + ?Sized> ErrorType for &T {
    type Span = T::Span;
    type Message = T::Message;

    #[inline]
    fn kind(&self) -> Kind {
        (*self).kind()
    }
    #[inline]
    fn number(&self) -> &str {
        (*self).number()
    }
    #[inline]
    fn code(&self) -> &str {
        (*self).code()
    }
    #[inline]
    fn primary_span(&self) -> Option<Self::Span> {
        (*self).primary_span()
    }
    #[inline]
    fn primary_message(&self) -> Self::Message {
        (*self).primary_message()
    }
    #[inline]
    fn primary_label(&self) -> Self::Message {
        (*self).primary_label()
    }

    #[inline]
    fn primary(&self) -> (Option<Self::Span>, Self::Message, Self::Message) {
        (*self).primary()
    }

    #[inline]
    fn additional(
        &self,
    ) -> Box<dyn Iterator<Item = (Option<Self::Span>, Self::Message, Self::Message)>> {
        (*self).additional()
    }
}

/// Conversion to other diagnostic types.
///
/// # Targets
///
/// - [annotate-snippets]
/// - [ariadne]
/// - [codespan-reporting]
/// - [miette]
///
/// ## Restrictions
///
/// In [ariadne], [codespan-reporting] and [miette], spans of additional diagnostics are ignored,
/// as these crates do not support additional diagnostics with spans.
///
/// [annotate-snippets]: https://docs.rs/annotate-snippets/0.9.1/annotate_snippets/
/// [ariadne]: https://docs.rs/ariadne/0.6.0/ariadne/
/// [codespan-reporting]: https://docs.rs/codespan-reporting/0.13.1/codespan_reporting/
/// [miette]: https://docs.rs/miette/7.6.0/miette/
pub trait ErrorTypeExt: ErrorType {
    /// Format the error as an [annotate snippet].
    ///
    /// [annotate snippet]: https://docs.rs/annotate-snippets/0.9.1/annotate_snippets/snippet/struct.Snippet.html
    #[cfg(feature = "annotate-snippets")]
    #[cfg_attr(docsrs, doc(cfg(feature = "annotate-snippets")))]
    fn fmt_as_annotate_snippets(&self) -> String {
        annotate_snippets_impl::fmt_as_annotate_snippets(
            self,
            annotate_snippets::display_list::FormatOptions::default(),
        )
    }
    /// Format the error as an [annotate snippet] with [format options].
    ///
    /// [annotate snippet]: https://docs.rs/annotate-snippets/0.9.1/annotate_snippets/snippet/struct.Snippet.html
    /// [format options]: https://docs.rs/annotate-snippets/0.9.1/annotate_snippets/display_list/struct.FormatOptions.html
    #[cfg(feature = "annotate-snippets")]
    #[cfg_attr(docsrs, doc(cfg(feature = "annotate-snippets")))]
    fn fmt_as_annotate_snippets_with_opts(
        &self,
        opts: annotate_snippets::display_list::FormatOptions,
    ) -> String {
        annotate_snippets_impl::fmt_as_annotate_snippets(self, opts)
    }

    /// Format the error as an [Ariadne report].
    ///
    /// [Ariadne report]: https://docs.rs/ariadne/0.6.0/ariadne/struct.Report.html
    #[cfg(feature = "ariadne")]
    #[cfg_attr(docsrs, doc(cfg(feature = "ariadne")))]
    fn fmt_as_ariadne_report(&self) -> Result<String, std::io::Error> {
        ariadne_impl::fmt_as_ariadne_report(
            self,
            ariadne::Config::new().with_index_type(ariadne::IndexType::Byte),
        )
    }
    /// Format the error as an [Ariadne report] with [Ariadne config].
    ///
    /// [Ariadne report]: https://docs.rs/ariadne/0.6.0/ariadne/struct.Report.html
    /// [Ariadne config]: https://docs.rs/ariadne/0.6.0/ariadne/struct.Config.html
    #[cfg(feature = "ariadne")]
    #[cfg_attr(docsrs, doc(cfg(feature = "ariadne")))]
    fn fmt_as_ariadne_report_with(
        &self,
        config: ariadne::Config,
    ) -> Result<String, std::io::Error> {
        ariadne_impl::fmt_as_ariadne_report(self, config)
    }

    /// Format the error as an [Codespan diagnostic].
    ///
    /// [Codespan diagnostic]: https://docs.rs/codespan-reporting/0.13.1/codespan_reporting/diagnostic/struct.Diagnostic.html
    /// [Codespan config]: https://docs.rs/codespan-reporting/0.13.1/codespan_reporting/term/config/struct.Config.html
    #[cfg(feature = "codespan-reporting")]
    #[cfg_attr(docsrs, doc(cfg(feature = "codespan-reporting")))]
    fn as_codespan_diagnostic(
        &self,
    ) -> (
        codespan_reporting::diagnostic::Diagnostic<usize>,
        codespan_reporting_impl::Files<Self>,
    ) {
        codespan_reporting_impl::to_codespan_diagnostic(self)
    }
    /// Format the error as an [Codespan diagnostic] with [Codespan config].
    ///
    /// [Codespan diagnostic]: https://docs.rs/codespan-reporting/0.13.1/codespan_reporting/diagnostic/struct.Diagnostic.html
    /// [Codespan config]: https://docs.rs/codespan-reporting/0.13.1/codespan_reporting/term/config/struct.Config.html
    #[cfg(feature = "codespan-reporting")]
    #[cfg_attr(docsrs, doc(cfg(feature = "codespan-reporting")))]
    fn fmt_as_codespan_diagnostic_with(
        &self,
        config: codespan_reporting::term::Config,
        styles: Option<&codespan_reporting::term::Styles>,
    ) -> Result<String, codespan_reporting::files::Error> {
        codespan_reporting_impl::fmt_as_codespan_diagnostic(self, config, styles)
    }

    /// Convert the error to a [Miette diagnostic].
    ///
    /// [Miette diagnostic]: https://docs.rs/miette/7.6.0/miette/trait.Diagnostic.html
    #[cfg(feature = "miette")]
    #[cfg_attr(docsrs, doc(cfg(feature = "miette")))]
    fn as_miette_diagnostic(&self) -> impl miette::Diagnostic + '_
    where
        Self::Span: Send + Sync,
    {
        miette_impl::Wrapper::new(self)
    }
    /// Format the error as a [Miette diagnostic] with a [Miette handler].
    ///
    /// [Miette diagnostic]: https://docs.rs/miette/7.6.0/miette/trait.Diagnostic.html
    /// [Miette Handler]: https://docs.rs/miette/7.6.0/miette/trait.ReportHandler.html
    #[cfg(feature = "miette")]
    #[cfg_attr(docsrs, doc(cfg(feature = "miette")))]
    fn fmt_as_miette_diagnostic_with(&self, handler: &impl miette::ReportHandler) -> String
    where
        Self: 'static + Sized,
        Self::Span: Send + Sync,
    {
        miette_impl::Wrapper::new(self).fmt_with(handler)
    }
}

impl<T: ErrorType + ?Sized> ErrorTypeExt for T {}
