use clippy_utils::paths::{PathNS, find_crates, lookup_path};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{Applicability, Diag};
use rustc_hir::PrimTy;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefIdMap;
use rustc_middle::ty::TyCtxt;
use rustc_span::{Span, Symbol};
use serde::de::{self, Deserializer, Visitor};
use serde::{Deserialize, Serialize, ser};
use std::collections::HashMap;
use std::fmt;

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Rename {
    pub path: String,
    pub rename: String,
}

pub type DisallowedPathWithoutReplacement = DisallowedPath<false>;

#[derive(Debug, Serialize)]
pub struct DisallowedPath<const REPLACEMENT_ALLOWED: bool = true> {
    path: String,
    reason: Option<String>,
    replacement: Option<String>,
    /// Setting `allow_invalid` to true suppresses a warning if `path` does not refer to an existing
    /// definition.
    ///
    /// This could be useful when conditional compilation is used, or when a clippy.toml file is
    /// shared among multiple projects.
    allow_invalid: bool,
    /// The span of the `DisallowedPath`.
    ///
    /// Used for diagnostics.
    #[serde(skip_serializing)]
    span: Span,
}

impl<'de, const REPLACEMENT_ALLOWED: bool> Deserialize<'de> for DisallowedPath<REPLACEMENT_ALLOWED> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let enum_ = DisallowedPathEnum::deserialize(deserializer)?;
        if !REPLACEMENT_ALLOWED && enum_.replacement().is_some() {
            return Err(de::Error::custom("replacement not allowed for this configuration"));
        }
        Ok(Self {
            path: enum_.path().to_owned(),
            reason: enum_.reason().map(ToOwned::to_owned),
            replacement: enum_.replacement().map(ToOwned::to_owned),
            allow_invalid: enum_.allow_invalid(),
            span: Span::default(),
        })
    }
}

// `DisallowedPathEnum` is an implementation detail to enable the `Deserialize` implementation just
// above. `DisallowedPathEnum` is not meant to be used outside of this file.
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged, deny_unknown_fields)]
enum DisallowedPathEnum {
    Simple(String),
    WithReason {
        path: String,
        reason: Option<String>,
        replacement: Option<String>,
        #[serde(rename = "allow-invalid")]
        allow_invalid: Option<bool>,
    },
}

impl<const REPLACEMENT_ALLOWED: bool> DisallowedPath<REPLACEMENT_ALLOWED> {
    pub fn path(&self) -> &str {
        &self.path
    }

    pub fn diag_amendment(&self, span: Span) -> impl FnOnce(&mut Diag<'_, ()>) {
        move |diag| {
            if let Some(replacement) = &self.replacement {
                diag.span_suggestion(
                    span,
                    self.reason.as_ref().map_or_else(|| String::from("use"), Clone::clone),
                    replacement,
                    Applicability::MachineApplicable,
                );
            } else if let Some(reason) = &self.reason {
                diag.note(reason.clone());
            }
        }
    }

    pub fn span(&self) -> Span {
        self.span
    }

    pub fn set_span(&mut self, span: Span) {
        self.span = span;
    }
}

impl DisallowedPathEnum {
    pub fn path(&self) -> &str {
        let (Self::Simple(path) | Self::WithReason { path, .. }) = self;

        path
    }

    fn reason(&self) -> Option<&str> {
        match &self {
            Self::WithReason { reason, .. } => reason.as_deref(),
            Self::Simple(_) => None,
        }
    }

    fn replacement(&self) -> Option<&str> {
        match &self {
            Self::WithReason { replacement, .. } => replacement.as_deref(),
            Self::Simple(_) => None,
        }
    }

    fn allow_invalid(&self) -> bool {
        match &self {
            Self::WithReason { allow_invalid, .. } => allow_invalid.unwrap_or_default(),
            Self::Simple(_) => false,
        }
    }
}

/// Creates a map of disallowed items to the reason they were disallowed.
#[expect(clippy::type_complexity)]
pub fn create_disallowed_map<const REPLACEMENT_ALLOWED: bool>(
    tcx: TyCtxt<'_>,
    disallowed_paths: &'static [DisallowedPath<REPLACEMENT_ALLOWED>],
    ns: PathNS,
    def_kind_predicate: impl Fn(DefKind) -> bool,
    predicate_description: &str,
    allow_prim_tys: bool,
) -> (
    DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)>,
    FxHashMap<PrimTy, (&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)>,
) {
    let mut def_ids: DefIdMap<(&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> = DefIdMap::default();
    let mut prim_tys: FxHashMap<PrimTy, (&'static str, &'static DisallowedPath<REPLACEMENT_ALLOWED>)> =
        FxHashMap::default();
    for disallowed_path in disallowed_paths {
        let path = disallowed_path.path();
        let sym_path: Vec<Symbol> = path.split("::").map(Symbol::intern).collect();
        let mut resolutions = lookup_path(tcx, ns, &sym_path);
        resolutions.retain(|&def_id| def_kind_predicate(tcx.def_kind(def_id)));

        let (prim_ty, found_prim_ty) = if let &[name] = sym_path.as_slice()
            && let Some(prim) = PrimTy::from_name(name)
        {
            (allow_prim_tys.then_some(prim), true)
        } else {
            (None, false)
        };

        if resolutions.is_empty()
            && prim_ty.is_none()
            && !disallowed_path.allow_invalid
            // Don't warn about unloaded crates:
            // https://github.com/rust-lang/rust-clippy/pull/14397#issuecomment-2848328221
            && (sym_path.len() < 2 || !find_crates(tcx, sym_path[0]).is_empty())
        {
            // Relookup the path in an arbitrary namespace to get a good `expected, found` message
            let found_def_ids = lookup_path(tcx, PathNS::Arbitrary, &sym_path);
            let message = if let Some(&def_id) = found_def_ids.first() {
                let (article, description) = tcx.article_and_description(def_id);
                format!("expected a {predicate_description}, found {article} {description}")
            } else if found_prim_ty {
                format!("expected a {predicate_description}, found a primitive type")
            } else {
                format!("`{path}` does not refer to a reachable {predicate_description}")
            };
            tcx.sess
                .dcx()
                .struct_span_warn(disallowed_path.span(), message)
                .with_help("add `allow-invalid = true` to the entry to suppress this warning")
                .emit();
        }

        for def_id in resolutions {
            def_ids.insert(def_id, (path, disallowed_path));
        }
        if let Some(ty) = prim_ty {
            prim_tys.insert(ty, (path, disallowed_path));
        }
    }

    (def_ids, prim_tys)
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum MatchLintBehaviour {
    AllTypes,
    WellKnownTypes,
    Never,
}

#[derive(Debug)]
pub struct MacroMatcher {
    pub name: String,
    pub braces: (char, char),
}

impl<'de> Deserialize<'de> for MacroMatcher {
    fn deserialize<D>(deser: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        #[serde(field_identifier, rename_all = "lowercase")]
        enum Field {
            Name,
            Brace,
        }
        struct MacVisitor;
        impl<'de> Visitor<'de> for MacVisitor {
            type Value = MacroMatcher;

            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                formatter.write_str("struct MacroMatcher")
            }

            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
            where
                V: de::MapAccess<'de>,
            {
                let mut name = None;
                let mut brace: Option<char> = None;
                while let Some(key) = map.next_key()? {
                    match key {
                        Field::Name => {
                            if name.is_some() {
                                return Err(de::Error::duplicate_field("name"));
                            }
                            name = Some(map.next_value()?);
                        },
                        Field::Brace => {
                            if brace.is_some() {
                                return Err(de::Error::duplicate_field("brace"));
                            }
                            brace = Some(map.next_value()?);
                        },
                    }
                }
                let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
                let brace = brace.ok_or_else(|| de::Error::missing_field("brace"))?;
                Ok(MacroMatcher {
                    name,
                    braces: [('(', ')'), ('{', '}'), ('[', ']')]
                        .into_iter()
                        .find(|b| b.0 == brace)
                        .map(|(o, c)| (o.to_owned(), c.to_owned()))
                        .ok_or_else(|| de::Error::custom(format!("expected one of `(`, `{{`, `[` found `{brace}`")))?,
                })
            }
        }

        const FIELDS: &[&str] = &["name", "brace"];
        deser.deserialize_struct("MacroMatcher", FIELDS, MacVisitor)
    }
}

/// Represents the item categories that can be ordered by the source ordering lint.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceItemOrderingCategory {
    Enum,
    Impl,
    Module,
    Struct,
    Trait,
}

/// Represents which item categories are enabled for ordering.
///
/// The [`Deserialize`] implementation checks that there are no duplicates in
/// the user configuration.
pub struct SourceItemOrdering(Vec<SourceItemOrderingCategory>);

impl SourceItemOrdering {
    pub fn contains(&self, category: &SourceItemOrderingCategory) -> bool {
        self.0.contains(category)
    }
}

impl<T> From<T> for SourceItemOrdering
where
    T: Into<Vec<SourceItemOrderingCategory>>,
{
    fn from(value: T) -> Self {
        Self(value.into())
    }
}

impl core::fmt::Debug for SourceItemOrdering {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl<'de> Deserialize<'de> for SourceItemOrdering {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let items = Vec::<SourceItemOrderingCategory>::deserialize(deserializer)?;
        let mut items_set = std::collections::HashSet::new();

        for item in &items {
            if items_set.contains(item) {
                return Err(de::Error::custom(format!(
                    "The category \"{item:?}\" was enabled more than once in the source ordering configuration."
                )));
            }
            items_set.insert(item);
        }

        Ok(Self(items))
    }
}

impl Serialize for SourceItemOrdering {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: ser::Serializer,
    {
        self.0.serialize(serializer)
    }
}

/// Represents the items that can occur within a module.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceItemOrderingModuleItemKind {
    ExternCrate,
    Mod,
    ForeignMod,
    Use,
    Macro,
    GlobalAsm,
    Static,
    Const,
    TyAlias,
    Enum,
    Struct,
    Union,
    Trait,
    TraitAlias,
    Impl,
    Fn,
}

impl SourceItemOrderingModuleItemKind {
    pub fn all_variants() -> Vec<Self> {
        #[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
        use SourceItemOrderingModuleItemKind::*;
        vec![
            ExternCrate,
            Mod,
            ForeignMod,
            Use,
            Macro,
            GlobalAsm,
            Static,
            Const,
            TyAlias,
            Enum,
            Struct,
            Union,
            Trait,
            TraitAlias,
            Impl,
            Fn,
        ]
    }
}

/// Represents the configured ordering of items within a module.
///
/// The [`Deserialize`] implementation checks that no item kinds have been
/// omitted and that there are no duplicates in the user configuration.
#[derive(Clone)]
pub struct SourceItemOrderingModuleItemGroupings {
    groups: Vec<(String, Vec<SourceItemOrderingModuleItemKind>)>,
    lut: HashMap<SourceItemOrderingModuleItemKind, usize>,
    back_lut: HashMap<SourceItemOrderingModuleItemKind, String>,
}

impl SourceItemOrderingModuleItemGroupings {
    fn build_lut(
        groups: &[(String, Vec<SourceItemOrderingModuleItemKind>)],
    ) -> HashMap<SourceItemOrderingModuleItemKind, usize> {
        let mut lut = HashMap::new();
        for (group_index, (_, items)) in groups.iter().enumerate() {
            for item in items {
                lut.insert(item.clone(), group_index);
            }
        }
        lut
    }

    fn build_back_lut(
        groups: &[(String, Vec<SourceItemOrderingModuleItemKind>)],
    ) -> HashMap<SourceItemOrderingModuleItemKind, String> {
        let mut lut = HashMap::new();
        for (group_name, items) in groups {
            for item in items {
                lut.insert(item.clone(), group_name.clone());
            }
        }
        lut
    }

    pub fn grouping_name_of(&self, item: &SourceItemOrderingModuleItemKind) -> Option<&String> {
        self.back_lut.get(item)
    }

    pub fn grouping_names(&self) -> Vec<String> {
        self.groups.iter().map(|(name, _)| name.clone()).collect()
    }

    pub fn is_grouping(&self, grouping: &str) -> bool {
        self.groups.iter().any(|(g, _)| g == grouping)
    }

    pub fn module_level_order_of(&self, item: &SourceItemOrderingModuleItemKind) -> Option<usize> {
        self.lut.get(item).copied()
    }
}

impl From<&[(&str, &[SourceItemOrderingModuleItemKind])]> for SourceItemOrderingModuleItemGroupings {
    fn from(value: &[(&str, &[SourceItemOrderingModuleItemKind])]) -> Self {
        let groups: Vec<(String, Vec<SourceItemOrderingModuleItemKind>)> =
            value.iter().map(|item| (item.0.to_string(), item.1.to_vec())).collect();
        let lut = Self::build_lut(&groups);
        let back_lut = Self::build_back_lut(&groups);
        Self { groups, lut, back_lut }
    }
}

impl core::fmt::Debug for SourceItemOrderingModuleItemGroupings {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.groups.fmt(f)
    }
}

impl<'de> Deserialize<'de> for SourceItemOrderingModuleItemGroupings {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let groups = Vec::<(String, Vec<SourceItemOrderingModuleItemKind>)>::deserialize(deserializer)?;
        let items_total: usize = groups.iter().map(|(_, v)| v.len()).sum();
        let lut = Self::build_lut(&groups);
        let back_lut = Self::build_back_lut(&groups);

        let mut expected_items = SourceItemOrderingModuleItemKind::all_variants();
        for item in lut.keys() {
            expected_items.retain(|i| i != item);
        }

        let all_items = SourceItemOrderingModuleItemKind::all_variants();
        if expected_items.is_empty() && items_total == all_items.len() {
            let Some(use_group_index) = lut.get(&SourceItemOrderingModuleItemKind::Use) else {
                return Err(de::Error::custom("Error in internal LUT."));
            };
            let Some((_, use_group_items)) = groups.get(*use_group_index) else {
                return Err(de::Error::custom("Error in internal LUT."));
            };
            if use_group_items.len() > 1 {
                return Err(de::Error::custom(
                    "The group containing the \"use\" item kind may not contain any other item kinds. \
                    The \"use\" items will (generally) be sorted by rustfmt already. \
                    Therefore it makes no sense to implement linting rules that may conflict with rustfmt.",
                ));
            }

            Ok(Self { groups, lut, back_lut })
        } else if items_total != all_items.len() {
            Err(de::Error::custom(format!(
                "Some module item kinds were configured more than once, or were missing, in the source ordering configuration. \
                The module item kinds are: {all_items:?}"
            )))
        } else {
            Err(de::Error::custom(format!(
                "Not all module item kinds were part of the configured source ordering rule. \
                All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \
                The module item kinds are: {all_items:?}"
            )))
        }
    }
}

impl Serialize for SourceItemOrderingModuleItemGroupings {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: ser::Serializer,
    {
        self.groups.serialize(serializer)
    }
}

/// Represents all kinds of trait associated items.
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceItemOrderingTraitAssocItemKind {
    Const,
    Fn,
    Type,
}

impl SourceItemOrderingTraitAssocItemKind {
    pub fn all_variants() -> Vec<Self> {
        #[allow(clippy::enum_glob_use)] // Very local glob use for legibility.
        use SourceItemOrderingTraitAssocItemKind::*;
        vec![Const, Fn, Type]
    }
}

/// Represents the order in which associated trait items should be ordered.
///
/// The reason to wrap a `Vec` in a newtype is to be able to implement
/// [`Deserialize`]. Implementing `Deserialize` allows for implementing checks
/// on configuration completeness at the time of loading the clippy config,
/// letting the user know if there's any issues with the config (e.g. not
/// listing all item kinds that should be sorted).
#[derive(Clone)]
pub struct SourceItemOrderingTraitAssocItemKinds(Vec<SourceItemOrderingTraitAssocItemKind>);

impl SourceItemOrderingTraitAssocItemKinds {
    pub fn index_of(&self, item: &SourceItemOrderingTraitAssocItemKind) -> Option<usize> {
        self.0.iter().position(|i| i == item)
    }
}

impl<T> From<T> for SourceItemOrderingTraitAssocItemKinds
where
    T: Into<Vec<SourceItemOrderingTraitAssocItemKind>>,
{
    fn from(value: T) -> Self {
        Self(value.into())
    }
}

impl core::fmt::Debug for SourceItemOrderingTraitAssocItemKinds {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl<'de> Deserialize<'de> for SourceItemOrderingTraitAssocItemKinds {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let items = Vec::<SourceItemOrderingTraitAssocItemKind>::deserialize(deserializer)?;

        let mut expected_items = SourceItemOrderingTraitAssocItemKind::all_variants();
        for item in &items {
            expected_items.retain(|i| i != item);
        }

        let all_items = SourceItemOrderingTraitAssocItemKind::all_variants();
        if expected_items.is_empty() && items.len() == all_items.len() {
            Ok(Self(items))
        } else if items.len() != all_items.len() {
            Err(de::Error::custom(format!(
                "Some trait associated item kinds were configured more than once, or were missing, in the source ordering configuration. \
                The trait associated item kinds are: {all_items:?}",
            )))
        } else {
            Err(de::Error::custom(format!(
                "Not all trait associated item kinds were part of the configured source ordering rule. \
                All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \
                The trait associated item kinds are: {all_items:?}"
            )))
        }
    }
}

impl Serialize for SourceItemOrderingTraitAssocItemKinds {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: ser::Serializer,
    {
        self.0.serialize(serializer)
    }
}

/// Describes which specific groupings should have their items ordered
/// alphabetically.
///
/// This is separate from defining and enforcing groupings. For example,
/// defining enums are grouped before structs still allows for an enum B to be
/// placed before an enum A. Only when enforcing ordering within the grouping,
/// will it be checked if A is placed before B.
#[derive(Clone, Debug)]
pub enum SourceItemOrderingWithinModuleItemGroupings {
    /// All groupings should have their items ordered.
    All,

    /// None of the groupings should have their order checked.
    None,

    /// Only the specified groupings should have their order checked.
    Custom(Vec<String>),
}

impl SourceItemOrderingWithinModuleItemGroupings {
    pub fn ordered_within(&self, grouping_name: &String) -> bool {
        match self {
            SourceItemOrderingWithinModuleItemGroupings::All => true,
            SourceItemOrderingWithinModuleItemGroupings::None => false,
            SourceItemOrderingWithinModuleItemGroupings::Custom(groups) => groups.contains(grouping_name),
        }
    }
}

/// Helper struct for deserializing the [`SourceItemOrderingWithinModuleItemGroupings`].
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrVecOfString {
    String(String),
    Vec(Vec<String>),
}

impl<'de> Deserialize<'de> for SourceItemOrderingWithinModuleItemGroupings {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let description = "The available options for configuring an ordering within module item groups are: \
                    \"all\", \"none\", or a list of module item group names \
                    (as configured with the `module-item-order-groupings` configuration option).";

        match StringOrVecOfString::deserialize(deserializer) {
            Ok(StringOrVecOfString::String(preset)) if preset == "all" => {
                Ok(SourceItemOrderingWithinModuleItemGroupings::All)
            },
            Ok(StringOrVecOfString::String(preset)) if preset == "none" => {
                Ok(SourceItemOrderingWithinModuleItemGroupings::None)
            },
            Ok(StringOrVecOfString::String(preset)) => Err(de::Error::custom(format!(
                "Unknown configuration option: {preset}.\n{description}"
            ))),
            Ok(StringOrVecOfString::Vec(groupings)) => {
                Ok(SourceItemOrderingWithinModuleItemGroupings::Custom(groupings))
            },
            Err(e) => Err(de::Error::custom(format!("{e}\n{description}"))),
        }
    }
}

impl Serialize for SourceItemOrderingWithinModuleItemGroupings {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: ser::Serializer,
    {
        match self {
            SourceItemOrderingWithinModuleItemGroupings::All => serializer.serialize_str("all"),
            SourceItemOrderingWithinModuleItemGroupings::None => serializer.serialize_str("none"),
            SourceItemOrderingWithinModuleItemGroupings::Custom(vec) => vec.serialize(serializer),
        }
    }
}

// these impls are never actually called but are used by the various config options that default to
// empty lists
macro_rules! unimplemented_serialize {
    ($($t:ty,)*) => {
        $(
            impl Serialize for $t {
                fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
                where
                    S: ser::Serializer,
                {
                    Err(ser::Error::custom("unimplemented"))
                }
            }
        )*
    }
}

unimplemented_serialize! {
    Rename,
    MacroMatcher,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum PubUnderscoreFieldsBehaviour {
    PubliclyExported,
    AllPubFields,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum InherentImplLintScope {
    Crate,
    File,
    Module,
}
