//! This module lists attribute targets, with conversions from other types.

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

use rustc_ast::visit::AssocCtxt;
use rustc_ast::{AssocItemKind, ForeignItemKind, ast};
use rustc_macros::StableHash;

use crate::def::DefKind;
use crate::{Item, ItemKind, TraitItem, TraitItemKind, hir};

#[derive(Copy, Clone, PartialEq, Debug, Eq, StableHash)]
pub enum GenericParamKind {
    Type,
    Lifetime,
    Const,
}

#[derive(Copy, Clone, PartialEq, Debug, Eq, StableHash)]
pub enum MethodKind {
    /// Method in a `trait Trait` block
    Trait {
        /// Whether a default is provided for this method
        body: bool,
    },
    /// Method in a `impl Trait for Type` block
    TraitImpl,
    /// Method in a `impl Type` block
    Inherent,
}

#[derive(Copy, Clone, PartialEq, Debug, Eq, StableHash)]
pub enum Target {
    ExternCrate,
    Use,
    Static,
    Const,
    Fn,
    Closure,
    Mod,
    ForeignMod,
    GlobalAsm,
    TyAlias,
    Enum,
    Variant,
    Struct,
    Field,
    Union,
    Trait,
    TraitAlias,
    Impl { of_trait: bool },
    Expression,
    Statement,
    Arm,
    AssocConst,
    Method(MethodKind),
    AssocTy,
    ForeignFn,
    ForeignStatic,
    ForeignTy,
    GenericParam { kind: GenericParamKind, has_default: bool },
    MacroDef,
    Param,
    PatField,
    ExprField,
    WherePredicate,
    MacroCall,
    Crate,
    Delegation { mac: bool },
}

impl Display for Target {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", Self::name(*self))
    }
}

rustc_error_messages::into_diag_arg_using_display!(Target);

impl Target {
    pub fn is_associated_item(self) -> bool {
        match self {
            Target::AssocConst | Target::AssocTy | Target::Method(_) => true,
            Target::ExternCrate
            | Target::Use
            | Target::Static
            | Target::Const
            | Target::Fn
            | Target::Closure
            | Target::Mod
            | Target::ForeignMod
            | Target::GlobalAsm
            | Target::TyAlias
            | Target::Enum
            | Target::Variant
            | Target::Struct
            | Target::Field
            | Target::Union
            | Target::Trait
            | Target::TraitAlias
            | Target::Impl { .. }
            | Target::Expression
            | Target::Statement
            | Target::Arm
            | Target::ForeignFn
            | Target::ForeignStatic
            | Target::ForeignTy
            | Target::GenericParam { .. }
            | Target::MacroDef
            | Target::Param
            | Target::PatField
            | Target::ExprField
            | Target::MacroCall
            | Target::Crate
            | Target::WherePredicate
            | Target::Delegation { .. } => false,
        }
    }

    pub fn from_item(item: &Item<'_>) -> Target {
        match item.kind {
            ItemKind::ExternCrate(..) => Target::ExternCrate,
            ItemKind::Use(..) => Target::Use,
            ItemKind::Static { .. } => Target::Static,
            ItemKind::Const(..) => Target::Const,
            ItemKind::Fn { .. } => Target::Fn,
            ItemKind::Macro(..) => Target::MacroDef,
            ItemKind::Mod(..) => Target::Mod,
            ItemKind::ForeignMod { .. } => Target::ForeignMod,
            ItemKind::GlobalAsm { .. } => Target::GlobalAsm,
            ItemKind::TyAlias(..) => Target::TyAlias,
            ItemKind::Enum(..) => Target::Enum,
            ItemKind::Struct(..) => Target::Struct,
            ItemKind::Union(..) => Target::Union,
            ItemKind::Trait { .. } => Target::Trait,
            ItemKind::TraitAlias(..) => Target::TraitAlias,
            ItemKind::Impl(imp_) => Target::Impl { of_trait: imp_.of_trait.is_some() },
        }
    }

    // FIXME: For now, should only be used with def_kinds from ItemIds
    pub fn from_def_kind(def_kind: DefKind) -> Target {
        match def_kind {
            DefKind::ExternCrate => Target::ExternCrate,
            DefKind::Use => Target::Use,
            DefKind::Static { .. } => Target::Static,
            DefKind::Const { .. } => Target::Const,
            DefKind::Fn => Target::Fn,
            DefKind::Macro(..) => Target::MacroDef,
            DefKind::Mod => Target::Mod,
            DefKind::ForeignMod => Target::ForeignMod,
            DefKind::GlobalAsm => Target::GlobalAsm,
            DefKind::TyAlias => Target::TyAlias,
            DefKind::Enum => Target::Enum,
            DefKind::Struct => Target::Struct,
            DefKind::Union => Target::Union,
            DefKind::Trait => Target::Trait,
            DefKind::TraitAlias => Target::TraitAlias,
            DefKind::Impl { of_trait } => Target::Impl { of_trait },
            _ => panic!("impossible case reached"),
        }
    }

    pub fn from_ast_item(item: &ast::Item) -> Target {
        match item.kind {
            ast::ItemKind::ExternCrate(..) => Target::ExternCrate,
            ast::ItemKind::Use(..) => Target::Use,
            ast::ItemKind::Static { .. } => Target::Static,
            ast::ItemKind::Const(..) => Target::Const,
            ast::ItemKind::ConstBlock(..) => Target::Const,
            ast::ItemKind::Fn { .. } => Target::Fn,
            ast::ItemKind::Mod(..) => Target::Mod,
            ast::ItemKind::ForeignMod { .. } => Target::ForeignMod,
            ast::ItemKind::GlobalAsm { .. } => Target::GlobalAsm,
            ast::ItemKind::TyAlias(..) => Target::TyAlias,
            ast::ItemKind::Enum(..) => Target::Enum,
            ast::ItemKind::Struct(..) => Target::Struct,
            ast::ItemKind::Union(..) => Target::Union,
            ast::ItemKind::Trait(..) => Target::Trait,
            ast::ItemKind::TraitAlias(..) => Target::TraitAlias,
            ast::ItemKind::Impl(ref i) => Target::Impl { of_trait: i.of_trait.is_some() },
            ast::ItemKind::MacCall(..) => Target::MacroCall,
            ast::ItemKind::MacroDef(..) => Target::MacroDef,
            ast::ItemKind::Delegation(..) => Target::Delegation { mac: false },
            ast::ItemKind::DelegationMac(..) => Target::Delegation { mac: true },
        }
    }

    pub fn from_foreign_item_kind(kind: &ast::ForeignItemKind) -> Target {
        match kind {
            ForeignItemKind::Static(_) => Target::ForeignStatic,
            ForeignItemKind::Fn(_) => Target::ForeignFn,
            ForeignItemKind::TyAlias(_) => Target::ForeignTy,
            ForeignItemKind::MacCall(_) => Target::MacroCall,
        }
    }

    pub fn from_trait_item(trait_item: &TraitItem<'_>) -> Target {
        match trait_item.kind {
            TraitItemKind::Const(..) => Target::AssocConst,
            TraitItemKind::Fn(_, hir::TraitFn::Required(_)) => {
                Target::Method(MethodKind::Trait { body: false })
            }
            TraitItemKind::Fn(_, hir::TraitFn::Provided(_)) => {
                Target::Method(MethodKind::Trait { body: true })
            }
            TraitItemKind::Type(..) => Target::AssocTy,
        }
    }

    pub fn from_foreign_item(foreign_item: &hir::ForeignItem<'_>) -> Target {
        match foreign_item.kind {
            hir::ForeignItemKind::Fn(..) => Target::ForeignFn,
            hir::ForeignItemKind::Static(..) => Target::ForeignStatic,
            hir::ForeignItemKind::Type => Target::ForeignTy,
        }
    }

    pub fn from_generic_param(generic_param: &hir::GenericParam<'_>) -> Target {
        match generic_param.kind {
            hir::GenericParamKind::Type { default, .. } => Target::GenericParam {
                kind: GenericParamKind::Type,
                has_default: default.is_some(),
            },
            hir::GenericParamKind::Lifetime { .. } => {
                Target::GenericParam { kind: GenericParamKind::Lifetime, has_default: false }
            }
            hir::GenericParamKind::Const { default, .. } => Target::GenericParam {
                kind: GenericParamKind::Const,
                has_default: default.is_some(),
            },
        }
    }

    pub fn from_assoc_item_kind(kind: &ast::AssocItemKind, assoc_ctxt: AssocCtxt) -> Target {
        match kind {
            AssocItemKind::Const(_) => Target::AssocConst,
            AssocItemKind::Fn(f) => Target::Method(match assoc_ctxt {
                AssocCtxt::Trait => MethodKind::Trait { body: f.body.is_some() },
                AssocCtxt::Impl { of_trait, .. } => {
                    if of_trait {
                        MethodKind::TraitImpl
                    } else {
                        MethodKind::Inherent
                    }
                }
            }),
            AssocItemKind::Type(_) => Target::AssocTy,
            AssocItemKind::Delegation(_) => Target::Delegation { mac: false },
            AssocItemKind::DelegationMac(_) => Target::Delegation { mac: true },
            AssocItemKind::MacCall(_) => Target::MacroCall,
        }
    }

    pub fn from_expr(expr: &ast::Expr) -> Self {
        match &expr.kind {
            ast::ExprKind::Closure(..) | ast::ExprKind::Gen(..) => Self::Closure,
            ast::ExprKind::Paren(e) => Self::from_expr(&e),
            _ => Self::Expression,
        }
    }

    pub fn name(self) -> &'static str {
        match self {
            Target::ExternCrate => "extern crate",
            Target::Use => "use",
            Target::Static => "static",
            Target::Const => "constant",
            Target::Fn => "function",
            Target::Closure => "closure",
            Target::Mod => "module",
            Target::ForeignMod => "foreign module",
            Target::GlobalAsm => "global asm",
            Target::TyAlias => "type alias",
            Target::Enum => "enum",
            Target::Variant => "enum variant",
            Target::Struct => "struct",
            Target::Field => "struct field",
            Target::Union => "union",
            Target::Trait => "trait",
            Target::TraitAlias => "trait alias",
            Target::Impl { .. } => "implementation block",
            Target::Expression => "expression",
            Target::Statement => "statement",
            Target::Arm => "match arm",
            Target::AssocConst => "associated const",
            Target::Method(kind) => match kind {
                MethodKind::Inherent => "inherent method",
                MethodKind::Trait { body: false } => "required trait method",
                MethodKind::Trait { body: true } => "provided trait method",
                MethodKind::TraitImpl => "trait method in an impl block",
            },
            Target::AssocTy => "associated type",
            Target::ForeignFn => "foreign function",
            Target::ForeignStatic => "foreign static item",
            Target::ForeignTy => "foreign type",
            Target::GenericParam { kind, .. } => match kind {
                GenericParamKind::Type => "type parameter",
                GenericParamKind::Lifetime => "lifetime parameter",
                GenericParamKind::Const => "const parameter",
            },
            Target::MacroDef => "macro def",
            Target::Param => "function param",
            Target::PatField => "pattern field",
            Target::ExprField => "struct field",
            Target::WherePredicate => "where predicate",
            Target::MacroCall => "macro call",
            Target::Crate => "crate",
            Target::Delegation { .. } => "delegation",
        }
    }

    pub fn plural_name(self) -> &'static str {
        match self {
            Target::ExternCrate => "extern crates",
            Target::Use => "use statements",
            Target::Static => "statics",
            Target::Const => "constants",
            Target::Fn => "functions",
            Target::Closure => "closures",
            Target::Mod => "modules",
            Target::ForeignMod => "foreign modules",
            Target::GlobalAsm => "global asms",
            Target::TyAlias => "type aliases",
            Target::Enum => "enums",
            Target::Variant => "enum variants",
            Target::Struct => "structs",
            Target::Field => "struct fields",
            Target::Union => "unions",
            Target::Trait => "traits",
            Target::TraitAlias => "trait aliases",
            Target::Impl { of_trait: false } => "inherent impl blocks",
            Target::Impl { of_trait: true } => "trait impl blocks",
            Target::Expression => "expressions",
            Target::Statement => "statements",
            Target::Arm => "match arms",
            Target::AssocConst => "associated consts",
            Target::Method(kind) => match kind {
                MethodKind::Inherent => "inherent methods",
                MethodKind::Trait { body: false } => "required trait methods",
                MethodKind::Trait { body: true } => "provided trait methods",
                MethodKind::TraitImpl => "trait methods in impl blocks",
            },
            Target::AssocTy => "associated types",
            Target::ForeignFn => "foreign functions",
            Target::ForeignStatic => "foreign statics",
            Target::ForeignTy => "foreign types",
            Target::GenericParam { kind, has_default: _ } => match kind {
                GenericParamKind::Type => "type parameters",
                GenericParamKind::Lifetime => "lifetime parameters",
                GenericParamKind::Const => "const parameters",
            },
            Target::MacroDef => "macro defs",
            Target::Param => "function params",
            Target::PatField => "pattern fields",
            Target::ExprField => "struct fields",
            Target::WherePredicate => "where predicates",
            Target::MacroCall => "macro calls",
            Target::Crate => "crates",
            Target::Delegation { .. } => "delegations",
        }
    }
}
