#![cfg_attr(all(test, unstable_assert_matches), feature(assert_matches))]
#![cfg_attr(unstable_never_type, feature(never_type))]
#![cfg_attr(unstable_proc_macro_diagnostic, feature(proc_macro_diagnostic))]
#![cfg_attr(unstable_iterator_try_collect, feature(iterator_try_collect))]
#![cfg_attr(unstable_try_trait_v2, feature(try_trait_v2))]
#![cfg_attr(unstable_try_trait_v2_residual, feature(try_trait_v2_residual))]
#![doc = include_str!("../README.md")]

use std::fmt::{Debug, Display};

extern crate proc_macro;

use proc_macro::TokenStream as TokenStream1;
use proc_macro2::Span;
#[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
use try_v2_traits::{Extract, Transform};

#[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
use crate::DiagnosticResult_::{Error, Ok as Ok_, Warning};

/// Prelude for easy `*`` imports: `use proc_macro2_diagnostic::prelude::*`
pub mod prelude {
    pub use super::AsDiagnostic;
    pub use super::DiagnosticKind;
    pub use super::DiagnosticResult;
    pub use super::DiagnosticStream;
    pub use super::{Ok, error, error_spanned, warn_spanned};
}

/// A convenience type which is designed to be returned from a proc_macro2-based macro
/// implementation.
///
/// ### Usage
/// 1. Shorten your proc_macro to `my_proc_macro2_impl(input.into()).to_tokens()`
/// 2. Return a DiagnosticStream from `my_proc_macro2_impl(input: proc_macro2::Tokenstream) -> DiagnosticStream`
/// 3. Use `Ok()`, `error` or `warn_spanned` within the function; return a `DiagnosticResult<_>`
///    from any supporting functions and handle it with `?`
///
/// All errors & warnings will be properly emitted by the compiler and nicely formatted.
pub type DiagnosticStream = DiagnosticResult<proc_macro2::TokenStream>;

#[derive(Clone, Debug)]
#[must_use = "this `DiagnosticResult` may be an error or a warning, which should be emitted"]
/// Result-like type which can represent a valid return value, an error or a warning accompanying
/// a valid return value. Warnings will be emitted upon `?`, allowing your code to continue with
/// the valid value. Errors will short-circuit upon `?` and be emitted upon final conversion to a
/// [proc_macro::TokenStream]
///
/// ### Usage
/// 1. Create a DiagnosticResult via `Ok()`, `error` or `warn_spanned`.
/// 2. Treat the DiagnosticResult as you would any other Result type and unpack it with `?` at a
///    suitable point in your code.
///
/// ### Stable / Nightly
/// On stable this will be replaced by a type alias which provides limited ergonomics for `Warning`s.
#[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
pub struct DiagnosticResult<T> {
    inner: DiagnosticResult_<T>,
}

#[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
impl<T> Transform<T> for DiagnosticResult<T> {}
#[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
impl<T> Extract<T> for DiagnosticResult<T> {}

#[cfg(not(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type)))]
/// This is a stable interface which provides limited ergonomics for `Warnings`
///
/// # WARNING
/// **DO NOT** expand this type alias, or your code will not compile on nightly, and will break at
/// some point in the future when try_trait_v2 is stabilised.
pub type DiagnosticResult<T> = Result<T, Diagnostic>;

#[derive(Clone, Debug)]
/// The type of top-level message contained in the DiagnosticResult
pub enum DiagnosticResultKind {
    Ok,
    Warning,
    Error,
}

pub trait DiagnosticKind {
    /// The type of top-level message
    fn kind(&self) -> DiagnosticResultKind;
    fn is_warning(&self) -> bool;
    fn is_error(&self) -> bool;
}

impl<T> DiagnosticKind for DiagnosticResult<T> {
    fn kind(&self) -> DiagnosticResultKind {
        #[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
        match self.inner {
            DiagnosticResult_::Ok(_) => DiagnosticResultKind::Ok,
            DiagnosticResult_::Warning(_, _) => DiagnosticResultKind::Warning,
            DiagnosticResult_::Error(_) => DiagnosticResultKind::Error,
        }
        #[cfg(not(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type)))]
        match self {
            Result::Ok(_) => DiagnosticResultKind::Ok,
            Result::Err(_) => DiagnosticResultKind::Error,
        }
    }

    fn is_warning(&self) -> bool {
        #[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
        {
            matches!(&self.kind(), DiagnosticResultKind::Warning)
        }
        #[cfg(not(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type)))]
        {
            // Warnings cannot be carried alongside values without try_trait_v2
            false
        }
    }

    fn is_error(&self) -> bool {
        matches!(&self.kind(), DiagnosticResultKind::Error)
    }
}

#[derive(Clone, Debug)]
/// Indirection via hidden inner to ensure invariant:
///   - Warning/Error must hold correct kind of Diagnostic
#[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
enum DiagnosticResult_<T> {
    Ok(T),
    Warning(T, Diagnostic),
    Error(Diagnostic),
}

/// Create an `Ok` result.
#[expect(non_snake_case, reason = "same feel as a Result type alias")]
pub fn Ok<T>(val: T) -> DiagnosticResult<T> {
    #[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
    {
        DiagnosticResult { inner: Ok_(val) }
    }
    #[cfg(not(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type)))]
    {
        Result::Ok(val)
    }
}

/// Create an error **spanning the macro call_site**
///
/// The message can be anything that implements `ToString` (incl. everything `Display`),
/// this means you can use format_args!() to avoid intermediate allocations.
pub fn error<T, MSG: ToString>(message: MSG) -> DiagnosticResult<T> {
    let diagnostic = Diagnostic::new(Level::Error, Span::call_site(), message);
    #[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
    {
        DiagnosticResult {
            inner: Error(diagnostic),
        }
    }
    #[cfg(not(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type)))]
    {
        Err(diagnostic)
    }
}

/// Create an error at the given `Span`s.
///
/// The message can be anything that implements `ToString` (incl. everything `Display`),
/// this means you can use format_args!() to avoid intermediate allocations.
///
/// A note will be added to the error when emitted, which highlights the original call site,
/// unless you add one manually.
pub fn error_spanned<T, MSG: ToString, SPN: MultiSpan>(
    span: SPN,
    message: MSG,
) -> DiagnosticResult<T> {
    let diagnostic = Diagnostic::new(Level::Error, span, message);
    #[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
    {
        DiagnosticResult {
            inner: Error(diagnostic),
        }
    }
    #[cfg(not(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type)))]
    {
        Err(diagnostic)
    }
}

/// Create a warning which will emit a message at the given `Span`s and deconstruct
/// to a valid value via `?`.
///
/// The message can be anything that implements `ToString` (incl. everything `Display`),
/// this means you can use format_args!() to avoid intermediate allocations.
///
/// A note will be added to the warning when emitted, which highlights the original call site,
/// unless you add one manually.
///
/// # Stable / Nightly
/// - Where try_trait_v2 is not available `Warnings` will be emitted immediately. See the readme
///   for more information
///
/// # Considerations for testing, etc.
/// - If code used outside a proc_macro context attempts to emit a Warning this will cause a
///   runtime failure. Be careful when constructing Warnings in code which will be subject to
///   unit tests or used in other contexts, e.g. in a build script.
pub fn warn_spanned<T, MSG: ToString, SPN: MultiSpan>(
    value: T,
    #[allow(
        unused_variables,
        reason = "warnings ignored if try trait not available"
    )]
    span: SPN,
    #[allow(
        unused_variables,
        reason = "warnings ignored if try trait not available"
    )]
    message: MSG,
) -> DiagnosticResult<T> {
    let warning = Diagnostic::new(Level::Warning, span, message);
    #[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
    {
        DiagnosticResult {
            inner: Warning(value, warning),
        }
    }
    #[cfg(not(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type)))]
    {
        warning.emit();
        Result::Ok(value)
    }
}

/// Provides methods to generate a diagnostic in "failure" case. This is a bit like
/// `Option::ok_or()` but returns a `DiagnosticResult` and can be implemented on
/// additional types.
pub trait ToDiagnostic<T> {
    /// Wrap the contained `T` in a `DiagnosticResult<T>` or create an error, spanning
    /// the call site with the given `message`
    fn or_error<MSG: ToString>(self, message: MSG) -> DiagnosticResult<T>;
    /// Wrap the contained `T` in a `DiagnosticResult<T>` or create an error at the
    /// given `span` with the given `message`
    fn or_error_spanned<MSG: ToString, SPN: MultiSpan>(
        self,
        span: SPN,
        message: MSG,
    ) -> DiagnosticResult<T>;
    /// Wrap the contained `T` in a `DiagnosticResult<T>` or create a warning, at the
    /// given `span` with the given `message` and containing the default value for `T`
    ///     
    ///
    /// A note will be added to the warning when emitted, which highlights the original call site,
    /// unless you add one manually.
    ///
    /// # Stable / Nightly
    /// - Where try_trait_v2 is not available `Warnings` will be emitted immediately. See the readme
    ///   for more information
    ///
    /// # Considerations for testing, etc.
    /// - If code used outside a proc_macro context attempts to emit a Warning this will cause a
    ///   runtime failure. Be careful when contructing Warnings in code which will be subject to
    ///   unit tests or used in other contexts, e.g. in a build script.
    fn or_warn_spanned_with_default<MSG: ToString, SPN: MultiSpan>(
        self,
        span: SPN,
        message: MSG,
    ) -> DiagnosticResult<T>
    where
        T: Default;
}

impl<T> ToDiagnostic<T> for Option<T> {
    fn or_error<MSG: ToString>(self, message: MSG) -> DiagnosticResult<T> {
        self.or_error_spanned(Span::call_site(), message)
    }

    fn or_error_spanned<MSG: ToString, SPN: MultiSpan>(
        self,
        span: SPN,
        message: MSG,
    ) -> DiagnosticResult<T> {
        match self {
            Some(val) => Ok(val),
            None => error_spanned(span, message),
        }
    }

    fn or_warn_spanned_with_default<MSG: ToString, SPN: MultiSpan>(
        self,
        span: SPN,
        message: MSG,
    ) -> DiagnosticResult<T>
    where
        T: Default,
    {
        match self {
            Some(val) => Ok(val),
            None => warn_spanned(Default::default(), span, message),
        }
    }
}

pub trait AsDiagnostic<T> {
    /// Add a `Help` message to an existing _error_ or _warning_ at one or more `Span`s.
    ///
    /// The message can be anything that implements `ToString` (incl. everything `Display`),
    /// this means you can use format_args!() to avoid intermediate allocations.
    fn add_help<MSG: ToString, SPN: MultiSpan>(
        self,
        span: SPN,
        message: MSG,
    ) -> DiagnosticResult<T>;

    /// Add a `Note` to an existing _error_ or _warning_ at one or more `Span`s.
    ///
    /// The message can be anything that implements `ToString` (incl. everything `Display`),
    /// this means you can use format_args!() to avoid intermediate allocations.
    fn add_note<MSG: ToString, SPN: MultiSpan>(
        self,
        span: SPN,
        message: MSG,
    ) -> DiagnosticResult<T>;

    /// Get the underlying Diagnostic, if one exists.
    fn diagnostic(self) -> Option<Diagnostic>;
}

/// Converts `Err` to `Error`.
impl<T, E> AsDiagnostic<T> for Result<T, E>
where
    Diagnostic: From<E>,
{
    fn add_help<MSG: ToString, SPN: MultiSpan>(
        self,
        span: SPN,
        message: MSG,
    ) -> DiagnosticResult<T> {
        match self {
            Result::Ok(val) => Ok(val),
            Result::Err(e) => {
                let mut diagnostic = Diagnostic::from(e);
                diagnostic.add_help(span, message);
                #[cfg(not(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type)))]
                {
                    Err(diagnostic)
                }
                #[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
                {
                    DiagnosticResult {
                        inner: Error(diagnostic),
                    }
                }
            }
        }
    }

    fn add_note<MSG: ToString, SPN: MultiSpan>(
        self,
        span: SPN,
        message: MSG,
    ) -> DiagnosticResult<T> {
        match self {
            Result::Ok(val) => Ok(val),
            Result::Err(e) => {
                let mut diagnostic = Diagnostic::from(e);
                diagnostic.add_note(span, message);
                #[cfg(not(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type)))]
                {
                    Err(diagnostic)
                }
                #[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
                {
                    DiagnosticResult {
                        inner: Error(diagnostic),
                    }
                }
            }
        }
    }

    fn diagnostic(self) -> Option<Diagnostic> {
        match self {
            Self::Ok(_) => None,
            Self::Err(e) => Some(Diagnostic::from(e)),
        }
    }
}

#[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
impl<T> AsDiagnostic<T> for DiagnosticResult<T> {
    fn add_help<MSG: ToString, SPN: MultiSpan>(
        mut self,
        span: SPN,
        message: MSG,
    ) -> DiagnosticResult<T> {
        match self.inner {
            Ok_(_) => self,
            Warning(_, ref mut diagnostic) | Error(ref mut diagnostic) => {
                diagnostic.add_help(span, message);
                self
            }
        }
    }

    fn add_note<MSG: ToString, SPN: MultiSpan>(
        mut self,
        span: SPN,
        message: MSG,
    ) -> DiagnosticResult<T> {
        match self.inner {
            Ok_(_) => self,
            Warning(_, ref mut diagnostic) | Error(ref mut diagnostic) => {
                diagnostic.add_note(span, message);
                self
            }
        }
    }

    fn diagnostic(self) -> Option<Diagnostic> {
        match self.inner {
            Ok_(_) => None,
            Warning(_, diagnostic) | Error(diagnostic) => Some(diagnostic),
        }
    }
}

#[cfg(all(
    has_try_trait_v2,
    has_try_trait_v2_residual,
    has_never_type,
    has_iterator_try_collect
))]
impl<T, V> FromIterator<DiagnosticResult<T>> for DiagnosticResult<V>
where
    V: FromIterator<T>,
{
    fn from_iter<I: IntoIterator<Item = DiagnosticResult<T>>>(iter: I) -> Self {
        iter.into_iter().try_collect()
    }
}

#[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
// TODO: use traits from try_v2
impl<T> DiagnosticResult<T> {
    pub fn is_ok(&self) -> bool {
        matches!(&self.kind(), DiagnosticResultKind::Ok)
    }

    /// Return the Ok result or panic.
    pub fn unwrap(self) -> T
    where
        T: Debug,
    {
        match self.inner {
            Ok_(t) => t,
            Warning(val, warning) => panic!(
                "Called unwrap on value {:?} with accompanying warning: {:?}",
                val, warning
            ),
            Error(error) => panic!("Called unwrap on an error: {:?}", error),
        }
    }
}

#[derive(Debug, Clone)]
/// The internal Diagnostic stored within DiagnosticResult.
/// Not (currently) designed for direct usage.
///
/// 1:1 structure to match [proc_macro::Diagnostic]
pub struct Diagnostic {
    level: Level,
    message: String,
    spans: Vec<Span>,
    children: Vec<Diagnostic>,
}

#[derive(Debug, Clone, Copy, PartialEq)]
/// 1:1 match for [proc_macro::Level].
pub enum Level {
    Error,
    Warning,
    Note,
    Help,
}

impl Display for Level {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Level::Error => write!(f, "error"),
            Level::Warning => write!(f, "warning"),
            Level::Note => write!(f, "note"),
            Level::Help => write!(f, "help"),
        }
    }
}

impl Diagnostic {
    fn new<MSG: ToString, SPN: MultiSpan>(level: Level, span: SPN, message: MSG) -> Self {
        Self {
            level,
            message: message.to_string(),
            spans: span.into_spans(),
            children: vec![],
        }
    }

    fn push(&mut self, child: Diagnostic) {
        self.children.push(child);
    }

    fn add_child<MSG: ToString, SPN: MultiSpan>(&mut self, level: Level, span: SPN, message: MSG) {
        self.children.push(Diagnostic::new(level, span, message));
    }

    fn add_help<MSG: ToString, SPN: MultiSpan>(&mut self, span: SPN, message: MSG) {
        self.add_child(Level::Help, span, message);
    }

    fn add_note<MSG: ToString, SPN: MultiSpan>(&mut self, span: SPN, message: MSG) {
        self.add_child(Level::Note, span, message);
    }

    /// Does any message _exactly_ span the call_site (not just across it)?
    fn spans_call_site(&self) -> bool {
        let call_site = Span::call_site();
        let cs_file = call_site.local_file();
        let cs_start = call_site.start();
        let cs_end = call_site.end();
        let is_call_site = |span: &Span| {
            span.local_file() == cs_file && span.start() == cs_start && span.end() == cs_end
        };

        self.spans.iter().any(is_call_site) || self.children.iter().any(Diagnostic::spans_call_site)
    }
}

impl From<syn::Error> for Diagnostic {
    fn from(error: syn::Error) -> Self {
        let mut diagnostic = Diagnostic::new(Level::Error, error.span(), error.to_string());
        for err in error.into_iter().skip(1) {
            diagnostic.push(err.into());
        }
        diagnostic
    }
}

/// Functions for the conversion to the proc_macro world.
impl Diagnostic {
    /// Convert to a [`proc_macro::Diagnostic`] (or [`syn::Error`] if
    /// [`proc_macro::Diagnostic`] is not available) and then emit.
    ///
    /// Returns a [proc_macro::TokenStream] which *should be used*. On toolchains without
    /// the experiment Diagnostic API, this will contain a compiler error, on toolchains
    /// with the experimental API it will be empty.
    pub fn emit(mut self) -> TokenStream1 {
        if !self.spans_call_site() {
            self.add_note(
                Span::call_site(),
                format!(
                    "this {} originates from the macro invocation here",
                    self.level
                ),
            );
        };
        #[cfg(has_proc_macro_diagnostic)]
        {
            let spans = self.as_spans();
            let mut pm_diagnostic =
                proc_macro::Diagnostic::spanned(spans, self.level.into(), self.message);
            for child in self.children {
                pm_diagnostic = child.add_to_parent(pm_diagnostic);
            }
            pm_diagnostic.emit();
            TokenStream1::new()
        }
        #[cfg(not(has_proc_macro_diagnostic))]
        {
            self.into_syn_err().into_compile_error().into()
        }
    }

    #[cfg(not(has_proc_macro_diagnostic))]
    fn into_syn_err(self) -> syn::Error {
        // take first span as all functions needed to join are nightly only
        let span = self.spans.into_iter().next().expect("at least one span");
        // new syn err
        let message = if matches!(self.level, Level::Error) {
            self.message
        } else {
            format!("{}: {}", self.level, self.message)
        };
        let mut err = syn::Error::new(span, message);
        for child in self.children {
            err.combine(child.into_syn_err());
        }
        err
    }

    /// Add this [Diagnostic] as the child of a [proc_macro::Diagnostic].
    /// Consumes both and returns a new [proc_macro::Diagnostic].
    #[cfg(has_proc_macro_diagnostic)]
    fn add_to_parent(self, parent: proc_macro::Diagnostic) -> proc_macro::Diagnostic {
        let msg = self.message.clone();
        match self.level {
            Level::Error => parent.span_error(self.as_spans(), msg),
            Level::Warning => parent.span_warning(self.as_spans(), msg),
            Level::Note => parent.span_note(self.as_spans(), msg),
            Level::Help => parent.span_help(self.as_spans(), msg),
        }
    }

    /// Get and convert the spans to use in a new [proc_macro::Diagnostic].
    #[cfg(has_proc_macro_diagnostic)]
    fn as_spans(&self) -> Vec<proc_macro::Span> {
        self.spans.iter().map(|span| span.unwrap()).collect()
    }
}

#[cfg(has_proc_macro_diagnostic)]
impl From<Level> for proc_macro::Level {
    fn from(level: Level) -> Self {
        match level {
            Level::Error => Self::Error,
            Level::Help => Self::Help,
            Level::Note => Self::Note,
            Level::Warning => Self::Warning,
        }
    }
}

/// A helper trait for APIs that accept one or more `Span`s.
///
/// This mirrors the behavior of [proc_macro::diagnostic::MultiSpan] and allows
/// callers to pass a `Span`, `Vec<Span>`, or `&[Span]` to supported APIs.
pub trait MultiSpan {
    /// Consume `self` and convert into an owned `Vec<Span>`.
    fn into_spans(self) -> Vec<Span>;
}

impl MultiSpan for Span {
    fn into_spans(self) -> Vec<Span> {
        vec![self]
    }
}

/// WARNING: Only first Span will be used if not has proc_macro_diagnostic
impl MultiSpan for Vec<Span> {
    fn into_spans(self) -> Vec<Span> {
        self
    }
}

impl MultiSpan for &[Span] {
    fn into_spans(self) -> Vec<Span> {
        self.to_vec()
    }
}

/// Will emit diagnostics in non-fatal cases:
/// - `Ok(val)?` -> `val`
/// - `Warning(val, diag)` -> `val` _and_ emits `diag` immediately
/// - `Err(diag)` -> short-circuits with `Err(diag)` but _does NOT emit_ `diag` as this would lead to
///   repeat emissions
#[cfg(all(has_never_type, has_try_trait_v2, has_never_type))]
impl<T> std::ops::Try for DiagnosticResult<T> {
    type Output = T;

    type Residual = DiagnosticResult<!>;

    fn from_output(output: Self::Output) -> Self {
        Self { inner: Ok_(output) }
    }

    fn branch(self) -> std::ops::ControlFlow<Self::Residual, Self::Output> {
        match self.inner {
            Ok_(t) => std::ops::ControlFlow::Continue(t),
            Warning(t, d) => {
                d.emit();
                std::ops::ControlFlow::Continue(t)
            }
            Error(d) => std::ops::ControlFlow::Break(DiagnosticResult { inner: Error(d) }),
        }
    }
}

#[cfg(all(has_never_type, has_try_trait_v2, has_never_type, has_never_type))]
impl<T> std::ops::FromResidual<DiagnosticResult<!>> for DiagnosticResult<T> {
    fn from_residual(residual: DiagnosticResult<!>) -> Self {
        match residual.inner {
            Error(residual) => Self {
                inner: Error(residual),
            },
        }
    }
}

#[cfg(all(has_never_type, has_try_trait_v2_residual, has_never_type))]
impl<T> std::ops::Residual<T> for DiagnosticResult<!> {
    type TryType = DiagnosticResult<T>;
}

#[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
impl<T, E> std::ops::FromResidual<Result<std::convert::Infallible, E>> for DiagnosticResult<T>
where
    E: Into<Diagnostic>,
{
    fn from_residual(result: Result<std::convert::Infallible, E>) -> Self {
        match result {
            Err(e) => DiagnosticResult {
                inner: DiagnosticResult_::Error(e.into()),
            },
        }
    }
}

#[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
impl<T, E> From<E> for DiagnosticResult<T>
where
    E: Into<Diagnostic> + std::error::Error,
{
    fn from(error: E) -> Self {
        Self {
            inner: DiagnosticResult_::Error(error.into()),
        }
    }
}

pub trait ToTokens {
    /// Convert the underlying [proc_macro2::TokenStream] to a [proc_macro::TokenStream] and/or convert
    /// and emit the contained [Diagnostic] as per [proc_macro::Diagnostic], returning an empty
    /// [proc_macro::TokenStream] in case of [DiagnosticResult::Err].
    fn to_tokens(self) -> TokenStream1;
}

impl ToTokens for DiagnosticStream {
    fn to_tokens(self) -> TokenStream1 {
        #[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))]
        match self.inner {
            Ok_(t) => t.into(),
            Warning(t, warning) => {
                _ = warning.emit();
                t.into()
            }
            Error(error) => error.emit(),
        }
        #[cfg(not(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type)))]
        match self {
            Self::Ok(t) => t.into(),
            Self::Err(error) => error.emit(),
        }
    }
}

#[cfg(test)]
mod tests {
    #[cfg(assert_matches_location = "root")]
    use std::assert_matches;

    #[cfg(assert_matches_location = "module")]
    use std::assert_matches::assert_matches;

    use super::*;

    #[test]
    fn is_ok() {
        assert!(Ok(()).is_ok());
    }

    #[test]
    #[cfg(all(has_try_trait_v2, has_try_trait_v2_residual, has_never_type))] // Cannot test without, warning would be emitted on construction.
    fn is_warning() {
        assert!(warn_spanned((), Span::call_site(), "foo").is_warning());
    }

    #[test]
    fn is_error() {
        assert!(error::<(), &str>("foo").is_error());
    }

    #[test]
    fn kind() {
        match Ok(()).kind() {
            DiagnosticResultKind::Ok => (),
            DiagnosticResultKind::Warning => panic!("not a warning"),
            DiagnosticResultKind::Error => panic!("not an error"),
        }
    }

    #[test]
    #[cfg(has_assert_matches)]
    fn ok_with_help() {
        assert_matches!(
            Ok(()).add_help(Span::call_site(), "help text").kind(),
            DiagnosticResultKind::Ok
        )
    }

    #[test]
    fn ok_or() {
        fn five() -> DiagnosticResult<i32> {
            let five = Some(5).or_error("oops!")?;
            Ok(five)
        }
        assert_eq!(five().unwrap(), 5)
    }

    #[derive(Debug)]
    struct Number(i32);
    impl TryFrom<i32> for Number {
        type Error = Diagnostic;

        fn try_from(value: i32) -> Result<Self, Self::Error> {
            const MAX: i32 = i16::MAX as i32;
            match value {
                ..=MAX => Result::Ok(Number(value)),
                _ => Result::Err(error::<Number, _>("oops").diagnostic().unwrap()),
            }
        }
    }

    #[test]
    fn try_from_fail() {
        let big = i32::MAX;
        let diagnostic = Number::try_from(big).unwrap_err();
        assert_eq!(diagnostic.level, Level::Error);
        assert_eq!(diagnostic.message, "oops");
    }

    #[test]
    fn try_from_pass() {
        let small = 1;
        let n = Number::try_from(small).unwrap();
        assert_eq!(n.0, 1);
    }

    #[test]
    fn map() {
        let input = Some((0, 1));
        let first = input.or_error("map").map(|tuple| tuple.0).unwrap();
        assert_eq!(first, 0);
    }
}
