use rustc_errors::codes::*;
use rustc_hir::limit::Limit;
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_span::{Span, Symbol};

#[derive(Diagnostic)]
#[help(
    "consider increasing the recursion limit by adding a `#![recursion_limit = \"{$suggested_limit}\"]` attribute to your crate (`{$crate_name}`)"
)]
#[diag("queries overflow the depth limit!")]
pub(crate) struct QueryOverflow {
    #[primary_span]
    pub span: Span,
    #[subdiagnostic]
    pub note: QueryOverflowNote,
    pub suggested_limit: Limit,
    pub crate_name: Symbol,
}

#[derive(Subdiagnostic)]
#[note("query depth increased by {$depth} when {$desc}")]
pub(crate) struct QueryOverflowNote {
    pub desc: String,
    pub depth: usize,
}

#[derive(Subdiagnostic)]
#[note("...which requires {$desc}...")]
pub(crate) struct CycleStack {
    #[primary_span]
    pub span: Span,
    pub desc: String,
}

#[derive(Subdiagnostic)]
pub(crate) enum StackCount {
    #[note("...which immediately requires {$stack_bottom} again")]
    Single { stack_bottom: String },
    #[note("...which again requires {$stack_bottom}, completing the cycle")]
    Multiple { stack_bottom: String },
}

#[derive(Subdiagnostic)]
pub(crate) enum Alias {
    #[note("type aliases cannot be recursive")]
    #[help("consider using a struct, enum, or union instead to break the cycle")]
    #[help(
        "see <https://doc.rust-lang.org/reference/types.html#recursive-types> for more information"
    )]
    Ty,
    #[note("trait aliases cannot be recursive")]
    Trait,
}

#[derive(Subdiagnostic)]
#[note("cycle used when {$usage}")]
pub(crate) struct CycleUsage {
    #[primary_span]
    pub span: Span,
    pub usage: String,
}

#[derive(Diagnostic)]
#[diag("cycle detected when {$stack_bottom}", code = E0391)]
pub(crate) struct Cycle {
    #[primary_span]
    pub span: Span,
    pub stack_bottom: String,
    #[subdiagnostic]
    pub cycle_stack: Vec<CycleStack>,
    #[subdiagnostic]
    pub stack_count: StackCount,
    #[subdiagnostic]
    pub alias: Option<Alias>,
    #[subdiagnostic]
    pub cycle_usage: Option<CycleUsage>,
    #[note(
        "see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information"
    )]
    pub note_span: (),
}

#[derive(Subdiagnostic)]
#[note("...when {$stack_bottom}")]
pub(crate) struct NestedCycleBottom {
    pub stack_bottom: String,
}

#[derive(Diagnostic)]
#[diag("internal compiler error: query cycle when printing cycle detected")]
pub(crate) struct NestedCycle {
    #[primary_span]
    pub span: Span,
    #[subdiagnostic]
    pub stack_bottom: NestedCycleBottom,
    #[subdiagnostic]
    pub cycle_stack: Vec<CycleStack>,
    #[subdiagnostic]
    pub stack_count: StackCount,
    #[subdiagnostic]
    pub cycle_usage: Option<CycleUsage>,
    #[note(
        "see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information"
    )]
    pub note_span: (),
}
