//! HirDisplay implementations for various hir types.

use either::Either;
use hir_def::{
    AdtId, BuiltinDeriveImplId, DefWithBodyId, ExpressionStoreOwnerId, FunctionId, GenericDefId,
    ImplId, ItemContainerId,
    builtin_derive::BuiltinDeriveImplMethod,
    expr_store::{Body, ExpressionStore},
    hir::generics::{GenericParams, TypeOrConstParamData, TypeParamProvenance, WherePredicate},
    item_tree::FieldsShape,
    layout::ExternAbi,
    signatures::{
        ConstSignature, FunctionSignature, ImplSignature, StaticFlags, StaticSignature, TraitFlags,
        TraitSignature, TypeAliasSignature,
    },
    type_ref::{TypeBound, TypeRef, TypeRefId},
};
use hir_expand::name::Name;
use hir_ty::{
    GenericPredicates,
    db::HirDatabase,
    display::{
        HirDisplay, HirDisplayWithExpressionStore, HirFormatter, Result, SizedByDefault,
        hir_display_with_store, write_bounds_like_dyn_trait_with_prefix, write_params_bounds,
        write_visibility,
    },
    next_solver::{ClauseKind, Unnormalized},
};
use itertools::Itertools;
use rustc_type_ir::inherent::IntoKind as _;

use crate::{
    Adt, AnyFunctionId, AsAssocItem, AssocItem, AssocItemContainer, Const, ConstParam, Crate, Enum,
    EnumVariant, ExternCrateDecl, Field, Function, GenericParam, HasCrate, HasVisibility, Impl,
    LifetimeParam, Macro, Module, SelfParam, Static, Struct, StructKind, Trait, TraitPredicate,
    TraitRef, TupleField, Type, TypeAlias, TypeNs, TypeOrConstParam, TypeParam, Union,
};

fn write_builtin_derive_impl_method<'db>(
    f: &mut HirFormatter<'_, 'db>,
    impl_: BuiltinDeriveImplId,
    method: BuiltinDeriveImplMethod,
) -> Result {
    let db = f.db;
    let loc = impl_.loc(db);
    let adt_params = GenericParams::of(db, loc.adt.into());

    if f.show_container_bounds() && !adt_params.is_empty() {
        f.write_str("impl")?;
        write_generic_params(loc.adt.into(), f)?;
        f.write_char(' ')?;
        let trait_id = loc.trait_.get_id(f.lang_items());
        if let Some(trait_id) = trait_id {
            f.start_location_link(trait_id.into());
        }
        write!(f, "{}", Name::new_symbol_root(loc.trait_.name()).display(db, f.edition()))?;
        if trait_id.is_some() {
            f.end_location_link();
        }
        f.write_str(" for ")?;
        f.start_location_link(loc.adt.into());
        write!(f, "{}", Adt::from(loc.adt).name(db).display(db, f.edition()))?;
        f.end_location_link();
        write_generic_args(loc.adt.into(), f)?;
        f.write_char('\n')?;
    }

    let Some(trait_method) = method.trait_method(db, impl_) else {
        return write!(f, "fn {}(…)", method.name());
    };
    let has_written_where = write_function(f, trait_method)?;

    if f.show_container_bounds() && !adt_params.is_empty() {
        if !has_written_where {
            f.write_str("\nwhere")?
        }
        write!(f, "\n    // Bounds from impl:")?;

        let predicates =
            hir_ty::builtin_derive::predicates(db, impl_).explicit_predicates().skip_binder();
        write_params_bounds(f, &Vec::from_iter(predicates))?;
    }

    Ok(())
}

impl<'db> HirDisplay<'db> for Function {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        let id = match self.id {
            AnyFunctionId::FunctionId(id) => id,
            AnyFunctionId::BuiltinDeriveImplMethod { method, impl_ } => {
                return write_builtin_derive_impl_method(f, impl_, method);
            }
        };

        let db = f.db;
        let container = id.loc(db).container;

        // Write container (trait or impl)
        let container_params = match container {
            ItemContainerId::TraitId(trait_) => {
                let (params, params_store) = GenericParams::with_store(f.db, trait_.into());
                if f.show_container_bounds() && !params.is_empty() {
                    write_trait_header(trait_.into(), f)?;
                    f.write_char('\n')?;
                    has_disaplayable_predicates(f.db, params, params_store).then_some((
                        params,
                        trait_.into(),
                        params_store,
                    ))
                } else {
                    None
                }
            }
            ItemContainerId::ImplId(impl_) => {
                let (params, params_store) = GenericParams::with_store(f.db, impl_.into());
                if f.show_container_bounds() && !params.is_empty() {
                    write_impl_header(impl_, f)?;
                    f.write_char('\n')?;
                    has_disaplayable_predicates(f.db, params, params_store).then_some((
                        params,
                        impl_.into(),
                        params_store,
                    ))
                } else {
                    None
                }
            }
            _ => None,
        };

        // Write signature of the function

        let has_written_where = write_function(f, id)?;
        if let Some((container_params, owner, container_params_store)) = container_params {
            if !has_written_where {
                f.write_str("\nwhere")?;
            }
            let container_name = match container {
                ItemContainerId::TraitId(_) => "trait",
                ItemContainerId::ImplId(_) => "impl",
                _ => unreachable!(),
            };
            write!(f, "\n    // Bounds from {container_name}:",)?;
            write_where_predicates(
                container_params,
                ExpressionStoreOwnerId::Signature(owner),
                container_params_store,
                f,
            )?;
        }
        Ok(())
    }
}

fn write_function<'db>(f: &mut HirFormatter<'_, 'db>, func_id: FunctionId) -> Result<bool> {
    let db = f.db;
    let func = Function::from(func_id);
    let data = FunctionSignature::of(db, func_id);

    let mut module = func.module(db);
    // Block-local impls are "hoisted" to the nearest (non-block) module.
    if let ItemContainerId::ImplId(_) = func_id.loc(db).container {
        module = module.nearest_non_block_module(db);
    }
    let module_id = module.id;

    write_visibility(module_id, func.visibility(db), f)?;

    if data.is_default() {
        f.write_str("default ")?;
    }
    if data.is_const() {
        f.write_str("const ")?;
    }
    if data.is_async() {
        f.write_str("async ")?;
    }
    if data.is_gen() {
        f.write_str("gen ")?;
    }
    // FIXME: This will show `unsafe` for functions that are `#[target_feature]` but not unsafe
    // (they are conditionally unsafe to call). We probably should show something else.
    if func.is_unsafe_to_call(db, None, f.edition()) {
        f.write_str("unsafe ")?;
    }
    if data.abi != ExternAbi::Rust {
        write!(f, "extern \"{}\" ", data.abi.as_str())?;
    }
    write!(f, "fn {}", data.name.display(f.db, f.edition()))?;

    write_generic_params(GenericDefId::FunctionId(func_id), f)?;

    let too_long_param = data.params.len() > 4;
    f.write_char('(')?;

    if too_long_param {
        f.write_str("\n    ")?;
    }

    let mut first = true;
    let mut skip_self = 0;
    if let Some(self_param) = func.self_param(db) {
        self_param.hir_fmt(f)?;
        first = false;
        skip_self = 1;
    }

    let comma = if too_long_param { ",\n    " } else { ", " };
    // FIXME: Use resolved `param.ty` once we no longer discard lifetimes
    let body = Body::of(db, func_id.into());
    let owner = DefWithBodyId::FunctionId(func_id).into();
    for (type_ref, param) in data.params.iter().zip(func.assoc_fn_params(db)).skip(skip_self) {
        if !first {
            f.write_str(comma)?;
        } else {
            first = false;
        }

        let pat_id = body.params[param.idx - body.self_param().is_some() as usize];
        let pat_str = body.pretty_print_pat(db, owner, pat_id, true, f.edition());
        f.write_str(&pat_str)?;

        f.write_str(": ")?;
        type_ref.hir_fmt(f, owner, &data.store)?;
    }

    if data.is_varargs() {
        if !first {
            f.write_str(comma)?;
        }
        f.write_str("...")?;
    }

    if too_long_param {
        f.write_char('\n')?;
    }
    f.write_char(')')?;

    // `FunctionData::ret_type` will be `::core::future::Future<Output = ...>` for async fns.
    // Use ugly pattern match to strip the Future trait.
    // Better way?
    let ret_type = if !data.is_async() && !data.is_gen() {
        data.ret_type
    } else if let Some(ret_type) = data.ret_type {
        match &data.store[ret_type] {
            TypeRef::ImplTrait(bounds) => match &bounds[0] {
                &TypeBound::Path(path, _) => Some(
                    *data.store[path]
                        .segments()
                        .iter()
                        .last()
                        .unwrap()
                        .args_and_bindings
                        .unwrap()
                        .bindings[0]
                        .type_ref
                        .as_ref()
                        .unwrap(),
                ),
                _ => None,
            },
            _ => None,
        }
    } else {
        None
    };

    if let Some(ret_type) = ret_type {
        match &data.store[ret_type] {
            TypeRef::Tuple(tup) if tup.is_empty() => {}
            _ => {
                f.write_str(" -> ")?;
                ret_type.hir_fmt(f, owner, &data.store)?;
            }
        }
    }

    // Write where clauses
    let has_written_where = write_where_clause(GenericDefId::FunctionId(func_id), f)?;
    Ok(has_written_where)
}

fn write_impl_header<'db>(impl_: ImplId, f: &mut HirFormatter<'_, 'db>) -> Result {
    let db = f.db;

    f.write_str("impl")?;
    let def_id = GenericDefId::ImplId(impl_);
    write_generic_params(def_id, f)?;

    let impl_data = ImplSignature::of(db, impl_);
    if let Some(target_trait) = &impl_data.target_trait {
        f.write_char(' ')?;
        hir_display_with_store(&impl_data.store[target_trait.path], impl_.into(), &impl_data.store)
            .hir_fmt(f)?;
        f.write_str(" for")?;
    }

    f.write_char(' ')?;
    Impl::from(impl_).self_ty(db).hir_fmt(f)?;

    Ok(())
}

impl<'db> HirDisplay<'db> for SelfParam {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        let func = match self.func.id {
            AnyFunctionId::FunctionId(id) => id,
            AnyFunctionId::BuiltinDeriveImplMethod { method, .. } => match method {
                BuiltinDeriveImplMethod::clone
                | BuiltinDeriveImplMethod::fmt
                | BuiltinDeriveImplMethod::hash
                | BuiltinDeriveImplMethod::cmp
                | BuiltinDeriveImplMethod::partial_cmp
                | BuiltinDeriveImplMethod::eq => return f.write_str("&self"),
                BuiltinDeriveImplMethod::default => {
                    unreachable!("this trait method does not have a self param")
                }
            },
        };
        let data = FunctionSignature::of(f.db, func);
        let param = *data.params.first().unwrap();
        let owner = ExpressionStoreOwnerId::Body(func.into());
        match &data.store[param] {
            TypeRef::Path(p) if p.is_self_type() => f.write_str("self"),
            TypeRef::Reference(ref_) if matches!(&data.store[ref_.ty], TypeRef::Path(p) if p.is_self_type()) =>
            {
                f.write_char('&')?;
                if let Some(lifetime) = &ref_.lifetime {
                    lifetime.hir_fmt(f, owner, &data.store)?;
                    f.write_char(' ')?;
                }
                if let hir_def::type_ref::Mutability::Mut = ref_.mutability {
                    f.write_str("mut ")?;
                }
                f.write_str("self")
            }
            _ => {
                f.write_str("self: ")?;
                param.hir_fmt(f, owner, &data.store)
            }
        }
    }
}

impl<'db> HirDisplay<'db> for Adt {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        match self {
            Adt::Struct(it) => it.hir_fmt(f),
            Adt::Union(it) => it.hir_fmt(f),
            Adt::Enum(it) => it.hir_fmt(f),
        }
    }
}

impl<'db> HirDisplay<'db> for Struct {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        let module_id = self.module(f.db).id;
        // FIXME: Render repr if its set explicitly?
        write_visibility(module_id, self.visibility(f.db), f)?;
        f.write_str("struct ")?;
        write!(f, "{}", self.name(f.db).display(f.db, f.edition()))?;
        let def_id = GenericDefId::AdtId(AdtId::StructId(self.id));
        write_generic_params(def_id, f)?;

        let variant_data = self.variant_fields(f.db);
        match self.kind(f.db) {
            StructKind::Tuple => {
                f.write_char('(')?;
                let mut it = variant_data.fields().iter().peekable();

                while let Some((id, _)) = it.next() {
                    let field = Field { parent: (*self).into(), id };
                    write_visibility(module_id, field.visibility(f.db), f)?;
                    field.ty(f.db).hir_fmt(f)?;
                    if it.peek().is_some() {
                        f.write_str(", ")?;
                    }
                }

                f.write_char(')')?;
                write_where_clause(def_id, f)?;
            }
            StructKind::Record => {
                let has_where_clause = write_where_clause(def_id, f)?;
                if let Some(limit) = f.entity_limit {
                    write_fields(&self.fields(f.db), has_where_clause, limit, false, f)?;
                }
            }
            StructKind::Unit => _ = write_where_clause(def_id, f)?,
        }

        Ok(())
    }
}

impl<'db> HirDisplay<'db> for Enum {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
        f.write_str("enum ")?;
        write!(f, "{}", self.name(f.db).display(f.db, f.edition()))?;
        let def_id = GenericDefId::AdtId(AdtId::EnumId(self.id));
        write_generic_params(def_id, f)?;

        let has_where_clause = write_where_clause(def_id, f)?;
        if let Some(limit) = f.entity_limit {
            write_variants(&self.variants(f.db), has_where_clause, limit, f)?;
        }

        Ok(())
    }
}

impl<'db> HirDisplay<'db> for Union {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
        f.write_str("union ")?;
        write!(f, "{}", self.name(f.db).display(f.db, f.edition()))?;
        let def_id = GenericDefId::AdtId(AdtId::UnionId(self.id));
        write_generic_params(def_id, f)?;

        let has_where_clause = write_where_clause(def_id, f)?;
        if let Some(limit) = f.entity_limit {
            write_fields(&self.fields(f.db), has_where_clause, limit, false, f)?;
        }
        Ok(())
    }
}

fn write_fields<'db>(
    fields: &[Field],
    has_where_clause: bool,
    limit: usize,
    in_line: bool,
    f: &mut HirFormatter<'_, 'db>,
) -> Result {
    let count = fields.len().min(limit);
    let (indent, separator) = if in_line { ("", ' ') } else { ("    ", '\n') };
    f.write_char(if !has_where_clause { ' ' } else { separator })?;
    if count == 0 {
        f.write_str(if fields.is_empty() { "{}" } else { "{ /* … */ }" })?;
    } else {
        f.write_char('{')?;

        if !fields.is_empty() {
            f.write_char(separator)?;
            for field in &fields[..count] {
                f.write_str(indent)?;
                field.hir_fmt(f)?;
                write!(f, ",{separator}")?;
            }

            if fields.len() > count {
                write!(f, "{indent}/* … */{separator}")?;
            }
        }

        f.write_str("}")?;
    }

    Ok(())
}

fn write_variants<'db>(
    variants: &[EnumVariant],
    has_where_clause: bool,
    limit: usize,
    f: &mut HirFormatter<'_, 'db>,
) -> Result {
    let count = variants.len().min(limit);
    f.write_char(if !has_where_clause { ' ' } else { '\n' })?;
    if count == 0 {
        let variants = if variants.is_empty() { "{}" } else { "{ /* … */ }" };
        f.write_str(variants)?;
    } else {
        f.write_str("{\n")?;
        for variant in &variants[..count] {
            write!(f, "    {}", variant.name(f.db).display(f.db, f.edition()))?;
            match variant.kind(f.db) {
                StructKind::Tuple => {
                    let fields_str =
                        if variant.fields(f.db).is_empty() { "()" } else { "( /* … */ )" };
                    f.write_str(fields_str)?;
                }
                StructKind::Record => {
                    let fields_str =
                        if variant.fields(f.db).is_empty() { " {}" } else { " { /* … */ }" };
                    f.write_str(fields_str)?;
                }
                StructKind::Unit => {}
            }
            f.write_str(",\n")?;
        }

        if variants.len() > count {
            f.write_str("    /* … */\n")?;
        }
        f.write_str("}")?;
    }

    Ok(())
}

impl<'db> HirDisplay<'db> for Field {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        write_visibility(self.parent.module(f.db).id, self.visibility(f.db), f)?;
        write!(f, "{}: ", self.name(f.db).display(f.db, f.edition()))?;
        self.ty(f.db).hir_fmt(f)
    }
}

impl<'db> HirDisplay<'db> for TupleField {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        write!(f, "pub {}: ", self.name().display(f.db, f.edition()))?;
        self.ty(f.db).hir_fmt(f)
    }
}

impl<'db> HirDisplay<'db> for EnumVariant {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        write!(f, "{}", self.name(f.db).display(f.db, f.edition()))?;
        let data = self.id.fields(f.db);
        match data.shape {
            FieldsShape::Unit => {}
            FieldsShape::Tuple => {
                f.write_char('(')?;
                let mut first = true;
                for (_, field) in data.fields().iter() {
                    if first {
                        first = false;
                    } else {
                        f.write_str(", ")?;
                    }
                    // Enum variant fields must be pub.
                    field.type_ref.hir_fmt(
                        f,
                        ExpressionStoreOwnerId::VariantFields(self.id.into()),
                        &data.store,
                    )?;
                }
                f.write_char(')')?;
            }
            FieldsShape::Record => {
                if let Some(limit) = f.entity_limit {
                    write_fields(&self.fields(f.db), false, limit, true, f)?;
                }
            }
        }
        Ok(())
    }
}

impl<'db> HirDisplay<'db> for Type<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        self.ty.hir_fmt(f)
    }
}

impl<'db> HirDisplay<'db> for TypeNs<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        self.ty.hir_fmt(f)
    }
}

impl<'db> HirDisplay<'db> for ExternCrateDecl {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
        f.write_str("extern crate ")?;
        write!(f, "{}", self.name(f.db).display(f.db, f.edition()))?;
        if let Some(alias) = self.alias(f.db) {
            write!(f, " as {}", alias.display(f.edition()))?;
        }
        Ok(())
    }
}

impl<'db> HirDisplay<'db> for GenericParam {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        match self {
            GenericParam::TypeParam(it) => it.hir_fmt(f),
            GenericParam::ConstParam(it) => it.hir_fmt(f),
            GenericParam::LifetimeParam(it) => it.hir_fmt(f),
        }
    }
}

impl<'db> HirDisplay<'db> for TypeOrConstParam {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        match self.split(f.db) {
            either::Either::Left(it) => it.hir_fmt(f),
            either::Either::Right(it) => it.hir_fmt(f),
        }
    }
}

impl<'db> HirDisplay<'db> for TypeParam {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        let params = GenericParams::of(f.db, self.id.parent());
        let param_data = &params[self.id.local_id()];
        let krate = self.id.parent().krate(f.db).id;
        let ty = self.ty(f.db).ty;
        let predicates = GenericPredicates::query_all(f.db, self.id.parent());
        let predicates = predicates
            .iter_identity()
            .map(Unnormalized::skip_norm_wip)
            .filter(|wc| match wc.kind().skip_binder() {
                ClauseKind::Trait(tr) => tr.self_ty() == ty,
                ClauseKind::Projection(proj) => proj.self_ty() == ty,
                ClauseKind::TypeOutlives(to) => to.0 == ty,
                _ => false,
            })
            .collect::<Vec<_>>();

        match param_data {
            TypeOrConstParamData::TypeParamData(p) => match p.provenance {
                TypeParamProvenance::TypeParamList | TypeParamProvenance::TraitSelf => {
                    write!(f, "{}", p.name.clone().unwrap().display(f.db, f.edition()))?
                }
                TypeParamProvenance::ArgumentImplTrait => {
                    return write_bounds_like_dyn_trait_with_prefix(
                        f,
                        "impl",
                        Either::Left(ty),
                        &predicates,
                        SizedByDefault::Sized { anchor: krate },
                        false,
                    );
                }
            },
            TypeOrConstParamData::ConstParamData(p) => {
                write!(f, "{}", p.name.display(f.db, f.edition()))?;
            }
        }

        if f.omit_verbose_types() {
            return Ok(());
        }

        let sized_trait = f.lang_items().Sized;
        let has_only_sized_bound =
            predicates.iter().all(move |pred| match pred.kind().skip_binder() {
                ClauseKind::Trait(it) => Some(it.def_id().0) == sized_trait,
                _ => false,
            });
        let has_only_not_sized_bound = predicates.is_empty();
        if !has_only_sized_bound || has_only_not_sized_bound {
            let default_sized = SizedByDefault::Sized { anchor: krate };
            write_bounds_like_dyn_trait_with_prefix(
                f,
                ":",
                Either::Left(ty),
                &predicates,
                default_sized,
                false,
            )?;
        }
        Ok(())
    }
}

impl<'db> HirDisplay<'db> for LifetimeParam {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        write!(f, "{}", self.name(f.db).display(f.db, f.edition()))
    }
}

impl<'db> HirDisplay<'db> for ConstParam {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        write!(f, "const {}: ", self.name(f.db).display(f.db, f.edition()))?;
        self.ty(f.db).hir_fmt(f)
    }
}

fn write_generic_params<'db>(def: GenericDefId, f: &mut HirFormatter<'_, 'db>) -> Result {
    write_generic_params_or_args(def, f, true)
}

fn write_generic_args<'db>(def: GenericDefId, f: &mut HirFormatter<'_, 'db>) -> Result {
    write_generic_params_or_args(def, f, false)
}

fn write_generic_params_or_args<'db>(
    def: GenericDefId,
    f: &mut HirFormatter<'_, 'db>,
    include_defaults: bool,
) -> Result {
    let (params, store) = GenericParams::with_store(f.db, def);
    let owner = def.into();
    if params.iter_lt().next().is_none()
        && params.iter_type_or_consts().all(|it| it.1.const_param().is_none())
        && params
            .iter_type_or_consts()
            .filter_map(|it| it.1.type_param())
            .all(|param| !matches!(param.provenance, TypeParamProvenance::TypeParamList))
    {
        return Ok(());
    }
    f.write_char('<')?;

    let mut first = true;
    let mut delim = |f: &mut HirFormatter<'_, 'db>| {
        if first {
            first = false;
            Ok(())
        } else {
            f.write_str(", ")
        }
    };
    for (_, lifetime) in params.iter_lt() {
        delim(f)?;
        write!(f, "{}", lifetime.name.display(f.db, f.edition()))?;
    }
    for (_, ty) in params.iter_type_or_consts() {
        if let Some(name) = &ty.name() {
            match ty {
                TypeOrConstParamData::TypeParamData(ty) => {
                    if ty.provenance != TypeParamProvenance::TypeParamList {
                        continue;
                    }
                    delim(f)?;
                    write!(f, "{}", name.display(f.db, f.edition()))?;
                    if include_defaults && let Some(default) = &ty.default {
                        f.write_str(" = ")?;
                        default.hir_fmt(f, owner, store)?;
                    }
                }
                TypeOrConstParamData::ConstParamData(c) => {
                    delim(f)?;
                    write!(f, "const {}: ", name.display(f.db, f.edition()))?;
                    c.ty.hir_fmt(f, owner, store)?;

                    if include_defaults && let Some(default) = &c.default {
                        f.write_str(" = ")?;
                        default.hir_fmt(f, owner, store)?;
                    }
                }
            }
        }
    }

    f.write_char('>')?;
    Ok(())
}

fn write_where_clause<'db>(def: GenericDefId, f: &mut HirFormatter<'_, 'db>) -> Result<bool> {
    let (params, store) = GenericParams::with_store(f.db, def);
    if !has_disaplayable_predicates(f.db, params, store) {
        return Ok(false);
    }

    f.write_str("\nwhere")?;
    write_where_predicates(params, def.into(), store, f)?;

    Ok(true)
}

fn has_disaplayable_predicates(
    db: &dyn HirDatabase,
    params: &GenericParams,
    store: &ExpressionStore,
) -> bool {
    params.where_predicates().iter().any(|pred| {
        !matches!(
            pred,
            WherePredicate::TypeBound { target, .. }
            if  matches!(store[*target],
                TypeRef::TypeParam(id) if GenericParams::of(db,id.parent())[id.local_id()].name().is_none()
            )
        )
    })
}

fn write_where_predicates<'db>(
    params: &GenericParams,
    owner: ExpressionStoreOwnerId,
    store: &ExpressionStore,
    f: &mut HirFormatter<'_, 'db>,
) -> Result {
    use WherePredicate::*;

    // unnamed type targets are displayed inline with the argument itself, e.g. `f: impl Y`.
    let is_unnamed_type_target = |target: TypeRefId| {
        matches!(store[target],
            TypeRef::TypeParam(id) if GenericParams::of(f.db,id.parent())[id.local_id()].name().is_none()
        )
    };

    let check_same_target = |pred1: &WherePredicate, pred2: &WherePredicate| match (pred1, pred2) {
        (TypeBound { target: t1, .. }, TypeBound { target: t2, .. }) => t1 == t2,
        (Lifetime { target: t1, .. }, Lifetime { target: t2, .. }) => t1 == t2,
        (
            ForLifetime { lifetimes: l1, target: t1, .. },
            ForLifetime { lifetimes: l2, target: t2, .. },
        ) => l1 == l2 && t1 == t2,
        _ => false,
    };

    let mut iter = params.where_predicates().iter().peekable();
    while let Some(pred) = iter.next() {
        if matches!(pred, TypeBound { target, .. } if is_unnamed_type_target(*target)) {
            continue;
        }

        f.write_str("\n    ")?;
        match pred {
            TypeBound { target, bound } => {
                target.hir_fmt(f, owner, store)?;
                f.write_str(": ")?;
                bound.hir_fmt(f, owner, store)?;
            }
            Lifetime { target, bound } => {
                target.hir_fmt(f, owner, store)?;
                write!(f, ": ")?;
                bound.hir_fmt(f, owner, store)?;
            }
            ForLifetime { lifetimes, target, bound } => {
                let lifetimes = lifetimes.iter().map(|it| it.display(f.db, f.edition())).join(", ");
                write!(f, "for<{lifetimes}> ")?;
                target.hir_fmt(f, owner, store)?;
                f.write_str(": ")?;
                bound.hir_fmt(f, owner, store)?;
            }
        }

        while let Some(nxt) = iter.next_if(|nxt| check_same_target(pred, nxt)) {
            f.write_str(" + ")?;
            match nxt {
                TypeBound { bound, .. } | ForLifetime { bound, .. } => {
                    bound.hir_fmt(f, owner, store)?
                }
                Lifetime { bound, .. } => bound.hir_fmt(f, owner, store)?,
            }
        }
        f.write_str(",")?;
    }

    Ok(())
}

impl<'db> HirDisplay<'db> for Const {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        let db = f.db;
        let container = self.as_assoc_item(db).map(|it| it.container(db));
        let mut module = self.module(db);
        if let Some(AssocItemContainer::Impl(_)) = container {
            // Block-local impls are "hoisted" to the nearest (non-block) module.
            module = module.nearest_non_block_module(db);
        }
        write_visibility(module.id, self.visibility(db), f)?;
        let data = ConstSignature::of(db, self.id);
        f.write_str("const ")?;
        match &data.name {
            Some(name) => write!(f, "{}: ", name.display(f.db, f.edition()))?,
            None => f.write_str("_: ")?,
        }
        data.type_ref.hir_fmt(f, ExpressionStoreOwnerId::Signature(self.id.into()), &data.store)?;
        Ok(())
    }
}

impl<'db> HirDisplay<'db> for Static {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
        let data = StaticSignature::of(f.db, self.id);
        f.write_str("static ")?;
        if data.flags.contains(StaticFlags::MUTABLE) {
            f.write_str("mut ")?;
        }
        write!(f, "{}: ", data.name.display(f.db, f.edition()))?;
        data.type_ref.hir_fmt(f, ExpressionStoreOwnerId::Signature(self.id.into()), &data.store)?;
        Ok(())
    }
}

impl<'db> HirDisplay<'db> for TraitRef<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        self.trait_ref.hir_fmt(f)
    }
}

impl<'db> HirDisplay<'db> for TraitPredicate<'db> {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        self.inner.hir_fmt(f)
    }
}

impl<'db> HirDisplay<'db> for Trait {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        // FIXME(trait-alias) needs special handling to print the equal sign
        write_trait_header(*self, f)?;
        let def_id = GenericDefId::TraitId(self.id);
        let has_where_clause = write_where_clause(def_id, f)?;

        if let Some(limit) = f.entity_limit {
            let assoc_items = self.items(f.db);
            let count = assoc_items.len().min(limit);
            f.write_char(if !has_where_clause { ' ' } else { '\n' })?;
            if count == 0 {
                if assoc_items.is_empty() {
                    f.write_str("{}")?;
                } else {
                    f.write_str("{ /* … */ }")?;
                }
            } else {
                f.write_str("{\n")?;
                for item in &assoc_items[..count] {
                    f.write_str("    ")?;
                    match item {
                        AssocItem::Function(func) => func.hir_fmt(f),
                        AssocItem::Const(cst) => cst.hir_fmt(f),
                        AssocItem::TypeAlias(type_alias) => type_alias.hir_fmt(f),
                    }?;
                    f.write_str(";\n")?;
                }

                if assoc_items.len() > count {
                    f.write_str("    /* … */\n")?;
                }
                f.write_str("}")?;
            }
        }

        Ok(())
    }
}

fn write_trait_header<'db>(trait_: Trait, f: &mut HirFormatter<'_, 'db>) -> Result {
    write_visibility(trait_.module(f.db).id, trait_.visibility(f.db), f)?;
    let data = TraitSignature::of(f.db, trait_.id);
    if data.flags.contains(TraitFlags::UNSAFE) {
        f.write_str("unsafe ")?;
    }
    if data.flags.contains(TraitFlags::AUTO) {
        f.write_str("auto ")?;
    }
    write!(f, "trait {}", data.name.display(f.db, f.edition()))?;
    write_generic_params(GenericDefId::TraitId(trait_.id), f)?;
    Ok(())
}

impl<'db> HirDisplay<'db> for TypeAlias {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        write_visibility(self.module(f.db).id, self.visibility(f.db), f)?;
        let data = TypeAliasSignature::of(f.db, self.id);
        write!(f, "type {}", data.name.display(f.db, f.edition()))?;
        let def_id = GenericDefId::TypeAliasId(self.id);
        write_generic_params(def_id, f)?;
        if !data.bounds.is_empty() {
            f.write_str(": ")?;
            f.write_joined(
                data.bounds.iter().map(|bound| {
                    hir_display_with_store(
                        bound,
                        ExpressionStoreOwnerId::Signature(self.id.into()),
                        &data.store,
                    )
                }),
                " + ",
            )?;
        }
        if let Some(ty) = data.ty {
            f.write_str(" = ")?;
            ty.hir_fmt(f, ExpressionStoreOwnerId::Signature(self.id.into()), &data.store)?;
        }
        write_where_clause(def_id, f)?;
        Ok(())
    }
}

impl<'db> HirDisplay<'db> for Module {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        match self.parent(f.db) {
            Some(m) => write_visibility(m.id, self.visibility(f.db), f)?,
            None => {
                return match self.krate(f.db).display_name(f.db) {
                    Some(name) => write!(f, "extern crate {name}"),
                    None => f.write_str("extern crate {unknown}"),
                };
            }
        }
        match self.name(f.db) {
            Some(name) => write!(f, "mod {}", name.display(f.db, f.edition())),
            None => f.write_str("mod {unknown}"),
        }
    }
}

impl<'db> HirDisplay<'db> for Crate {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        match self.display_name(f.db) {
            Some(name) => write!(f, "extern crate {name}"),
            None => f.write_str("extern crate {unknown}"),
        }
    }
}

impl<'db> HirDisplay<'db> for Macro {
    fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result {
        match self.id {
            hir_def::MacroId::Macro2Id(_) => f.write_str("macro"),
            hir_def::MacroId::MacroRulesId(_) => f.write_str("macro_rules!"),
            hir_def::MacroId::ProcMacroId(_) => f.write_str("proc_macro"),
        }?;
        write!(f, " {}", self.name(f.db).display(f.db, f.edition()))
    }
}
